Entity Framework Code First實體對象變動跟蹤


  Entity Framework Code First通過DbContext.ChangeTracker對實體對象的變動進行跟蹤,實現跟蹤的方式有兩種:變動跟蹤快照和變動跟蹤代理。

  變動跟蹤快照:前面幾篇隨筆的示例都是通過實體對象變動快照跟蹤來實現數據操作的,POCO模型不包含任何邏輯去通知Entity Framework實體類屬性的變動。Entity Framework在第一次對象加載到內存中時進行一次快照,添加快照發生在返回一次查詢或添加一個對象到DbSet中時。當Entity Framework需要知道對象的變動時,將先把當前實體與快照中的對象進行掃描對比。實現掃描對比的方法是調用DbContext.ChangeTracker的DetectChanges方法。

  變動跟蹤代理:變動跟蹤代理是一種會主動通知Entity Framework實體對象發生變動的機制。如:延遲加載的實現方式。要使用變動跟蹤代理,需要在定義的類結構中,Entity Framework可以在運行時從POCO類中創建動態類型並重寫POCO屬性。動態代理就是一種動態類型,包含重寫屬性和通知Entity Framework實體對象變動的邏輯。

  Entity Framework Code First中能夠自動調用DbContext.ChangeTracker.DetectChanges的方法:

  ◊ DbSet.Add

  ◊ DbSet.Find

  ◊ DbSet.Remove

  ◊ DbSet.Attach

  ◊ DbSet.Local

  ◊ DbContext.SaveChanges

  ◊ DbContext.GetValidationErrors

  ◊ DbContext.Entry

  ◊ DbChangeTracker.Entries

  ◊ 任何在DbSet上進行LINQ的查詢

  1、控制什么時間調用DetectChanges

  大部分的實例對象的變動調整需要在Entity Framework進行SaveChanges時才會知道,但也可以根據需要調用變動跟蹤獲取當前對象的狀態。

  Entity Framework Code First的DbContext.DetectChanges在檢測實例對象的變動時,大部分情況不會有性能的問題。但當有大量的實例對象在內存中,或DbContext有大量的操作時,自動的DetectChanges行為可能會一定程度的影響性能。Entity Framework提供關閉自動的DetectChanges的功能,在需要的時候進行手動調用。

using (var ctx = new PortalContext())
{
    ctx.Configuration.AutoDetectChangesEnabled = false;
}

  示例:

using (var ctx = new PortalContext())
{
    ctx.Configuration.AutoDetectChangesEnabled = false;

    var province = ctx.Provinces.Find(1);
    province.ProvinceName = "測試";

    Console.WriteLine("Before DetectChanges:{0}", ctx.Entry(province).State);

    ctx.ChangeTracker.DetectChanges();

    Console.WriteLine("After DetectChanges:{0}", ctx.Entry(province).State);
}

  代碼運行結果:

Before DetectChanges:Unchanged
After DetectChanges:Modified

  2、獲取不帶變動跟蹤的實體查詢

  在一些情況下,我們只需要查詢返回一個只讀的數據記錄,而不會對數據記錄進行任何的修改。這種時候不希望Entity Framework進行不必要的狀態變動跟蹤,可以使用Entity Framework的AsNoTracking方法來查詢返回不帶變動跟蹤的查詢結果。

using (var ctx = new PortalContext())
{
    foreach (var province in ctx.Provinces.AsNoTracking())
    {
        Console.WriteLine(province.ProvinceName);
    }
}

  以上代碼中使用AsNoTracking方法查詢返回無變動跟蹤的Province的DbSet,由於是無變動跟蹤,所以對返回的Province集中數據的任何修改,在SaveChanges()時,都不會提交到數據庫中。

  AsNoTracking是定義在IQueryable<T>中的擴展方法,所以也可以用於LINQ表達式查詢。

using (var ctx = new PortalContext())
{
    var query = from p in ctx.Provinces.AsNoTracking()
                where p.ProvinceID > 10
                select p;
    foreach (var province in query)
    {
        Console.WriteLine(province.ProvinceName);
    }
}

using (var ctx = new PortalContext())
{
    var query = from p in ctx.Provinces
                where p.ProvinceID > 10
                select p;
    query = query.AsNoTracking();

    foreach (var province in query)
    {
        Console.WriteLine(province.ProvinceName);
    }
}

  注:使用AsNoTracking需要添加引用命名空間using System.Data.Entity。

  3、單個實體的變動跟蹤信息及操作

  使用狀態屬性:

using (var ctx = new PortalContext())
{
    var province = ctx.Provinces.Find(10);
    DbEntityEntry<Province> entry = ctx.Entry(province);
    Console.WriteLine("Before Edit: {0}", entry.State);
    province.ProvinceName = "Test";
    ctx.ChangeTracker.DetectChanges();
    Console.WriteLine("After Edit: {0}", entry.State);
}

  注:DbEntityEntry需要引用命名空間using System.Data.Entity.Infrastructure;

  代碼運行結果為:

Before Edit: Unchanged
After Edit: Modified

  4、查看對象的當前值、原始值及數據庫中的值

  通過DbEntityEntry可以獲取對象的當前、原始及在數據庫中的值,DbPropertyValues則用於保存對象具體的屬性。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;

using Portal.Models;

namespace Portal
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var ctx = new PortalContext())
            {
                var province = ctx.Provinces.Find(10);
                province.ProvinceName = "Test";

                ctx.Database.ExecuteSqlCommand("UPDATE Province SET ProvinceName = 'Testing' WHERE ProvinceID = 10");

                PrintChangeTrackingInfo(ctx, province);
            }
        }

        static void PrintChangeTrackingInfo(DbContext ctx, Province province)
        {
            var entry = ctx.Entry(province);
            Console.WriteLine(entry.State);

            Console.WriteLine("\nCurrent Values:");
            PrintPropertyValues(entry.CurrentValues);

            Console.WriteLine("\nOriginal Values:");
            PrintPropertyValues(entry.OriginalValues);

            Console.WriteLine("\nDatabase Values:");
            PrintPropertyValues(entry.GetDatabaseValues());
        }

        static void PrintPropertyValues(DbPropertyValues values)
        {
            foreach (var propertyName in values.PropertyNames)
            {
                Console.WriteLine("- {0}-{1}", propertyName, values[propertyName]);
            }
        }
    }
}

  代碼運行的結果:

Modified

Current Values:
- ProvinceID-10
- ProvinceNo-320000
- ProvinceName-Test

Original Values:
- ProvinceID-10
- ProvinceNo-320000
- ProvinceName-測試

Database Values:
- ProvinceID-10
- ProvinceNo-320000
- ProvinceName-Testing
請按任意鍵繼續. . .

  代碼運行所執行的SQL語句:

exec sp_executesql N'SELECT 
[Limit1].[ProvinceID] AS [ProvinceID], 
[Limit1].[ProvinceNo] AS [ProvinceNo], 
[Limit1].[ProvinceName] AS [ProvinceName]
FROM ( SELECT TOP (2) 
    [Extent1].[ProvinceID] AS [ProvinceID], 
    [Extent1].[ProvinceNo] AS [ProvinceNo], 
    [Extent1].[ProvinceName] AS [ProvinceName]
    FROM [dbo].[Province] AS [Extent1]
    WHERE [Extent1].[ProvinceID] = @p0
)  AS [Limit1]',N'@p0 int',@p0=10
UPDATE Province SET ProvinceName = 'Testing' WHERE ProvinceID = 10
exec sp_executesql N'SELECT 
[Limit1].[ProvinceID] AS [ProvinceID], 
[Limit1].[ProvinceNo] AS [ProvinceNo], 
[Limit1].[ProvinceName] AS [ProvinceName]
FROM ( SELECT TOP (2) 
    [Extent1].[ProvinceID] AS [ProvinceID], 
    [Extent1].[ProvinceNo] AS [ProvinceNo], 
    [Extent1].[ProvinceName] AS [ProvinceName]
    FROM [dbo].[Province] AS [Extent1]
    WHERE [Extent1].[ProvinceID] = @p0
)  AS [Limit1]',N'@p0 int',@p0=10

  從代碼運行所執行的SQL語句可以看出,在最后獲取對象在數據庫中的值時,Entity Framework再一次到數據庫中去查詢對象的記錄值。

  5、修改DbPropertyValues中的值

  DbPropertyValues中的值不是只讀,故可以在第一次加載之后進行修改。

using (var ctx = new PortalContext())
{
    ctx.Configuration.AutoDetectChangesEnabled = false;
    var province = ctx.Provinces.Find(10);
    ctx.Entry(province)
        .CurrentValues["ProvinceName"] = "測試";

    Console.WriteLine("Property Value:{0}", province.ProvinceName);
    Console.WriteLine("State:{0}", ctx.Entry(province).State);
}

  運行結果:

Property Value:測試
State:Modified

  在上面的代碼中,盡管已經禁用了自動偵測變動,但在修改了屬性值之后,對象的屬性仍修改為Modified。實體屬性的修改是通過Change Tracker API實現的,實體的狀態在不需要調用DetectChanges即修改為Modified。

  若不希望實體的狀態發生改變,則實現方式為:

using (var ctx = new PortalContext())
{
    ctx.Configuration.AutoDetectChangesEnabled = false;
    var province = ctx.Provinces.Find(10);

    var _province = ctx.Entry(province).CurrentValues.Clone();
    _province["ProvinceName"] = "測試";

    Console.WriteLine("Property Value:{0}", province.ProvinceName);
    Console.WriteLine("State:{0}", ctx.Entry(province).State);
}

  運行結果:

Property Value:Test
State:Unchanged

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM