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类型包含属性来记录实体各个状态的值:
- CurrentValues 包含实体所有属性的当前值。
- OriginalValues 包含实体属性未被修改前的值。
- 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
- Code First 的默认约定是将不包含主键的实体理解为“复杂类型”,当需要将包含主键的实体定义为“复杂类型”时,可借助[ComplexType]特性进行标注。
- 当我们从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
- DbPropertyValues 包含一个 ToObject 方法,可以在不覆盖原有实例的情况下,将所有的值复制到一个新的实例对象。
注:ToObject 方法只复制标量属性,忽略导航属性。
Changing Values in a DbPropertyValues
- DbPropertyValues 不是只读的,可以被修改。当你修改CurrentValues 的值的时候,将改变当前实例的值。
修改将自动触发 Changes Tracking.
- Clone() 方法可以复制所有的DbPropertyValues ,新克隆出来的对象将不会被Change Tracker 跟踪。
Using the SetValues method
- 用户在修改了实体的属性值之后,想撤销所有的修改,最简单的方式就是利用 SetValues() 方法,将OriginalValues 中的数据拷贝到 CurrentValues 中。
代码如下:
entry.CurrentValues.SetValues(entry.OriginalValues);
entry.State = EntityState.Unchanged;//因为SetVaues方法不会自动修改实体的状态,所以需要手动修改。
- SetValues方法不尽可以接受DbPropertyValues做为参数,也可以接受其他类型,该方法会自动查找同名属性的值进行覆盖,找不到任何值时,引发异常。
- 可以利用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
- 提供对复杂属性的操作。
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.
- 可以修改复杂属性的值
entry.ComplexProperty(p => p.Address).CurrentValue = new Address { State = "VT" };
该操作将会把整个复杂属性标记为:Modified.
Working with Navigation Properties
Reference and Collection 方法被用户访问导航属性
- Reference 方法用于访问单个实体
- Collection 方法用于访问集合属性
这些方法提供以下功能:
- 读写导航属性的当前值
- 从数据库加载关联数据
- 获取导航属性代表的查询(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