假设我有一个应用程序,例如一个网站,我的objectcontext在请求期间离开。我应该缓存一些使用EF加载的数据,以避免读取数据库并提高性能。
好的,我用EF读取了我的数据,我把我的对象放在缓存中(说AppFabric,而不是内存缓存),但是可以延迟加载的相关数据现在为null(访问此属性会导致nullreferenceexception) 。我不想在一个请求中加载所有内容,因为它会太长,所以我想按需加载,一旦读取,我想用新获取的数据完成缓存。 / p>
注意:
我该怎么做?
编辑:我用northwind数据库构建了这个样本,它正在运行:class Program
{
static void Main(string[] args)
{
// normal use
List<Products> allProductCached = null;
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
allProductCached = db.Products.ToList().Clone<DbSet<Products>>();
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<Suppliers>();
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<Products> Clone<T>(this List<Products> list)
{
return list.Select(obj =>
new Products
{
ProductName = obj.ProductName,
SupplierID = obj.SupplierID,
UnitPrice = obj.UnitPrice
}).ToList();
}
public static Suppliers Clone<T>(this Suppliers obj)
{
if (obj == null)
return null;
return new Suppliers
{
SupplierID = obj.SupplierID,
CompanyName = obj.CompanyName
};
}
}
问题是我必须复制所有内容(不丢失属性)并在属性为null时测试到处并加载所需的属性。我的代码当然越来越复杂,如果我错过了某些东西,这将成为一个问题。没有其他解决方案?
答案 0 :(得分:12)
如果没有ObjectContext
或DbContext
,您无法访问EF中的数据库。
即使您不再拥有原始上下文,仍然可以有效地使用缓存。
也许您的情况是这样的......想象一下,您经常使用一些参考数据。您不希望每次需要时都访问数据库,因此将其存储在缓存中。您还有每个用户您不想缓存的数据。您具有从用户数据到参考数据的导航属性。您希望从数据库加载用户数据,并让EF 自动“修复”导航属性以指向参考数据。
对于请求:
DbContext
。DbSet.Attach()
)EF中的延迟加载通常使用dynamic proxies来完成。我们的想法是,您可以创建所有可能动态加载虚拟的属性。每当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; }
}
}
以下是一个与您的不同的示例模型,它说明了如何使其原则上正常工作。