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

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

  變更跟蹤快照:前面幾篇隨筆的示例都是經過實體對象變更快照跟蹤來實現數據操做的,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.Addspa

  ◊ DbSet.Find代理

  ◊ DbSet.Removecode

  ◊ DbSet.Attach對象

  ◊ DbSet.Localblog

  ◊ DbContext.SaveChanges內存

  ◊ DbContext.GetValidationErrors

  ◊ DbContext.Entry

  ◊ DbChangeTracker.Entries

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

  一、控制什麼時間調用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

  二、獲取不帶變更跟蹤的實體查詢

  在一些狀況下,咱們只須要查詢返回一個只讀的數據記錄,而不會對數據記錄進行任何的修改。這種時候不但願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。

  三、單個實體的變更跟蹤信息及操做

  使用狀態屬性:

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

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

  經過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再一次到數據庫中去查詢對象的記錄值。

  五、修改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
相關文章
相關標籤/搜索