使用属性值Working with property values

大多数情况下实体框架将负责跟踪实体实例的属性的状态、原始值和当前值。 但是,在某些情况下(例如,已断开连接的情况下),你希望查看或操作有关属性的信息 EF。 本主题所介绍的方法同样适用于查询使用 Code First 和 EF 设计器创建的模型。

实体框架跟踪所跟踪实体的每个属性的两个值。 当前值为,如名称所示,是实体中属性的当前值。 原始值是从数据库中查询实体或将实体附加到上下文时属性所具有的值。

使用属性值的一般机制有两种:

  • 单个属性的值可以使用属性方法以强类型方式获取。
  • 实体的所有属性的值都可以读取到 DbPropertyValues 对象中。 然后,DbPropertyValues 充当类似字典的对象,以允许读取和设置属性值。 DbPropertyValues 对象中的值可以从其他 DbPropertyValues 对象中的值或其他某个对象的值进行设置,如实体的另一个副本或简单的数据传输对象(DTO)。

以下部分显示了使用上述两种机制的示例。

获取和设置单个属性的当前值或原始值Getting and setting the current or original value of an individual property

下面的示例演示如何读取属性的当前值,然后将其设置为新值:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(3);
  4. // Read the current value of the Name property
  5. string currentName1 = context.Entry(blog).Property(u => u.Name).CurrentValue;
  6. // Set the Name property to a new value
  7. context.Entry(blog).Property(u => u.Name).CurrentValue = "My Fancy Blog";
  8. // Read the current value of the Name property using a string for the property name
  9. object currentName2 = context.Entry(blog).Property("Name").CurrentValue;
  10. // Set the Name property to a new value using a string for the property name
  11. context.Entry(blog).Property("Name").CurrentValue = "My Boring Blog";
  12. }

使用 OriginalValue 属性而非 CurrentValue 属性来读取或设置原始值。

请注意,当使用字符串指定属性名时,返回的值将被类型化为 “object”。 另一方面,如果使用 lambda 表达式,则返回值为强类型。

如果新值不同于旧值,则将属性值设置为时,只会将属性标记为已修改。

以这种方式设置属性值时,即使关闭了 AutoDetectChanges,也会自动检测更改。

获取和设置未映射的属性的当前值Getting and setting the current value of an unmapped property

还可以读取未映射到数据库的属性的当前值。 未映射的属性的一个示例可能是博客上的 .Rsslink 属性。 此值可以基于 BlogId 进行计算,因此无需存储在数据库中。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. // Read the current value of an unmapped property
  5. var rssLink = context.Entry(blog).Property(p => p.RssLink).CurrentValue;
  6. // Use a string to specify the property name
  7. var rssLinkAgain = context.Entry(blog).Property("RssLink").CurrentValue;
  8. }

如果属性公开了 setter,还可以设置当前值。

在对未映射的属性执行实体框架验证时,读取未映射的属性的值非常有用。 出于相同原因,可以读取当前值并将其设置为当前未由上下文跟踪的实体的属性。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. // Create an entity that is not being tracked
  4. var blog = new Blog { Name = "ADO.NET Blog" };
  5. // Read and set the current value of Name as before
  6. var currentName1 = context.Entry(blog).Property(u => u.Name).CurrentValue;
  7. context.Entry(blog).Property(u => u.Name).CurrentValue = "My Fancy Blog";
  8. var currentName2 = context.Entry(blog).Property("Name").CurrentValue;
  9. context.Entry(blog).Property("Name").CurrentValue = "My Boring Blog";
  10. }

请注意,原始值不可用于未映射的属性或上下文未跟踪的实体属性。

检查属性是否被标记为已修改Checking whether a property is marked as modified

下面的示例演示如何检查单个属性是否被标记为已修改:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. var nameIsModified1 = context.Entry(blog).Property(u => u.Name).IsModified;
  5. // Use a string for the property name
  6. var nameIsModified2 = context.Entry(blog).Property("Name").IsModified;
  7. }

调用 SaveChanges 时,已修改属性的值将作为更新发送到数据库。

将属性标记为已修改Marking a property as modified

下面的示例演示如何强制将单个属性标记为已修改:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. context.Entry(blog).Property(u => u.Name).IsModified = true;
  5. // Use a string for the property name
  6. context.Entry(blog).Property("Name").IsModified = true;
  7. }

将属性标记为已修改会强制在调用 SaveChanges 时将更新发送到该属性的数据库,即使该属性的当前值与原始值相同也是如此。

目前不能在标记为 “已修改” 后将单个属性重置为不修改。 这是我们计划在未来版本中提供支持的内容。

读取实体的所有属性的当前值、原始值和数据库值Reading current, original, and database values for all properties of an entity

下面的示例演示如何在数据库中读取实体的所有映射属性的当前值、原始值和值。

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. // Make a modification to Name in the tracked entity
  5. blog.Name = "My Cool Blog";
  6. // Make a modification to Name in the database
  7. context.Database.SqlCommand("update dbo.Blogs set Name = 'My Boring Blog' where Id = 1");
  8. // Print out current, original, and database values
  9. Console.WriteLine("Current values:");
  10. PrintValues(context.Entry(blog).CurrentValues);
  11. Console.WriteLine("\nOriginal values:");
  12. PrintValues(context.Entry(blog).OriginalValues);
  13. Console.WriteLine("\nDatabase values:");
  14. PrintValues(context.Entry(blog).GetDatabaseValues());
  15. }
  16. public static void PrintValues(DbPropertyValues values)
  17. {
  18. foreach (var propertyName in values.PropertyNames)
  19. {
  20. Console.WriteLine("Property {0} has value {1}",
  21. propertyName, values[propertyName]);
  22. }
  23. }

当前值是实体的属性当前包含的值。 原始值是查询实体时从数据库中读取的值。 数据库值是当前存储在数据库中的值。 如果数据库中的值可能在查询实体后发生更改(例如,当另一个用户对数据库进行了并发编辑时),获取数据库值将很有用。

设置其他对象的当前值或原始值Setting current or original values from another object

可以通过从另一个对象复制值来更新已跟踪实体的当前值或原始值。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. var coolBlog = new Blog { Id = 1, Name = "My Cool Blog" };
  5. var boringBlog = new BlogDto { Id = 1, Name = "My Boring Blog" };
  6. // Change the current and original values by copying the values from other objects
  7. var entry = context.Entry(blog);
  8. entry.CurrentValues.SetValues(coolBlog);
  9. entry.OriginalValues.SetValues(boringBlog);
  10. // Print out current and original values
  11. Console.WriteLine("Current values:");
  12. PrintValues(entry.CurrentValues);
  13. Console.WriteLine("\nOriginal values:");
  14. PrintValues(entry.OriginalValues);
  15. }
  16. public class BlogDto
  17. {
  18. public int Id { get; set; }
  19. public string Name { get; set; }
  20. }

运行上面的代码将输出:

  1. Current values:
  2. Property Id has value 1
  3. Property Name has value My Cool Blog
  4. Original values:
  5. Property Id has value 1
  6. Property Name has value My Boring Blog

当使用从服务调用或 n 层应用程序中的客户端获取的值更新实体时,有时会使用此方法。 请注意,所使用的对象不必与实体具有相同的类型,但前提是它具有与实体的名称相匹配的属性。 在上面的示例中,BlogDTO 的实例用于更新原始值。

请注意,从其他对象复制时,仅将设置为不同值的属性标记为已修改。

从字典设置当前值或原始值Setting current or original values from a dictionary

可以通过从字典或其他一些数据结构复制值来更新已跟踪实体的当前值或原始值。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. var newValues = new Dictionary\<string, object>
  5. {
  6. { "Name", "The New ADO.NET Blog" },
  7. { "Url", "blogs.msdn.com/adonet" },
  8. };
  9. var currentValues = context.Entry(blog).CurrentValues;
  10. foreach (var propertyName in newValues.Keys)
  11. {
  12. currentValues[propertyName] = newValues[propertyName];
  13. }
  14. PrintValues(currentValues);
  15. }

使用 OriginalValues 属性而非 CurrentValues 属性来设置原始值。

使用属性设置字典中的当前值或原始值Setting current or original values from a dictionary using Property

如上所述,使用 CurrentValues 或 OriginalValues 的一种替代方法是使用属性方法来设置每个属性的值。 当你需要设置复杂属性的值时,这可能更好。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var user = context.Users.Find("johndoe1987");
  4. var newValues = new Dictionary\<string, object>
  5. {
  6. { "Name", "John Doe" },
  7. { "Location.City", "Redmond" },
  8. { "Location.State.Name", "Washington" },
  9. { "Location.State.Code", "WA" },
  10. };
  11. var entry = context.Entry(user);
  12. foreach (var propertyName in newValues.Keys)
  13. {
  14. entry.Property(propertyName).CurrentValue = newValues[propertyName];
  15. }
  16. }

在上面的示例中,使用点分名称访问复杂属性。 有关访问复杂属性的其他方法,请参阅本主题后面有关复杂属性的两个部分。

创建包含当前、原始或数据库值的克隆对象Creating a cloned object containing current, original, or database values

从 CurrentValues、OriginalValues 或 GetDatabaseValues 返回的 DbPropertyValues 对象可用于创建实体的克隆。 此克隆将包含 DbPropertyValues 对象中用于创建它的属性值。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();
  5. }

请注意,返回的对象不是实体,也不是由上下文跟踪。 返回的对象也不会将任何关系设置为其他对象。

克隆的对象可用于解决与数据库并发更新相关的问题,尤其是在使用涉及到特定类型的对象的数据绑定的 UI 时。

获取和设置复杂属性的当前值或原始值Getting and setting the current or original values of complex properties

可以使用属性方法读取和设置整个复杂对象的值,就像它可用于基元属性一样。 此外,还可以向下钻取到复杂对象,并读取或设置该对象的属性,甚至是嵌套的对象。 下面是一些示例:

  1. using (var context = new BloggingContext())
  2. {
  3. var user = context.Users.Find("johndoe1987");
  4. // Get the Location complex object
  5. var location = context.Entry(user)
  6. .Property(u => u.Location)
  7. .CurrentValue;
  8. // Get the nested State complex object using chained calls
  9. var state1 = context.Entry(user)
  10. .ComplexProperty(u => u.Location)
  11. .Property(l => l.State)
  12. .CurrentValue;
  13. // Get the nested State complex object using a single lambda expression
  14. var state2 = context.Entry(user)
  15. .Property(u => u.Location.State)
  16. .CurrentValue;
  17. // Get the nested State complex object using a dotted string
  18. var state3 = context.Entry(user)
  19. .Property("Location.State")
  20. .CurrentValue;
  21. // Get the value of the Name property on the nested State complex object using chained calls
  22. var name1 = context.Entry(user)
  23. .ComplexProperty(u => u.Location)
  24. .ComplexProperty(l => l.State)
  25. .Property(s => s.Name)
  26. .CurrentValue;
  27. // Get the value of the Name property on the nested State complex object using a single lambda expression
  28. var name2 = context.Entry(user)
  29. .Property(u => u.Location.State.Name)
  30. .CurrentValue;
  31. // Get the value of the Name property on the nested State complex object using a dotted string
  32. var name3 = context.Entry(user)
  33. .Property("Location.State.Name")
  34. .CurrentValue;
  35. }

使用 OriginalValue 属性而非 CurrentValue 属性来获取或设置原始值。

请注意,可以使用属性或 ComplexProperty 方法来访问复杂属性。 但是,如果想要使用其他属性或 ComplexProperty 调用深化到复杂对象,则必须使用 ComplexProperty 方法。

使用 DbPropertyValues 访问复杂属性Using DbPropertyValues to access complex properties

使用 CurrentValues、OriginalValues 或 GetDatabaseValues 获取实体的所有当前值、原始值或数据库值时,任何复杂属性的值将作为嵌套 DbPropertyValues 对象返回。 然后,可以使用这些嵌套对象获取复杂对象的值。 例如,以下方法将打印出所有属性的值,包括任何复杂属性的值和嵌套的复杂属性。

  1. public static void WritePropertyValues(string parentPropertyName, DbPropertyValues propertyValues)
  2. {
  3. foreach (var propertyName in propertyValues.PropertyNames)
  4. {
  5. var nestedValues = propertyValues[propertyName] as DbPropertyValues;
  6. if (nestedValues != null)
  7. {
  8. WritePropertyValues(parentPropertyName + propertyName + ".", nestedValues);
  9. }
  10. else
  11. {
  12. Console.WriteLine("Property {0}{1} has value {2}",
  13. parentPropertyName, propertyName,
  14. propertyValues[propertyName]);
  15. }
  16. }
  17. }

若要打印出所有当前属性值,将调用方法,如下所示:

  1. using (var context = new BloggingContext())
  2. {
  3. var user = context.Users.Find("johndoe1987");
  4. WritePropertyValues("", context.Entry(user).CurrentValues);
  5. }