使用实体框架缓存和延迟加载

时间:2013-11-28 15:41:56

标签: c# caching entity-framework-4

假设我有一个应用程序,例如一个网站,我的objectcontext在请求期间离开。我应该缓存一些使用EF加载的数据,以避免读取数据库并提高性能。

好的,我用EF读取了我的数据,我把我的对象放在缓存中(说AppFabric,而不是内存缓存),但是可以延迟加载的相关数据现在为null(访问此属性会导致nullreferenceexception) 。我不想在一个请求中加载所有内容,因为它会太长,所以我想按需加载,一旦读取,我想用新获取的数据完成缓存。 / p>

注意:

  • 仅读取操作,无创建/更新/删除。
  • 不想使用Jarek Kowalski制作的“EF Provider Wrappers”等二级缓存

我该怎么做?

编辑:我用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时测试到处并加载所需的属性。我的代码当然越来越复杂,如果我错过了某些东西,这将成为一个问题。没有其他解决方案?

1 个答案:

答案 0 :(得分:12)

如果没有ObjectContextDbContext,您无法访问EF中的数据库。

即使您不再拥有原始上下文,仍然可以有效地使用缓存。

也许您的情况是这样的......想象一下,您经常使用一些参考数据。您不希望每次需要时都访问数据库,因此将其存储在缓存中。您还有每个用户您不想缓存的数据。您具有从用户数据到参考数据的导航属性。您希望从数据库加载用户数据,并让EF 自动“修复”导航属性以指向参考数据。

对于请求:

  1. 创建新的DbContext
  2. 从缓存中检索参考数据。
  3. 制作deep copy个参考对象。 (您可能不希望同时将相同的实体附加到多个上下文。)
  4. 将每个参考对象附加到上下文。 (例如DbSet.Attach()
  5. 执行加载每用户数据所需的任何查询。 EF将自动“修复”对参考数据的引用。
  6. 识别可以缓存的新加载的实体。确保它们不包含对不应缓存的实体的引用,然后将它们保存到缓存中。
  7. 处理上下文。
  8. 克隆对象和延迟加载

    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; }
        }
    }
    

    以下是一个与您的不同的示例模型,它说明了如何使其原则上正常工作。