假设我有一个应用程序,例如一个网站,我的objectcontext在请求期间离开.我应该缓存一些使用EF加载的数据,以避免读取数据库并提高性能.
好吧,我用EF读取数据,我将对象放在缓存中(说AppFabric,而不是内存缓存),但是可以延迟加载的相关数据现在为空(对此属性的访问会导致nullreferenceexception).我不想在一个请求中加载所有内容,因为它会太长,所以我希望按需加载,一旦读取,我想用新获取的数据完成缓存.
注意 :
只读操作,没有创建/更新/删除.
不想使用Jarek Kowalski制作的"EF Provider Wrappers"等二级缓存
我怎样才能做到这一点 ?
编辑:我用northwind数据库构建了这个样本,它正在工作:
class Program { static void Main(string[] args) { // normal use ListallProductCached = null; using (NORTHWNDEntities1 db = new NORTHWNDEntities1()) { allProductCached = db.Products.ToList().Clone >(); foreach (var product in db.Products.Where(e => e.UnitPrice > 100)) { Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName); } } // try to use cache, but missing Suppliers using (NORTHWNDEntities1 db = new NORTHWNDEntities1()) { foreach (var product in allProductCached.Where(e => e.UnitPrice > 100)) { if (product.Suppliers == null) product.Suppliers = db.Suppliers.FirstOrDefault(s => s.SupplierID == product.SupplierID).Clone (); Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName); } } // try to use full cache using (NORTHWNDEntities1 db = new NORTHWNDEntities1()) { foreach (var product in allProductCached.Where(e => e.UnitPrice > 100)) { Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName); } } } } public static class Ext { public static List Clone (this List list) { return list.Select(obj => new Products { ProductName = obj.ProductName, SupplierID = obj.SupplierID, UnitPrice = obj.UnitPrice }).ToList(); } public static Suppliers Clone (this Suppliers obj) { if (obj == null) return null; return new Suppliers { SupplierID = obj.SupplierID, CompanyName = obj.CompanyName }; } }
问题是我必须复制所有内容(不丢失属性)并在属性为null时测试到处并加载所需的属性.我的代码当然越来越复杂,如果我错过了某些东西,这将是一个问题.没其他解决方案?
如果没有a 或a,则无法访问EF中的数据库.ObjectContext
DbContext
您能仍然有效地使用缓存,即使你没有原来的语境了.
也许您的场景是这样的......想象一下,您有一些经常使用的参考数据.您不希望每次需要时都访问数据库,因此将其存储在缓存中.您还拥有不想缓存的每用户数据.您具有从用户数据到参考数据的导航属性.您希望从数据库加载用户数据,并让EF 自动"修复"导航属性以指向参考数据.
对于请求:
创建一个新的DbContext
.
从缓存中检索参考数据.
制作参考对象的深层副本.(您可能不希望同时将多个上下文连接到相同的实体.)
将每个引用对象附加到上下文.(例如DbSet.Attach()
)
执行加载每用户数据所需的任何查询.EF将自动"修复"对参考数据的引用.
识别可以缓存的新加载的实体.确保它们不包含对不应缓存的实体的引用,然后将它们保存到缓存中.
处理上下文.
EF中的延迟加载通常使用动态代理来完成.我们的想法是,您可以创建所有可能动态加载虚拟的属性.每当EF创建实体类型的实例时,它实际上替换了派生类型,并且该派生类型在其属性的重写版本中具有延迟加载逻辑.
这一切都很好,但在这种情况下,您将实体对象附加到非EF创建的上下文中.您使用一个名为的方法创建了它们Clone
.您实例化了真正的POCO实体,而不是一些神秘的EF动态代理类型.这意味着您不会在这些实体上进行延迟加载.
解决方案很简单.该Clone
方法必须采取另一个参数:DbContext
.不要使用实体的构造函数来创建新实例.相反,使用DbSet.Create()
.这将返回动态代理.然后初始化其属性以创建引用实体的克隆.然后将其附加到上下文中.
以下是您可能用于克隆单个Products
实体的代码:
public static Products Clone(this Products product, DbContext context) { var set = context.Set<Products>(); var clone = set.Create(); clone.ProductName = product.ProductName; clone.SupplierID = product.SupplierID; clone.UnitProce = product.UnitPrice; // Initialize collection so you don't have to do the null check, but // if the property is virtual and proxy creation is enabled, it should get lazy loaded. clone.Suppliers = new List<Suppliers>(); return clone; }
namespace EFCacheLazyLoadDemo { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Linq; class Program { static void Main(string[] args) { // Add some demo data. using (MyContext c = new MyContext()) { var sampleData = new Master { Details = { new Detail { SomeDetail = "Cod" }, new Detail { SomeDetail = "Haddock" }, new Detail { SomeDetail = "Perch" } } }; c.Masters.Add(sampleData); c.SaveChanges(); } Master cachedMaster; using (MyContext c = new MyContext()) { c.Configuration.LazyLoadingEnabled = false; c.Configuration.ProxyCreationEnabled = false; // We don't load the details here. And we don't even need a proxy either. cachedMaster = c.Masters.First(); } Console.WriteLine("Reference entity details count: {0}.", cachedMaster.Details.Count); using (MyContext c = new MyContext()) { var liveMaster = cachedMaster.DeepCopy(c); c.Masters.Attach(liveMaster); Console.WriteLine("Re-attached entity details count: {0}.", liveMaster.Details.Count); } Console.ReadKey(); } } public static class MasterExtensions { public static Master DeepCopy(this Master source, MyContext context) { var copy = context.Masters.Create(); copy.MasterId = source.MasterId; foreach (var d in source.Details) { var copyDetail = context.Details.Create(); copyDetail.DetailId = d.DetailId; copyDetail.MasterId = d.MasterId; copyDetail.Master = copy; copyDetail.SomeDetail = d.SomeDetail; } return copy; } } public class MyContext : DbContext { static MyContext() { // Just for demo purposes, re-create db each time this runs. Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>()); } public DbSet<Master> Masters { get { return this.Set<Master>(); } } public DbSet<Detail> Details { get { return this.Set<Detail>(); } } } public class Master { public Master() { this.Details = new List<Detail>(); } public int MasterId { get; set; } public virtual List<Detail> Details { get; private set; } } public class Detail { public int DetailId { get; set; } public string SomeDetail { get; set; } public int MasterId { get; set; } [ForeignKey("MasterId")] public Master Master { get; set; } } }
这是一个与您的不同的示例模型,它说明了如何使其在原理上正常工作.