使用屬性值

在大部分的情況下,Entity Framework 會負責追蹤實體實例屬性的狀態、原始值和目前值。 不過,在某些情況下,您可能會想要檢視或操作 EF 關於屬性的資訊,例如已中斷連線的案例。 本主題所示範的技巧同樣適用於使用 Code First 和 EF 設計工具所建立的模型。

Entity Framework 會針對追蹤實體的每個屬性追蹤兩個值。 目前的值為實體中屬性的目前值,如名稱所示。 原始值是屬性從資料庫查詢實體或附加至內容時所擁有的值。

使用屬性值有兩個一般機制:

  • 單一屬性的值可以使用 Property 方法,以強型別的方式取得。
  • 實體的所有屬性值都可以讀入 DbPropertyValues 物件。 DbPropertyValues 接著會做為類似字典的物件,以允許讀取和設定屬性值。 DbPropertyValues 物件中的值可以從另一個 DbPropertyValues 物件中的值,或從其他物件中的值設定,例如實體的另一個複本或簡單的資料傳輸物件 (DTO)。

下列各節顯示使用上述兩種機制的範例。

取得和設定個別屬性的目前或原始值

下列範例顯示如何讀取屬性的目前值,然後設定為新的值:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(3);

    // Read the current value of the Name property
    string currentName1 = context.Entry(blog).Property(u => u.Name).CurrentValue;

    // Set the Name property to a new value
    context.Entry(blog).Property(u => u.Name).CurrentValue = "My Fancy Blog";

    // Read the current value of the Name property using a string for the property name
    object currentName2 = context.Entry(blog).Property("Name").CurrentValue;

    // Set the Name property to a new value using a string for the property name
    context.Entry(blog).Property("Name").CurrentValue = "My Boring Blog";
}

使用 OriginalValue 屬性,而不是 CurrentValue 屬性來讀取或設定原始值。

請注意,當字串用來指定屬性名稱時,傳回的值會輸入為 「object」。 另一方面,如果使用 Lambda 運算式,則傳回的值會強型別。

如果新的值與舊值不同,則設定這樣的屬性值只會將屬性標示為已修改。

以這種方式設定屬性值時,即使 AutoDetectChanges 已關閉,也會自動偵測變更。

取得和設定未對應的屬性目前值

也可以讀取未對應至資料庫之屬性的目前值。 未對應的屬性範例可能是 Blog 上的 RssLink 屬性。 此值可能會根據 BlogId 計算,因此不需要儲存在資料庫中。 例如:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);
    // Read the current value of an unmapped property
    var rssLink = context.Entry(blog).Property(p => p.RssLink).CurrentValue;

    // Use a string to specify the property name
    var rssLinkAgain = context.Entry(blog).Property("RssLink").CurrentValue;
}

如果 屬性公開 setter,也可以設定目前的值。

執行未對應屬性的 Entity Framework 驗證時,讀取未對應的屬性值很有用。 基於相同的原因,目前值可以讀取和設定內容目前未追蹤之實體的屬性。 例如:

using (var context = new BloggingContext())
{
    // Create an entity that is not being tracked
    var blog = new Blog { Name = "ADO.NET Blog" };

    // Read and set the current value of Name as before
    var currentName1 = context.Entry(blog).Property(u => u.Name).CurrentValue;
    context.Entry(blog).Property(u => u.Name).CurrentValue = "My Fancy Blog";
    var currentName2 = context.Entry(blog).Property("Name").CurrentValue;
    context.Entry(blog).Property("Name").CurrentValue = "My Boring Blog";
}

請注意,原始值不適用於未對應的屬性,或內容未追蹤之實體的屬性。

檢查屬性是否標示為已修改

下列範例示範如何檢查是否將個別屬性標示為已修改:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    var nameIsModified1 = context.Entry(blog).Property(u => u.Name).IsModified;

    // Use a string for the property name
    var nameIsModified2 = context.Entry(blog).Property("Name").IsModified;
}

呼叫 SaveChanges 時,已修改屬性的值會以更新的形式傳送至資料庫。

將屬性標示為已修改

下列範例示範如何強制將個別屬性標示為已修改:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    context.Entry(blog).Property(u => u.Name).IsModified = true;

    // Use a string for the property name
    context.Entry(blog).Property("Name").IsModified = true;
}

當呼叫 SaveChanges 時,將屬性標示為修改時,強制將更新傳送至屬性的資料庫,即使屬性的目前值與其原始值相同也一樣。

目前無法重設個別屬性,使其在標示為修改之後無法修改。 這是我們計畫在未來版本中支援的內容。

讀取實體所有屬性的目前、原始和資料庫值

下列範例示範如何讀取目前值、原始值,以及實體所有對應屬性的實際資料庫中的值。

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Make a modification to Name in the tracked entity
    blog.Name = "My Cool Blog";

    // Make a modification to Name in the database
    context.Database.SqlCommand("update dbo.Blogs set Name = 'My Boring Blog' where Id = 1");

    // Print out current, original, and database values
    Console.WriteLine("Current values:");
    PrintValues(context.Entry(blog).CurrentValues);

    Console.WriteLine("\nOriginal values:");
    PrintValues(context.Entry(blog).OriginalValues);

    Console.WriteLine("\nDatabase values:");
    PrintValues(context.Entry(blog).GetDatabaseValues());
}

public static void PrintValues(DbPropertyValues values)
{
    foreach (var propertyName in values.PropertyNames)
    {
        Console.WriteLine("Property {0} has value {1}",
                          propertyName, values[propertyName]);
    }
}

目前的值是實體目前包含之屬性的值。 原始值是查詢實體時從資料庫讀取的值。 資料庫值是值,因為它們目前儲存在資料庫中。 取得資料庫值時,當資料庫中的值可能已經變更,因為已查詢實體,例如其他使用者對資料庫的並行編輯時。

設定另一個物件的目前或原始值

從另一個物件複製值,即可更新追蹤實體的目前或原始值。 例如:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);
    var coolBlog = new Blog { Id = 1, Name = "My Cool Blog" };
    var boringBlog = new BlogDto { Id = 1, Name = "My Boring Blog" };

    // Change the current and original values by copying the values from other objects
    var entry = context.Entry(blog);
    entry.CurrentValues.SetValues(coolBlog);
    entry.OriginalValues.SetValues(boringBlog);

    // Print out current and original values
    Console.WriteLine("Current values:");
    PrintValues(entry.CurrentValues);

    Console.WriteLine("\nOriginal values:");
    PrintValues(entry.OriginalValues);
}

public class BlogDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}

執行上述程式碼將會列印出來:

Current values:
Property Id has value 1
Property Name has value My Cool Blog

Original values:
Property Id has value 1
Property Name has value My Boring Blog

使用從服務呼叫或多層式應用程式中用戶端取得的值來更新實體時,有時會使用這項技術。 請注意,使用的物件不一定與實體的類型相同,只要其名稱符合實體的屬性。 在上述範例中,使用 BlogDTO 的實例來更新原始值。

請注意,只有從其他物件複製時設定為不同值的屬性才會標示為修改。

設定字典中的目前或原始值

從字典或其他資料結構複製值,即可更新追蹤實體的目前或原始值。 例如:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    var newValues = new Dictionary<string, object>
    {
        { "Name", "The New ADO.NET Blog" },
        { "Url", "blogs.msdn.com/adonet" },
    };

    var currentValues = context.Entry(blog).CurrentValues;

    foreach (var propertyName in newValues.Keys)
    {
        currentValues[propertyName] = newValues[propertyName];
    }

    PrintValues(currentValues);
}

使用 OriginalValues 屬性,而不是 CurrentValues 屬性來設定原始值。

使用 Property 從字典設定目前或原始值

使用 CurrentValues 或 OriginalValues 的替代方法是使用 Property 方法來設定每個屬性的值。 當您需要設定複雜屬性的值時,最好這樣做。 例如:

using (var context = new BloggingContext())
{
    var user = context.Users.Find("johndoe1987");

    var newValues = new Dictionary<string, object>
    {
        { "Name", "John Doe" },
        { "Location.City", "Redmond" },
        { "Location.State.Name", "Washington" },
        { "Location.State.Code", "WA" },
    };

    var entry = context.Entry(user);

    foreach (var propertyName in newValues.Keys)
    {
        entry.Property(propertyName).CurrentValue = newValues[propertyName];
    }
}

在上述範例中,複雜屬性是使用點名稱來存取。 如需存取複雜屬性的其他方式,請參閱本主題稍後的兩節,特別是複雜屬性。

建立包含目前、原始或資料庫值的複製物件

從 CurrentValues、OriginalValues 或 GetDatabaseValues 傳回的 DbPropertyValues 物件可用來建立實體的複製品。 此複製品將包含用來建立它的 DbPropertyValues 物件的屬性值。 例如:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();
}

請注意,傳回的物件不是實體,而且不會由內容追蹤。 傳回的物件也沒有任何關聯性設定為其他物件。

複製的物件可用於解決與資料庫並行更新相關的問題,特別是使用牽涉到特定類型物件之資料系結的 UI。

取得和設定複雜屬性的目前或原始值

您可以使用 Property 方法讀取和設定整個複雜物件的值,就如同基本屬性一樣。 此外,您也可以向下切入至複雜物件,以及讀取或設定該物件的屬性,甚至是巢狀物件。 以下列出一些範例:

using (var context = new BloggingContext())
{
    var user = context.Users.Find("johndoe1987");

    // Get the Location complex object
    var location = context.Entry(user)
                       .Property(u => u.Location)
                       .CurrentValue;

    // Get the nested State complex object using chained calls
    var state1 = context.Entry(user)
                     .ComplexProperty(u => u.Location)
                     .Property(l => l.State)
                     .CurrentValue;

    // Get the nested State complex object using a single lambda expression
    var state2 = context.Entry(user)
                     .Property(u => u.Location.State)
                     .CurrentValue;

    // Get the nested State complex object using a dotted string
    var state3 = context.Entry(user)
                     .Property("Location.State")
                     .CurrentValue;

    // Get the value of the Name property on the nested State complex object using chained calls
    var name1 = context.Entry(user)
                       .ComplexProperty(u => u.Location)
                       .ComplexProperty(l => l.State)
                       .Property(s => s.Name)
                       .CurrentValue;

    // Get the value of the Name property on the nested State complex object using a single lambda expression
    var name2 = context.Entry(user)
                       .Property(u => u.Location.State.Name)
                       .CurrentValue;

    // Get the value of the Name property on the nested State complex object using a dotted string
    var name3 = context.Entry(user)
                       .Property("Location.State.Name")
                       .CurrentValue;
}

使用 OriginalValue 屬性,而不是 CurrentValue 屬性來取得或設定原始值。

請注意,Property 或 ComplexProperty 方法可用來存取複雜屬性。 不過,如果您想要向下切入具有其他 Property 或 ComplexProperty 呼叫的複雜物件,則必須使用 ComplexProperty 方法。

使用 DbPropertyValues 存取複雜屬性

當您使用 CurrentValues、OriginalValues 或 GetDatabaseValues 來取得實體的所有目前、原始或資料庫值時,任何複雜屬性的值都會以巢狀 DbPropertyValues 物件的形式傳回。 這些巢狀物件接著可用來取得複雜物件的值。 例如,下列方法會列印出所有屬性的值,包括任何複雜屬性的值和巢狀複雜屬性的值。

public static void WritePropertyValues(string parentPropertyName, DbPropertyValues propertyValues)
{
    foreach (var propertyName in propertyValues.PropertyNames)
    {
        var nestedValues = propertyValues[propertyName] as DbPropertyValues;
        if (nestedValues != null)
        {
            WritePropertyValues(parentPropertyName + propertyName + ".", nestedValues);
        }
        else
        {
            Console.WriteLine("Property {0}{1} has value {2}",
                              parentPropertyName, propertyName,
                              propertyValues[propertyName]);
        }
    }
}

若要列印出所有目前的屬性值,方法會呼叫如下:

using (var context = new BloggingContext())
{
    var user = context.Users.Find("johndoe1987");

    WritePropertyValues("", context.Entry(user).CurrentValues);
}