簡體   English   中英

使用實體框架緩存和延遲加載

[英]Caching and lazy loading with entity framework

假設我有一個應用程序,例如一個網站,我的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
        };
    }
}

問題是我必須復制所有內容(不丟失屬性)並在屬性為null時測試到處並加載所需的屬性。 我的代碼當然越來越復雜,如果我錯過了某些東西,這將成為一個問題。 沒其他解決方案?

如果沒有ObjectContextDbContext 則無法在EF中訪問數據庫。

您仍然可以有效地使用緩存,即使你沒有原始上下文了。

也許您的場景是這樣的......想象一下,您有一些經常使用的參考數據 您不希望每次需要時都訪問數據庫,因此將其存儲在緩存中 您還擁有不想緩存的每用戶數據 您具有從用戶數據到參考數據的導航屬性。 您希望從數據庫加載用戶數據,並讓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;
}

代碼示例

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

這是一個與您的不同的示例模型,它說明了如何使其在原理上正常工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM