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

Tim*_*Tim 8 c# caching entity-framework-4

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

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

注意 :

  • 只读操作,没有创建/更新/删除.
  • 不想使用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
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是我必须复制所有内容(不丢失属性)并在属性为null时测试到处并加载所需的属性.我的代码当然越来越复杂,如果我错过了某些东西,这将是一个问题.没其他解决方案?

Oll*_*lly 12

如果没有a 或a,则无法访问EF中的数据库.ObjectContextDbContext

仍然有效地使用缓存,即使你没有原来的语境了.

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

对于请求:

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

克隆对象和延迟加载

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;
}
Run Code Online (Sandbox Code Playgroud)

代码示例

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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个与您的不同的示例模型,它说明了如何使其在原理上正常工作.