다음을 통해 공유


Using DbContext in EF 4.1 Part 5: Working with Property Values

 


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.

For Working with Property Values see https://msdn.com/data/jj592677


 

Introduction

Version 4.1 of the Entity Framework contains both the Code First approach and the new DbContext API. This API provides a more productive surface for working with the Entity Framework and can be used with the Code First, Database First, and Model First approaches. This is the fifth post of a twelve part series containing collections of patterns and code fragments showing how features of the new API can be used.

The posts in this series do not contain complete walkthroughs. If you haven’t used EF 4.1 before then you should read Part 1 of this series and also Code First Walkthrough or Model and Database First with DbContext before tackling this post.

Working with property values

The Entity Framework keeps track of two values for each property of a tracked entity. The current value is, as the name indicates, the current value of the property in the entity. The original value is the value that the property had when the entity was queried from the database or attached to the context.

There are two general mechanisms for working with property values:

  • The value of a single property can be obtained in a strongly typed way using the Property method.
  • Values for all properties of an entity can be read into a DbPropertyValues object. DbPropertyValues then acts as a dictionary-like object to allow property values to be read and set. The values in a DbPropertyValues object can be set from values in another DbPropertyValues object or from values in some other object, such as another copy of the entity or a simple data transfer object (DTO).

The sections below show examples of using both of the above mechanisms.

Getting and setting the current or original value of an individual property

The example below shows how the current value of a property can be read and then set to a new value:

 using (var context = new UnicornsContext())
{
    var unicorn = context.Unicorns.Find(3);

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

    // Set the Name property to a new value
    context.Entry(unicorn).Property(u => u.Name).CurrentValue = "Franky";

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

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

Use the OriginalValue property instead of the CurrentValue property to read or set the original value.

Note that the returned value is typed as “object” when a string is used to specify the property name. On the other hand, the returned value is strongly typed if a lambda expression is used.

Setting the property value like this will only mark the property as modified if the new value is different from the old value.

When a property value is set in this way the change is automatically detected even if AutoDetectChanges is turned off. (More about automatically detecting changes is covered in Part 12.)

Getting and setting the current value of an unmapped property

The current value of a property that is not mapped to the database can also be read. For example:

 using (var context = new UnicornsContext())
{
    var lady = context.LadiesInWaiting.Find(1, "The EF Castle");

    // Read the current value of an unmapped property
    var name1 = context.Entry(lady).Property(p => p.Name).CurrentValue;

    // Use a string to specify the property name
    var name2 = context.Entry(lady).Property("Name").CurrentValue;
}

The current value can also be set if the property exposes a setter.

Reading the values of unmapped properties is useful when performing Entity Framework validation of unmapped properties. For the same reason current values can be read and set for properties of entities that are not currently being tracked by the context. For example:

 using (var context = new UnicornsContext())
{
    // Create an entity that is not being tracked
    var unicorn = new Unicorn { Name = "Franky" };

    // Read and set the current value of Name as before
    var currentName1 = context.Entry(unicorn).Property(u => u.Name).CurrentValue;
    context.Entry(unicorn).Property(u => u.Name).CurrentValue = "Franky";
    var currentName2 = context.Entry(unicorn).Property("Name").CurrentValue;
    context.Entry(unicorn).Property("Name").CurrentValue = "Squeaky";
}

Note that original values are not available for unmapped properties or for properties of entities that are not being tracked by the context.

Checking whether a property is marked as modified

The example below shows how to check whether or not an individual property is marked as modified:

 using (var context = new UnicornsContext())
{
    var unicorn = context.Unicorns.Find(1);

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

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

The values of modified properties are sent as updates to the database when SaveChanges is called.

Marking a property as modified

The example below shows how to force an individual property to be marked as modified:

 using (var context = new UnicornsContext())
{
    var unicorn = context.Unicorns.Find(1);

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

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

Marking a property as modified forces an update to be send to the database for the property when SaveChanges is called even if the current value of the property is the same as its original value.

It is not currently possible to reset an individual property to be not modified after it has been marked as modified. This is something we plan to support in a future release.

Reading current, original, and database values for all properties of an entity

The example below shows how to read the current values, the original values, and the values actually in the database for all mapped properties of an entity.

 using (var context = new UnicornsContext())
{
    var unicorn = context.Unicorns.Find(1);

    // Make a modification to Name in the tracked entity
    unicorn.Name = "Franky";

    // Make a modification to Name in the database
    context.Database.SqlCommand("update Unicorns set Name = 'Squeaky' where Id = 1");

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

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

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

PrintValues is defined like so:

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

Using the data set by the initializer defined in Part 1 of this series, running the code above will print out:

Current values:

Property Id has value 1

Property Name has value Franky

Property Version has value System.Byte[]

Property PrincessId has value 1

Original values:

Property Id has value 1

Property Name has value Binky

Property Version has value System.Byte[]

Property PrincessId has value 1

Database values:

Property Id has value 1

Property Name has value Squeaky

Property Version has value System.Byte[]

Property PrincessId has value 1

Notice how the current values are, as expected, the values that the properties of the entity currently contain—in this case the value of Name is Franky.

In contrast to the current values, the original values are the values that were read from the database when the entity was queried—the original value of Name is Binky.

Finally, the database values are the values as they are currently stored in the database. The database value of Name is Squeaky because we sent a raw command to the database to update it after we performed the query. Getting the database values is useful when the values in the database may have changed since the entity was queried such as when a concurrent edit to the database has been made by another user. (See Part 9 for more details on dealing with optimistic concurrency.)

Setting current or original values from another object

The current or original values of a tracked entity can be updated by copying values from another object. For example:

 using (var context = new UnicornsContext())
{
    var princess = context.Princesses.Find(1);
    var rapunzel = new Princess { Id = 1, Name = "Rapunzel" };
    var rosannella = new PrincessDto { Id = 1, Name = "Rosannella" };

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

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

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

This code uses the following DTO class:

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

Using the data set by the initializer defined in Part 1 of this series, running the code above will print out:

Current values:

Property Id has value 1

Property Name has value Rapunzel

Original values:

Property Id has value 1

Property Name has value Rosannella

This technique is sometimes used when updating an entity with values obtained from a service call or a client in an n-tier application. Note that the object used does not have to be of the same type as the entity so long as it has properties whose names match those of the entity. In the example above, an instance of PrincessDTO is used to update the original values.

Note that only properties that are set to different values when copied from the other object will be marked as modified.

Setting current or original values from a dictionary

The current or original values of a tracked entity can be updated by copying values from a dictionary or some other data structure. For example:

 using (var context = new UnicornsContext())
{
    var lady = context.LadiesInWaiting.Find(1, "The EF Castle");

    var newValues = new Dictionary<string, object>
    {
        { "FirstName", "Calypso" },
        { "Title", " Prima donna" },
    };

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

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

    PrintValues(currentValues);
}

Use the OriginalValues property instead of the CurrentValues property to set original values.

Setting current or original values from a dictionary using Property

An alternative to using CurrentValues or OriginalValues as shown above is to use the Property method to set the value of each property. This can be preferable when you need to set the values of complex properties. For example:

 using (var context = new UnicornsContext())
{
    var castle = context.Castles.Find("The EF Castle");

    var newValues = new Dictionary<string, object>
    {
        { "Name", "The EF Castle" },
        { "Location.City", "Redmond" },
        { "Location.Kingdom", "Building 18" },
        { "Location.ImaginaryWorld.Name", "Magic Astoria World" },
        { "Location.ImaginaryWorld.Creator", "ADO.NET" },
    };

    var entry = context.Entry(castle);

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

In the example above complex properties are accessed using dotted names. For other ways to access complex properties see the two sections below specifically about complex properties.

Creating a cloned object containing current, original, or database values

The DbPropertyValues object returned from CurrentValues, OriginalValues, or GetDatabaseValues can be used to create a clone of the entity. This clone will contain the property values from the DbPropertyValues object used to create it. For example:

 using (var context = new UnicornsContext())
{
    var unicorn = context.Unicorns.Find(1);

    var clonedUnicorn = context.Entry(unicorn).GetDatabaseValues().ToObject();
}

Note that the object returned is not the entity and is not being tracked by the context. The returned object also does not have any relationships set to other objects.

The cloned object can be useful for resolving issues related to concurrent updates to the database, especially where a UI that involves data binding to objects of a certain type is being used. (See Part 9 for more details on dealing with optimistic concurrency.)

Getting and setting the current or original values of complex properties

The value of an entire complex object can be read and set using the Property method just as it can be for a primitive property. In addition you can drill down into the complex object and read or set properties of that object, or even a nested object. Here are some examples:

 using (var context = new UnicornsContext())
{
    var castle = context.Castles.Find("The EF Castle");

    // Get the Location complex object
    var location = context.Entry(castle)
                       .Property(c => c.Location)
                       .CurrentValue;
    // Get the nested ImaginaryWorld complex object using chained calls
    var world1 = context.Entry(castle)
                     .ComplexProperty(c => c.Location)
                     .Property(l => l.ImaginaryWorld)
                     .CurrentValue;

    // Get the nested ImaginaryWorld complex object using a single lambda expression
    var world2 = context.Entry(castle)
                     .Property(c => c.Location.ImaginaryWorld)
                     .CurrentValue;

    // Get the nested ImaginaryWorld complex object using a dotted string
    var world3 = context.Entry(castle)
                     .Property("Location.ImaginaryWorld")
                     .CurrentValue;

    // Get the value of the Creator property on the nested complex object using
    // chained calls
    var creator1 = context.Entry(castle)
                       .ComplexProperty(c => c.Location)
                       .ComplexProperty(l => l.ImaginaryWorld)
                       .Property(w => w.Creator)
                       .CurrentValue;

    // Get the value of the Creator property on the nested complex object using a
    // single lambda expression
    var creator2 = context.Entry(castle)
                       .Property(c => c.Location.ImaginaryWorld.Creator)
                       .CurrentValue;

    // Get the value of the Creator property on the nested complex object using a
    // dotted string
    var creator3 = context.Entry(castle)
                       .Property("Location.ImaginaryWorld.Creator")
                       .CurrentValue;
}

Use the OriginalValue property instead of the CurrentValue property to get or set an original value.

Note that either the Property or the ComplexProperty method can be used to access a complex property. However, the ComplexProperty method must be used if you wish to drill down into the complex object with additional Property or ComplexProperty calls.

Using DbPropertyValues to access complex properties

When you use CurrentValues, OriginalValues, or GetDatabaseValues to get all the current, original, or database values for an entity, the values of any complex properties are returned as nested DbPropertyValues objects. These nested objects can then be used to get values of the complex object. For example, the following method will print out the values of all properties, including values of any complex properties and nested complex properties.

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

To print out all current property values the method would be called like this:

 using (var context = new UnicornsContext())
{
    var castle = context.Castles.Find("The EF Castle");

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

Using the data set by the initializer defined in Part 1 of this series, running the code above will print out:

Property Name has value The EF Castle

Property Location.City has value Redmond

Property Location.Kingdom has value Rainier

Property Location.ImaginaryWorld.Name has value Magic Unicorn World

Property Location.ImaginaryWorld.Creator has value ADO.NET

Summary

In this part of the series we looked at different ways to get information about property values and manipulate the current, original, and database property values of entities and complex objects.

As always we would love to hear any feedback you have by commenting on this blog post.

For support please use the Entity Framework Forum.

Arthur Vickers

Developer

ADO.NET Entity Framework

Comments

  • Anonymous
    January 30, 2011
    Examples can send me  one? Email:passvcword@126.com

  • Anonymous
    January 31, 2011
    OH MY GOODNESS!!!!!!!!! Thanks SO MUCH ADO.NET Team!!!!!!

  • Anonymous
    March 30, 2011
    Great lecture!!! could u please send me  the examples.. source code...? This is my Email address crow661@gmail.com Thank u so much

  • Anonymous
    May 18, 2011
    The comment has been removed

  • Anonymous
    May 25, 2011
    The comment has been removed

  • Anonymous
    June 02, 2011
    I am getting the same error.  I have my Context coming from a deparate project (.DLL) and have a reference to it.  Everything else work but it seems that the GetDatabaseValues() is broken.  Question?  If its broken or doesn't work with the context being in another referenced project, then what alternative is there to manage concurrency issues using Entiry Framework?  I don't mean to sound naive, but really,  I am struggling to find a good solution to manage concurrency and this article help provide a good solution, and there aren't many out there.  How can I move forward?

  • Anonymous
    June 29, 2011
    Same here, DbContext in different project than Model.

  • Anonymous
    July 11, 2011
    Same problem here. I'm using EF 4.1 RTW. DbContext and the entity classes are in different namespace. If I put them in the same namespace, everything runs fine.

  • Anonymous
    January 24, 2012
    @Shimmy: Thanks for the suggestions—there is certainly more we could do in this area. @Zhilu @rbigbie @Stefan @Attilla: This bug is fixed in EF 4.3 (Beta1 was released last week.) Sorry for not fixing it sooner—we’ll try to fix bugs like this faster in the future. Thanks, Arthur

  • Anonymous
    February 13, 2012
    Hey everyone... I want to have some properties in my model objects that cannot be edited. I mean even if someone changed their values, I want to ignore these values when a save operation is performed. Is there a way to implemet this using EF4.1? Thanks all.

  • Anonymous
    February 13, 2012
    @W,Mansour EF doesn't yet have a way to mark a property as read-only such that it will prevent values being written to the database. However, there are a couple of things you can do. First, you can make the setter private. EF will stll set it when performing a query as long as your app is not running under partial trust. Second, regardless of whether or not you make the setter private, you can override SaveChanges on your context and make a pass through your entities (see context.ChangeTracker.Entries) before saving reverting any changes made to the read-only properties by setting the current value to the original value for each property. Thanks, Arthur

  • Anonymous
    August 16, 2012
    Great!!! could u please send me  the examples.. source code...? This is my Email :1144106538@qq.com Thank u so much