Programming Entity Framework-dbContext 学习笔记第五章

时间:2023-11-24 22:57:26

Programming Entity Framework-dbContext 学习笔记 第五章


将图表添加到Context中的方式及容易出现的错误

方法 结果 警告
Add Root 图标中的所有实体将被跟踪,并标记为Added SaveChage 将试图将所有实体插入数据库,即使数据库中已存在该实体
Attach Root 所有实体将被跟踪并标记为Unchanged 新添加的实体将不会被插入数据库,并容易造成主键冲突
Add or Attach Root,then paint state throughout graph 所有的实体将拥有正确的状态值 建议使用 Add Root 而不是Attach Root,以避免新实体的主键冲突

DbEntityEntry类型包含属性来记录实体各个状态的值:

  1. CurrentValues 包含实体所有属性的当前值。
  2. OriginalValues 包含实体属性未被修改前的值。
  3. GetDatabaseValues() 方法返回实体属性目前在数据库中的值。

    注:对于状态为Added 和 Deleted 的实体不包含后两个取值,试图读取时,将引发异常。

用于打印各个属性的代码:

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

Working with DbPropertyValues for Complex Types

  1. Code First 的默认约定是将不包含主键的实体理解为“复杂类型”,当需要将包含主键的实体定义为“复杂类型”时,可借助[ComplexType]特性进行标注。
  2. 当我们从DbPropertyValues 中获取的值为一个复杂对象时,它将被表示为一个新的 DbPropertyValues 对象。

下面是一个读取含有复杂属性的 DbPropertyValues 的代码

private static void PrintPropertyValues(DbPropertyValues values, int indent = 1)
{
foreach (var propertyName in values.PropertyNames)
{
var value = values[propertyName];
if (value is DbPropertyValues)
{
Console.WriteLine("{0}- Complex Property: {1}", string.Empty.PadLeft(indent),propertyName);
PrintPropertyValues((DbPropertyValues)value, indent + 1);
}
else
{
Console.WriteLine("{0}- {1}: {2}", string.Empty.PadLeft(indent), propertyName, values[propertyName]);
}
}
}

Copying the Values from DbPropertyValues into an Entity

  1. DbPropertyValues 包含一个 ToObject 方法,可以在不覆盖原有实例的情况下,将所有的值复制到一个新的实例对象。

注:ToObject 方法只复制标量属性,忽略导航属性。

Changing Values in a DbPropertyValues

  1. DbPropertyValues 不是只读的,可以被修改。当你修改CurrentValues 的值的时候,将改变当前实例的值。

修改将自动触发 Changes Tracking.

  1. Clone() 方法可以复制所有的DbPropertyValues ,新克隆出来的对象将不会被Change Tracker 跟踪。

Using the SetValues method

  1. 用户在修改了实体的属性值之后,想撤销所有的修改,最简单的方式就是利用 SetValues() 方法,将OriginalValues 中的数据拷贝到 CurrentValues 中。

代码如下:

 entry.CurrentValues.SetValues(entry.OriginalValues);
entry.State = EntityState.Unchanged;//因为SetVaues方法不会自动修改实体的状态,所以需要手动修改。
  1. SetValues方法不尽可以接受DbPropertyValues做为参数,也可以接受其他类型,该方法会自动查找同名属性的值进行覆盖,找不到任何值时,引发异常。
  2. 可以利用SetValues方法实现实体对象的克隆。直接上代码:
private static void CreateDavesCampsite()
{
using (var context = new BreakAwayContext())
{
//从数据库中读取d.Name == "Dave's Dump"的实体
var davesDump = (from d in context.Lodgings where d.Name == "Dave's Dump" select d).Single();
//新建实体
var clone = new Lodging();
//添加到追踪,这样才可以进行复制
context.Lodgings.Add(clone);
//复制,这里传入的就不是DbPropertyValues 的实例
context.Entry(clone).CurrentValues.SetValues(davesDump);
//修改名称
clone.Name = "Dave's Camp";
//保存
context.SaveChanges();
Console.WriteLine("Name: {0}", clone.Name); //output:Dave's Camp
Console.WriteLine("Miles: {0}", clone.MilesFromNearestAirport); //32.65
Console.WriteLine("Contact Id: {0}", clone.PrimaryContactId); //1
//只有名称被修改了。
}
}

Working with Individual Properties

你可以使用 Property, Complex, Reference, and Collection 方法去获取或者操作单独的属性:

  • Property 方法可以用来处理 Scalar 和 Complex 属性
  • Complex 方法提供对复杂属性的附加特殊操作
  • Reference 和 Collection 方法用于导航属性
  • 还有一个Member方法,可以用于任何类型的属性。该方法不是强类型的,仅提供对属性通用信息的访问
Working with Scalar Properties

Property方法可以访问属性的原始值,当前值,和是否被修改等信息,该方法具有弱类型和强类型的两个重载。直接上代码:

private static void WorkingWithPropertyMethod()
{
using (var context = new BreakAwayContext())
{
var davesDump = (from d in context.Lodgings where d.Name == "Dave's Dump" select d).Single();
//此处会调用强类型的泛型方法,所以后面可以使用lambda表达式
var entry = context.Entry(davesDump);
//使用lambda表达式访问Name属性
entry.Property(d => d.Name).CurrentValue = "Dave's Bargain Bungalows";
Console.WriteLine("Current Value: {0}", entry.Property(d => d.Name).CurrentValue);
Console.WriteLine("Original Value: {0}", entry.Property(d => d.Name).OriginalValue);
Console.WriteLine("Modified?: {0}", entry.Property(d => d.Name).IsModified);
}
}

输出结果:

Current Value: Dave's Bargain Bungalows

Original Value: Dave's Dump

Modified?: True

Working with Complex Properties
  1. 提供对复杂属性的操作。

Example 5-20. Accessing change tracking information for a complex property.

private static void WorkingWithComplexMethod()
{
using (var context = new BreakAwayContext())
{
//从数据库检索实体
var julie = (from p in context.People where p.FirstName == "Julie" select p).Single();
//获取Entry
var entry = context.Entry(julie);
//操作复杂属性,这里使用了Property方法操作复杂属性的属性。
entry.ComplexProperty(p => p.Address).Property(a => a.State).CurrentValue = "VT";
//以上方法可以用以下方法代替
entry.Property(p => p.Address.State).CurrentValue = "VT";
//又或者
entry.Property("Address.State").CurrentValue = "VT"; Console.WriteLine("Address.State Modified?: {0}", entry.ComplexProperty(p => p.Address).Property(a => a.State).IsModified); //true
Console.WriteLine("Address Modified?: {0}", entry.ComplexProperty(p => p.Address).IsModified); //true
//链式调用访问复杂属性中包含的复杂属性
Console.WriteLine("Info.Height.Units Modified?: {0}", entry.ComplexProperty(p => p.Info).ComplexProperty(i => i.Height).Property(h => h.Units).IsModified);//false
}
}

当操作复杂属性的时候,Entity Framework 会跟踪它的状态变化,但是不会追踪它的个别属性的变化,当修改其中任一个单独的属性时,所有属性的状态将变为Modified.

  1. 可以修改复杂属性的值
entry.ComplexProperty(p => p.Address).CurrentValue = new Address { State = "VT" };

该操作将会把整个复杂属性标记为:Modified.

Working with Navigation Properties

Reference and Collection 方法被用户访问导航属性

  • Reference 方法用于访问单个实体
  • Collection 方法用于访问集合属性

这些方法提供以下功能:

  1. 读写导航属性的当前值
  2. 从数据库加载关联数据
  3. 获取导航属性代表的查询(query)
Modifying the value of a navigation property

Example 5-21. Change tracking information for a reference navigation property

private static void WorkingWithReferenceMethod()
{
using (var context = new BreakAwayContext())
{
//从数据库检索Name == "Dave's Dump"的实体对象
var davesDump = (from d in context.Lodgings where d.Name == "Dave's Dump" select d).Single();
//获取entry
var entry = context.Entry(davesDump);
//获取并加载Destination导航属性,Load之前改属性为null
entry.Reference(l => l.Destination).Load();
var canyon = davesDump.Destination;
//输出导航属性destination 属性Name的当前值
Console.WriteLine("Current Value After Load: {0}", entry.Reference(d => d.Destination).CurrentValue.Name);
//从数据库检索 Name == "Great Barrier Reef" 的Destination 对象
var reef = (from d in context.Destinations where d.Name == "Great Barrier Reef" select d).Single();
//将导航属性修改为 reef
entry.Reference(d => d.Destination).CurrentValue = reef;
//输出修改后导航属性destination 属性Name的值
Console.WriteLine("Current Value After Change: {0}", davesDump.Destination.Name);
}
}

输出结果:

Current Value After Load: Grand Canyon Current Value

After Change: Great Barrier Reef

Modifying navigation properties with the change tracker

之前,我们在处理标量属性的时候,我们发现,当我们通过Change Tracker修改属性时,不必显示的调用DetectChanges()方法。改变就被跟踪到了。

对于导航属性,同样如此!

Working with collection navigation properties

Example 5-22. Method to explore interacting with a collection property

private static void WorkingWithCollectionMethod()
{
using (var context = new BreakAwayContext())
{
//从数据库检索 Description == "Trip from the database" 的Trip对象
var res = (from r in context.Reservations where r.Trip.Description == "Trip from the database" select r).Single();
var entry = context.Entry(res);
//获取并加载集合导航属性
entry.Collection(r => r.Payments).Load();
//输出导航属性包含记录数
Console.WriteLine("Payments Before Add: {0}", entry.Collection(r => r.Payments).CurrentValue.Count);
//添加一条新记录
var payment = new Payment { Amount = 245 };
//添加到Payments集合,确认被跟踪,这里容易出坑
context.Payments.Add(payment);
//将新对象添加到导航属性
entry.Collection(r => r.Payments).CurrentValue.Add(payment);
//输出导航属性包含记录数
Console.WriteLine("Payments After Add: {0}", entry.Collection(r => r.Payments).CurrentValue.Count);
}
}

输出结果:

Payments Before Add: 1

Payments After Add: 2

和之前的其他属性不同,修改集合导航属性后必须手动调用DetectChanges()方法来跟踪变化

Refreshing an Entity from the Database

Entity Framework 的 DbEntityEntry 对象包含Reload 方法来从数据库中加载最新数据。

Example 5-23. Reloading an entity from the database

private static void ReloadLodging()
{
using (var context = new BreakAwayContext())
{
//从数据库检索数据
var hotel = (from d in context.Lodgings where d.Name == "Grand Hotel" select d).Single();
//使用原始的SQL语句修改数据库中的值
context.Database.ExecuteSqlCommand(@"UPDATE dbo.Lodgings SET Name = 'Le Grand Hotel' WHERE Name = 'Grand Hotel'"); Console.WriteLine("Name Before Reload: {0}", hotel.Name);
Console.WriteLine("State Before Reload: {0}", context.Entry(hotel).State);
//重新加载数据
context.Entry(hotel).Reload();
Console.WriteLine("Name After Reload: {0}", hotel.Name);
Console.WriteLine("State After Reload: {0}", context.Entry(hotel).State);
}
}

数据结果

Name Before Reload: Grand Hotel

State Before Reload: Unchanged

Name After Reload: Le Grand Hotel

State After Reload: Unchanged

如果在从新加载数据之前对实体进行了修改 比如

hotel.Name = "A New Name";

输出结果将变为:

Name Before Reload: A New Name

State Before Reload: Modified

Name After Reload: Le Grande Hotel

State After Reload: Unchanged

Change Tracking Information and Operations for Multiple Entities

前面操纵的都是单个的实体,接下来我们介绍DbContext.ChangeTracker.Entries 方法,该方法包含两个

重载,一个是泛型的,一个是非泛型的。泛型的方法返回指定类型的记录结合。非泛型的重载

返回一个DbEntityEntry类型的集合,包含所有被追踪的实体。

Example 5-24. Iterating over all entries from the change tracker

private static void PrintChangeTrackerEntries()
{
using (var context = new BreakAwayContext())
{
//从数据库检索Description == "Trip from the database"的Reservations类型的对象
var res = (from r in context.Reservations where r.Trip.Description == "Trip from the database" select r).Single();
//加载它的集合属性 Payments
context.Entry(res).Collection(r => r.Payments).Load();
//添加一个新的Payment
res.Payments.Add(new Payment { Amount = 245 });
//使用非泛型的方法返回所有被追踪的对象
var entries = context.ChangeTracker.Entries();
//迭代输出类型和状态
foreach (var entry in entries)
{
Console.WriteLine("Entity Type: {0}", entry.Entity.GetType());
Console.WriteLine(" - State: {0}", entry.State);
}
}
}

输出结果

Entity Type: Model.Payment

- State: Added

Entity Type: Model.Reservation

- State: Unchanged

Entity Type: Model.Payment

- State: Unchanged

Using the Change Tracker API in Application Scenarios
Resolving Concurrency Conflicts

默认情况下 Entity Framework 总是更新所有的改变,而不管是不是存在并发冲突,但是你可以

配置你的Model,当并发冲突发生时,抛出一个异常。你可以将一个特定的属性指定为 concurrency token