Entity Framework inAction摘要
第三章对象模型查询基础
手动创建DbSet类型的实例:
// 为指定的类型返回System.Data.Entity.DbSet,这将允许对上下文中的给定实体执行 CRUD 操作。
publicDbSet<TEntity> Set<TEntity>() where TEntity : class;
// 为指定的类型返回System.Data.Entity.DbSet,这将允许对上下文中的给定实体执行 CRUD 操作。
publicDbSet Set(Type entityType);
建立连接字符串,下面是一个连接字符串的示例
<add name="ConnStringName"connectionString="
metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl;
provider=System.Data.SqlClient;providerconnection string='DataSource=.\sqlexpress;database=EFInactionOrders;IntegratedSecurity=True;MultipleActiveResultSets=True'" providerName="System.Data.EntityClient"/>
还可以用EntityConnectionStringBuilder类在运行时建立连接字符串:
varconnStringData = proxy.GetConnectionStringData();
varbuilder = new EntityConnectionStringBuilder();
builder.Provider= connStringData.Provider;
builder.Metadata= connStringData.Metadata;
builder.ProviderConnectionString= connStringData.ProviderConnectionString;
using(var ctx = new OrderITEntities(builder.ConnectionString))
{
...
}
ObjectContext有一个ObjectMaterialized事件,你可以用它跟踪Entity的实例化。因为在一个DbContext中,同一个Id的实体,只能有一个。如果后续查询中有相同的数据库行要序列化为实体,EF会先检查这个Id的实体是不是已经存在。如果已经存在就不会在此创建新的实体。
publiceventObjectMaterializedEventHandler ObjectMaterialized;
DbContext.Database对象包含了很多操纵数据库的方法和属性,可以实现自动部署数据库。
第四章使用Linq to Entities
使用where方法
fromo in ctx.Orders
whereo.ShippingAddress.City == "New York"
selecto;
多个查询条件
o.ShippingAddress.City== "New York" || o.ShippingAddress.City == "Seattle"
使用Contains查询(对应SQL的in关键字)
var cities = new[]{ "New York", "Seattle" };
from o inctx.Orders
where cities.Contains(o.ShippingAddress.City)
select o;
查询关联表
from order inctx.Orders
whereorder.Customer.BillingAddress.City == "New York"
select order;
查询关联子表
from order inctx.Orders
whereorder.OrderDetails.Any(d => d.Product.Brand == "MyBrand")
select order;
使用All,查询没有任何打折产品的订单。
from order inctx.Orders
whereorder.OrderDetails.All(d => d.Discount == 0)
select order;
在查询中使用算式
from order inctx.Orders
whereorder.OrderDetails.Sum(d => d.Discount * d.Quantity) > 5
select order;
查询超过包含多个产品的订单
from order in ctx.Orders
whereorder.OrderDetails
.Select(d =>d.Product.ProductId)
.Distinct()
.Count() > 1
select order;
分页查询
(from order inctx.Orders
orderbyorder.OrderDate
select order).Take(15);
(from o inctx.Orders
orderby o.OrderId
select o).Skip(10).Take(10);
获取一个Entity
(from order inctx.Orders
where order.OrderId== 1
selectorder).First();
(from order inctx.Orders
where order.OrderId== 1
select order).Single();
最好使用First,它比Single有更好的性能。该查询不能在VS中监测SQL语句。只能用SQL Profiler来监视。
还可以使用以下方法达到上述两个查询的相同的效果:
// 摘要:
// 使用主键值尝试查找上下文跟踪的实体。如果该实体未在上下文中,则将针对数据源中的数据执行和计算查询;如果未在上下文或数据源中找到该实体,则将返回 null。请注意,Find
// 还会返回已添加到上下文但尚未保存到数据库中的实体。
//
// 参数:
// keyValues:
// 要查找的实体的主键值。
//
// 返回结果:
// 返回 System.Boolean。
public TEntity Find(paramsobject[] keyValues);
动态创建查询
var date =DateTime.Today;
string city = null;
var result =ctx.Orders.AsQueryable();
if (date !=DateTime.MinValue)
result = result.Where(o => o.OrderDate < date);
if(String.IsNullOrEmpty(city))
result = result.Where(o => o.ShippingAddress.City ==city);
投影结果集,下面的LINQ只会生成查询所需列的SQL,因此提高了性能。
var result = from oin ctx.Orders
select new { o.OrderId,o.OrderDate, o.ShippingAddress };
组合新属性
from o in ctx.Orders
select new {
o.OrderId,
o.OrderDate,
ShippingAddress =String.Format("{0}-{1}-{2}-{3}",
o.ShippingAddress.Address,
o.ShippingAddress.City,
o.ShippingAddress.ZipCode
o.ShippingAddress.Country)
};
使用嵌套匿名类型:
from o in ctx.Orders
select new {
o.OrderId,
o.OrderDate,
Shipping = new
{
o.ShippingAddress.City,
o.ShippingAddress.Address
}
};
在投影中包含关系
from o in ctx.Orders
select new { o.OrderId,o.OrderDate, o.ShippingAddress, o.Customer };
投影集合关系
from o in ctx.Orders
select new { o.OrderId, o.OrderDate,o.ShippingAddress, o.OrderDetails };
或者从原始集合创建新的集合
from o in ctx.Orders
select new
{
o.OrderId,
o.OrderDate,
o.ShippingAddress,
Details = from d in o.OrderDetails
select new
{
d.OrderDetailId, d.Product.ProductId, d.Quantity
}
};
还可以对集合进行聚合操作:
from o in ctx.Orders
select new
{
o.OrderId,
o.OrderDate,
o.ShippingAddress,
Total = o.OrderDetails.Sum(d => d.Quantity *(d.UnitPrice - d.Discount))
};
投影和对象跟踪
不能创建一个Entity并且只赋值部分属性。如果在查询中创建一个Entity并且只赋值部分属性,那么运行时会抛出异常。这是因为只有部分属性的Entity无法正确检测数据变化。
分组数据
from c in ctx.Orders
group c byc.ShippingAddress.City;
从分组数据创建新的投影:
var result = from cin ctx.Orders
group c byc.ShippingAddress.City into oGroup
select new { CityName =oGroup.Key, Items = oGroup };
或者:
from o in ctx.Orders
group o by o.ShippingAddress.Cityinto g
select new
{
g.Key,
Items = g.Select(og => new { og.OrderId, og.OrderDate})
};
使用多个属性进行分组:
varresult = from o in ctx.Orders
group oby new
{
o.ShippingAddress.City,o.ShippingAddress.ZipCode
};
foreach(var key in result)
{
Console.WriteLine(key.Key.City +"-" + key.Key.ZipCode);
foreach (var item in key)
Console.WriteLine(item.OrderId);
}
查询聚合数据
from o in ctx.Orders
group o byo.ShippingAddress.City into g
where g.Count() >2
select g;
排序
升序排序
from o in ctx.Orders
orderby o.ShippingAddress.City
select o;
降序排序
from o in ctx.Orders
orderbyo.ShippingAddress.City, o.ShippingAddress.ZipCode descending
select o;
按照关联数据排序
from o in ctx.Orders
orderbyo.OrderDetails.Sum(d => d.Quantity * (d.UnitPrice - d.Discount))
select new
{
o.OrderId,
o.OrderDate,
o.ShippingAddress,
Total = o.OrderDetails.Sum(
d => d.Quantity * (d.UnitPrice - d.Discount))
};
或者
from o in ctx.Orders
orderbyo.Customer.ShippingAddress.City
select o;
对关联集合排序:
from o in ctx.Orders
select new
{
o.OrderId,
o.ShippingAddress.City,
Details = o.OrderDetails.OrderBy(d => d.Quantity)
};
连接数据
from oin ctx.Orders
join cin ctx.Companies
ono.ShippingAddress.City equals c.ShippingAddress.City
selecto;
from oin ctx.Orders
根据多个属性连接数据
join cin ctx.Companies.OfType<Customer>()
on new {o.ShippingAddress.City, o.Customer.CompanyId }
equalsnew { c.ShippingAddress.City, c.CompanyId }
select o;
实现外连接
from o in ctx.Orders
join c inctx.Companies.OfType<Customer>()
on new {o.ShippingAddress.City, o.Customer.CompanyId }
equals new {c.ShippingAddress.City, c.CompanyId }
into g
from item ing.DefaultIfEmpty()
select o;
查询具有继承关系的数据
IEnumerable<Product>products = from p in ctx.Products
where p is Shoe
select p;
或者
IEnumerable<Shoe>shoes = from p in ctx.Products.OfType<Shoe>()
select p;
使用函数
标准函数
下面的查询会出错:
from o in ctx.Orders
whereo.OrderDate.AddDays(5) < o.ActualShippingDate
select o;
需要使用下面的方法:
from o in ctx.Orders
whereEntityFunctions.DiffDays(o.OrderDate, o.ActualShippingDate) > 5
select o;
数据库函数,但是这些函数只能用于SQL数据库,所以下面的代码只能运行于SQL Server之上
from o in ctx.Orders
whereSqlFunctions.DateDiff("d", o.OrderDate, o.ActualShippingDate) > 5
select o;
执行SQL语句
var details =ctx.ExecuteStoreQuery<OrderDetail>
("Select * fromOrderDetail");
需要注意的是,映射过程不使用EMD映射文件,而直接使用列名映射。所以具有复杂类型的属性无法在这里映射。因为直接使用列名映射,所以可以使用任何类型作为泛型参数。
传递参数
传递简单值参数,下面的方法不会导致SQL注入攻击,因为EF内部还是使用了具体的IDbParameter类型。
varnames = ctx.ExecuteStoreQuery<string>
("SELECT name FROM company WHERE shippingcity = {0} and billingcity = {1}",
"NewYork", "Seattle");
传递SqlParameter
var p0 =new SqlParameter("p0", DbType.String)
{ Value= "New York" };
var p1 =new SqlParameter("p1", DbType.String)
{ Value= "Seattle" };
varnames = ctx.ExecuteStoreQuery<string>
("SELECTname FROM company
➥WHERE shippingcity = {0}
➥and billingcity = {1}", p0, p1);
使用命名参数(SQL server使用“@”,OLEDB使用“?”,Oracle使用“:”):
var names =ctx.ExecuteStoreQuery<string>
("SELECT nameFROM company WHERE shippingcity = @p0 and billingcity = @p1", "NewYork", "Seattle");
var p0 = newSqlParameter("p0", DbType.String) { Value = "New York" };
var p1 = newSqlParameter("p1", DbType.String) { Value = "Seattle" };
var names =ctx.ExecuteStoreQuery<string>
("SELECT nameFROM company WHERE shippingcity = @p0 and billingcity = @p1", p0, p1);
预先加载
包含单个引用
query.Include(e => e.Level1Reference).
包含单个集合
query.Include(e => e.Level1Collection).
包含单个引用,且此引用又引用下一级引用
query.Include(e =>e.Level1Reference.Level2Reference).
包含单个引用,且此引用又包含下一级集合
query.Include(e =>e.Level1Reference.Level2Collection).
包含单个集合,且该集合又包含下一级引用
query.Include(e => e.Level1Collection.Select(l1=> l1.Level2Reference)).
包含单个集合,且该集合又引用下一级集合
query.Include(e => e.Level1Collection.Select(l1=> l1.Level2Collection)).
更多级引用:
query.Include(e => e.Level1Collection.Select(l1=> l1.Level2Reference.Level3Reference)).
query.Include(e => e.Level1Collection.Select(l1=> l1.Level2Collection.Select(l2 => l2.Level3Reference))).
所有上面的预加载语法,都可以用字符串路径代替,如:
container.Users.Include("Roles.Tasks.Operations")
Lazy Loading不需要使用上述语法,当你在访问某个导航属性的时候,数据库访问会自动发生,为你填充该属性。但前提是你打开了LazyLoading选项,而且你的Entity对象是一个代理对象;否则什么都不会发生。
设置LazyLoading选项和启动代理选项(次两个选项默认为true),如下:
publicclassDbContextConfiguration
{
publicbool LazyLoadingEnabled { get; set; }
publicbool ProxyCreationEnabled { get; set; }
}
第五章领域模型映射
CSDL:
Schema元素用是CSDL文件的根元素:
<Schemaxmlns="http://schemas.microsoft.com/ado/2008/09/edm"
xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"Namespace="OrderITModel" Alias="Self">
...
</Schema>
EntityContainer元素定义了实体集和关系集,上下文类的代码由它来生成。
<EntityContainerName="OrderITEntities">
<EntitySetName="Orders" EntityType="OrderIT.DomainModel.Order" />
<EntitySetName="OrderDetails" EntityType="OrderITModel.OrderDetail"/>
</EntityContainer>
ComplexType元素定义负责类型,其内部为Property元素,用来定义一个属性:
元素属性 | 描述 | 是否必须 |
Name | 属性名 | 是 |
Type | 属性类型(全名称) | 是 |
Nullable | 是否可空 | 否 |
FixedLength | 是否固定长度 | 否 |
MaxLength | 最大长度 | 否 |
Scale | 小数位数(针对Decimal) | 否 |
Precision | 精度,数字位数(针对Decimal) | 否 |
store:StoreGeneratedPattern | 表示数据库列的值在插入和更新的时候如何被设置,有三种可选值: None:表示使用来自应用程序的值。 Identity:值由数据库产生,应用程序用它来更新。 Computed:该值由数据库计算产生 | 否 |
ConcurrencyMode | 并发模式,为了启用并发检查,设置该值为Fixed | 否 |
Entity元素定义实体类
<ComplexTypeName="AddressInfo">
<PropertyType="String" Name="Address" Nullable="false"MaxLength="50" />
<PropertyType="String" Name="City" Nullable="false" MaxLength="50"/>
<PropertyType="String" Name="ZipCode" Nullable="false"MaxLength="15" />
<PropertyType="String" Name="Country" Nullable="false"MaxLength="30" />
</ComplexType>
<EntityTypeName="Order">
<Key>
<PropertyRefName="OrderId" />
</Key>
<PropertyType="Int32" Name="OrderId" Nullable="false"store:StoreGeneratedPattern="Identity" />
<PropertyName="ShippingAddress" Type="OrderITModel.AddressInfo"Nullable="false" />
<PropertyType="DateTime" Name="EstimatedShippingDate"Nullable="false" Precision="29" />
<PropertyType="DateTime" Name="ActualShippingDate"Nullable="false" Precision="29" />
</EntityType>
<EntityTypeName="OrderDetail">
<Key>
<PropertyRefName=" OrderDetail Id" />
</Key>
<PropertyType="Int32" Name="OrderDetailId"Nullable="false" store:StoreGeneratedPattern="Identity"/>
<PropertyType="Int16" Name="Quantity" Nullable="false"/>
<PropertyType="Decimal" Name="UnitPrice" Nullable="false"Precision="29" Scale="29" />
<PropertyType="Decimal" Name="Discount" Nullable="false"Precision="29" Scale="29" />
</EntityType>
EntityType有两个属性:Abstract用来制定该类是不是一个抽象类,BaseType用来指定一个基类型。
SSDL:
Schema元素定义类根节点:
<SchemaNamespace="OrderITModel.Store" Alias="Self"
Provider="System.Data.SqlClient"ProviderManifestToken="2008"
xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
...
</Schema>
ProviderManifestToken指定了数据库的版本,对于SQLServer,它可以是2000,2005,2008等。
EntityContainer元素声明了所有数据库对象。
<EntityContainerName="OrderITModelStoreContainer">
<EntitySetName="Order" EntityType="OrderITModel.Store.Order"store:Type="Tables" Schema="dbo" />
<EntitySetName="OrderDetail"EntityType="OrderITModel.Store.OrderDetail"store:Type="Tables" Schema="dbo" />
</EntityContainer>
Table属性定义了表的名称,如果没有指定该属性,那么默认使用Name属性作为表名称。
store:Type表示该对象是表还是视图。
EntityType定义了数据库对象:
<EntityTypeName="Order">
<Key>
<PropertyRefName="OrderId" />
</Key>
<PropertyName="OrderId" Type="int"StoreGeneratedPattern="Identity" Nullable="false" />
<Property Name="ShippingAddress"Type="nvarchar" Nullable="false" MaxLength="50"/>
<PropertyName="ShippingCity" Type="nvarchar"Nullable="false" MaxLength="50" />
<PropertyName="ShippingZipCode" Type="nvarchar"Nullable="false" MaxLength="15" />
<Property Name="ShippingCountry"Type="nvarchar" Nullable="false" MaxLength="30"/>
<PropertyName="EstimatedShippingDate" Type="datetime"Nullable="false"/>
<PropertyName="ActualShippingDate" Type="datetime"Nullable="false" />
<PropertyName="CustomerId" Type="int" Nullable="false"/>
</EntityType>
Name属性必须与EntitySet的Name属性相同。
Property元素拥有和CSDL中的Property一样的属性,但是还有以下三个额外的属性:
Collation—指定了列的Collation
DefaultValue—设置列的默认值
Unicode—指定值是不是Unicode
MSL:
Mapping元素定义了根节点
<MappingSpace="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
<EntityContainerMappingCdmEntityContainer="OrderITEntities"StorageEntityContainer="OrderITModelStoreContainer">
...
</EntityContainerMapping>
</Mapping>
EntitySetMapping,EntityTypeMapping和MappingFragment定义类映射的详细信息:
<EntitySetMappingName="Orders">
<EntityTypeMappingTypeName="IsTypeOf(OrderITModel.Order)">
<MappingFragmentStoreEntitySet="Order">
<ScalarPropertyName="Id" ColumnName="Id" />
<ComplexPropertyName="ShippingAddress"TypeName="OrderITModel.AddressInfo">
<ScalarPropertyName="Address" ColumnName="ShippingAddress" />
<ScalarPropertyName="City" ColumnName="ShippingCity" />
<ScalarPropertyName="ZipCode" ColumnName="ShippingZipCode" />
<ScalarPropertyName="Country" ColumnName="ShippingCountry" />
</ComplexProperty>
<ScalarPropertyName="EstimatedShippingDate"ColumnName="EstimatedShippingDate" />
<ScalarPropertyName="ActualShippingDate" ColumnName="ActualShippingDate"/>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
添加一对多关系
NavigationProperty元素定义了导航属性。
<EntityTypeName="OrderDetail">
...
<NavigationPropertyName="Order" Relationship="OrderIT.OrderOrderDetail"FromRole="OrderDetail" ToRole="Order" />
<ScalarPropertyName="OrderId" ColumnName="OrderId" />
</EntityType>
Relationship属性需要指定Association的全名称,而不是AssociationSet的全名称。
AssociationSet元素声明了一个关系,该元素包含在EntityContainer中。
<EntityContainer...>
<AssociationSetName="OrderOrderDetail"Association="OrderITModel.OrderOrderDetail">
<EndRole="Order" EntitySet="Orders">
<OnDeleteAction="Cascade" />
</End>
<End Role="OrderDetail"EntitySet="OrderDetails" />
</AssociationSet>
</EntityContainer>
<AssociationName="OrderOrderDetail">
<EndRole="Order" Type="OrderITModel.Order"Multiplicity="1" />
<EndRole="OrderDetail" Type="OrderITModel.OrderDetail"Multiplicity="*" />
</Association>
Association的Name属性和AssociationSet的Association属性需要保持一致(引用一个关系或者实体的时候需要使用全名称)。AssociationSet的Name属性到目前为止,还没看出有什么用。
End元素中可以指定级联删除操作。这个级联删除跟数据库的级联删除没有任何关系。在此处,如果指定了级联删除,那么当你删除一个Order的时候,所有该Order的OrderDetail都会被设置删除状态。
具有外键属性的关系在CSDL中映射,只有导航属性的关系在MSL中映射,我们只描述前者:
<AssociationName="OrderOrderDetail">
...
<ReferentialConstraint>
<PrincipalRole="Order">
<PropertyRefName="OrderId" />
</Principal>
<DependentRole="OrderDetail">
<PropertyRefName="OrderId" />
</Dependent>
</ReferentialConstraint>
</Association>
有时候,为了性能考虑,也许会在数据库里删除外键关联;所以SSDL里面不会有关于关系的描述。但是CSDL里的关系描述仍然有效。不过下面我们会讲数据库里定义了外键的情况,所以SSDL需要添加此关系定义。
<EntityContainer...>
<AssociationSetName="FK_OrderDetail_Order" Association="OrderITModel.Store.FK_OrderDetail_Order">
<End Role="Order"EntitySet="Order" />
<EndRole="OrderDetail" EntitySet="OrderDetail" />
</AssociationSet>
</EntityContainer>
<AssociationName="FK_OrderDetail_Order">
<EndRole="Order" Type="OrderITModel.Store.Order"Multiplicity="1">
<OnDeleteAction="Cascade" />
</End>
<EndRole="OrderDetail" Type="OrderITModel.Store.OrderDetail"Multiplicity="*"/>
<ReferentialConstraint>
<PrincipalRole="Order">
<PropertyRefName="OrderId" />
</Principal>
<DependentRole="OrderDetail">
<PropertyRefName="OrderId" />
</Dependent>
</ReferentialConstraint>
</Association>
最后一步是修改MSL,完成对外键属性的映射:
<EntitySetMappingName="OrderDetails">
<EntityTypeMappingTypeName="IsTypeOf(OrderITModel.OrderDetail)">
<MappingFragmentStoreEntitySet="OrderDetail">
...
<ScalarPropertyName="OrderId" ColumnName="OrderId" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
多对多的关系与1对多关系基本相同,不同的是在MSL里面需要用AssociationSetMapping元素来描述映射:
<AssociationSetMappingName="ProductsSuppliers"TypeName="OrderItModel.ProductsSuppliers" StoreEntitySet="ProductSupplier">
<EndPropertyName="Suppliers">
<ScalarProperty Name="CompanyId"ColumnName="SupplierId" />
</EndProperty>
<EndPropertyName="Products">
<ScalarProperty Name="ProductId"ColumnName="ProductId" />
</EndProperty>
</AssociationSetMapping>
使用模型描述TPH
注意Abstract属性和BaseType属性的使用。
<EntityContainer...>
<EntitySetName="Companies" EntityType="OrderIT.Domain.Company" />
</EntityContainer>
<EntityTypeName="Company" Abstract="true">
<Key>
<PropertyRefName="CompanyId" />
</Key>
<PropertyType="Int32" Name="CompanyId" Nullable="false"store:StoreGeneratedPattern="Identity" />
<PropertyType="String" Name="Name" Nullable="false"MaxLength="50" />
</EntityType>
<EntityTypeName="Customer" BaseType="OrderITModel.Company" >
<Property Name="BillingAddress"Type="OrderITModel.AddressInfo" Nullable="false" />
<PropertyName="ShippingAddress" Type="OrderITModel.AddressInfo"Nullable="false" />
<PropertyType="String" Name="WSUsername" Nullable="true"MaxLength="20" />
<PropertyType="String" Name="WSPassword" Nullable="true"/>
<PropertyType="String" Name="WSEnabled" Nullable="false"/>
</EntityType>
<EntityTypeName="Supplier" BaseType="OrderITModel.Company" >
<PropertyType="String" Name="IBAN" Nullable="false"FixedLength="true" MaxLength="26" />
<PropertyType="Int16" Name="PaymentDays" Nullable="false"/>
</EntityType>
在MSL中映射TPH
<EntitySetMappingName="Companies">
<EntityTypeMappingTypeName="IsTypeOf(OrderITModel.Company)">
<MappingFragmentStoreEntitySet="Company">
<ScalarPropertyName="CompanyId" ColumnName="CompanyId" />
<ScalarPropertyName="Name" ColumnName="Name" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMappingTypeName="IsTypeOf(OrderITModel.Customer)">
<MappingFragmentStoreEntitySet="Company">
<ConditionColumnName="Type" Value="C" />
<ScalarPropertyName="CompanyId" ColumnName="CompanyId" />
<ScalarPropertyName="WSUsername" ColumnName="WSUsername" />
<ScalarPropertyName="WSPassword" ColumnName="WSPassword" />
...
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMappingTypeName="IsTypeOf(OrderITModel.Supplier)">
<MappingFragmentStoreEntitySet="Company">
<ConditionColumnName="Type" Value="S" />
<ScalarPropertyName="CompanyId" ColumnName="CompanyId" />
...
</MappingFragment>
</EntityTypeMapping>
<EntitySetMappingName="Companies">
EntitySetMapping的Name属性需要对应到CSDL中的EntitySet的Name属性。可以看到MSL中在一个EntitySetMapping中使用多个EntityTypeMapping来达到TPH的映射关系。
在MSL中映射TPT
<EntitySetMappingName="Products">
<EntityTypeMappingTypeName="IsTypeOf(OrderITModel.Product)">
<MappingFragmentStoreEntitySet="Product">
...
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMappingTypeName="IsTypeOf(OrderITModel.Shirt)">
<MappingFragmentStoreEntitySet="Shirt">
...
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(OrderITModel.Shoes)">
<MappingFragmentStoreEntitySet="Shoe">
...
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
在每个MappingFragment中只映射Entity自己对应的属性值。其中当然会包括主键映射。
第六章理解对象生命周期
下面的枚举表示了Entity的所有状态:
[Flags]
publicenumEntityState
{
Detached = 1,
Unchanged = 2,
Added = 4,
Deleted = 8,
Modified = 16,
}
l 添加一个Entity到实体集中,对象的状态为Added
通过DbContext.Set方法获取一个DbSet或者DbSet<T>的的实例。
publicDbSet<TEntity>Set<TEntity>() where TEntity : class;
publicDbSet Set(TypeentityType);
然后调用DbSet.Add方法来添加新的实体。
public TEntityAdd(TEntity entity);
l 将一个Entity附加到DbContext中,对象的状态为Unchanged
public TEntityAttach(TEntity entity);
当附加一个Entity的时候,Entity的主键必须被设置,否则会抛出InvalidOperationException,如果该对该Entity的更新没有影响任何数据库行,也会抛出异常。
如果要保存一个附加的Entity,需要通知DbContext改Entity已经被更改,否则DbContext不会有任何对该Entity的操作。通知DbContext Entity已更改有三种方法。
1. 第一种方法
a) 调用DbSet.Attach方法把Entity对象附加到DbContext中。
b) 获取当前Entity对应的DbEntityEntry对象,使用如下方法:
publicDbEntityEntry Entry(object entity);
publicDbEntityEntry<TEntity>Entry<TEntity>(TEntity entity) where TEntity : class;
c) 然后手动设置DbEntityEntry.State
DbEntityEntry.State = EntityState.Modified;
事实上,不Attach而直接修改DbEntityEntry对象的状态也是可以的。EF会在内部检测到该对象没有对应的DbEntityEntry而自动创建一个并把对象Attach到DbContext里。
2. 第二种方法
a) 将当前Entity附加到DbContext中。
b) 从数据库中加载当前Entity的原始值。
publicDbPropertyValuesGetDatabaseValues();
c) 将原始值设置到当前Entity对应的DbEntityEntry里。
DbEntityEntry.OriginalValues.SetValues(…)
3. 第三种方法
a) 假设当前Entity对应的原始Entity已经在DbContext中,那么可以直接对原始Entity对应的DbEntityEntry对象应用当前更改。
DbEntityEntry.CurrentValues.SetValues(…)
所有以上方法都只检测标量属性,忽略导航属性。
l 删除一个Entity,该Entity状态为Deleted
删除一个Entity前,该Entity必须存在于DbContext中,否则将会引发异常。在保持更改之前,该Entity的状态为Deleted,之后,该Entity将从DbContext中移除。调用以下方法删除一个Entity:
public TEntityRemove(TEntity entity);
l 调用Detach方法,将一个对象从DbContext中分离
EF5.0的DbSet类中没有对应于EF4.0里的Detach方法,原因不明。
EF5.0中不能对多对多关系应用级联删除,除非把所有相关联的对象预先记载道导航属性里,基于性能考虑,这是不可接受的。所以级联删除只能在数据库里设置。这样EF5.0在删除一个Entity的时候,相关联的Entity或者关系都会被数据库删除,但是这样做的后果是,EF5.0不知道数据库已经删除了相关联的Entity或者关系,会导致DbContext中缓存的对象和数据库里的数据不一致。
第七章把对象持久化到数据库中
下面的方法将数据更改保存到数据库中:
publicvirtualint SaveChanges();
调用该方法后,添加和修改的Entity状态变为Unchanged,删除状态的Entity将会被从DbContext中移除。整个保存过程在一个事物中执行用以保持数据完整性。默认情况下:DbContext.Configuration.AutoDetectChangesEnabled的值为true,表明DbContext将会自动检测Entity变化。这个选项最好被关闭,因为它会导致DbContext遍历所有缓存的Entity,比较他们的原始值和当前值,因此这是一个非常耗费性能的操作。关闭此选项后,需要用代码显式告诉DbContext Entity的变化(只针对Modified的情况,因为新Entity和删除的Entity都需要用EF框架里的方法来实现)。
l 添加Entity
public TEntity Add(TEntityentity);
使用上面的方法将一个新的Entity添加到EntitySet中,然后调用SaveChanges方法。
l 修改Entity
上一章中已经讲了如何修改Entity的方法。这里需要注意的一点是,如果是显式通知DbContext关于Entity的变化,那么要注意有可能数据库数据有可能会丢失。例如:你修改了User对象的Name属性,然后去更新此User,那么User的另一个属性Description同样会被更新到数据库中,因为Description的值为null,所以数据库中的值会被更新为null。
对于上述情况,要么打开自动检测变化,要么把Entity的所有属性都设置为有效值。另外复杂类型只能整体更新,不可能只把负责类型的一个属性更新到数据库。
为了方便对外键的更新,最好在Entity中同时包含外键字段,而不是只包含导航属性。只包含导航属性令更新外键变得复杂。
l 删除Entity
使用如下方法删除一个Entity并调用SaveChanges方法。被删除的Entity必须存在于DbContext中。
public TEntityRemove(TEntity entity);
如果要在删除主表条目的时候同时删除对应子表记录,最好使用数据库的级联删除功能,或者在代码中使用Entity SQL, 否则EF会对每个子表记录产生一条删除语句,影响性能。
第八章处理并发和事务
如果要支持并发检查,首先要为需要被检查的表添加一个Version列,该列的类型为Timestamp, 在EDM设计窗口里设置对应属性的并发检查模式为Fixed。
EF始终使用原始值里的Version值来做检查,所以一定要确保Version的原始值是你获取的时候值,在某些时候,可以手动修改这个值,以达到并发检查的效果。
在主/子表关系的并发检查里,如果只对主表应用了并发检查,而且修改了子表而没有对主表修改,那么并发检查不会发生。如果强制修改了主Entity的状态(Modified),并发坚持会发生,但是这么做会影响性能。
在继承关系的数据库结构里(TPT),只能在公共表上添加version列。否则会引发异常。
如果并发检查失败,OptimisticConcurrencyException会被抛出。
管理事务
使用TransactionScope类实现多次SaveChanges调用之间的事务处理。