Entity Framework 代码模板
生活随笔
收集整理的這篇文章主要介紹了
Entity Framework 代码模板
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
各種使用方式
EntityClient+EntitySQLstring city = "London"; using (EntityConnection cn = new EntityConnection("Name=Entities")) {cn.Open();EntityCommand cmd = cn.CreateCommand();cmd.CommandText = @"SELECT VALUE c FROM Entities.Customers AS c WHERE c.Address.City = @city";cmd.Parameters.AddWithValue("city", city);DbDataReader rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess);while (rdr.Read())Console.WriteLine(rdr["CompanyName"].ToString());rdr.Close(); }
ObjectService+EntitySQL
string city = "London"; using (Entities entities = new Entities()) {ObjectQuery<Customers> query = entities.CreateQuery<Customers>("SELECT VALUE c FROM Customers AS c WHERE c.Address.City = @city",new ObjectParameter("city", city));foreach (Customers c in query)Console.WriteLine(c.CompanyName); }
ObjectContext+LINQ( to Entity)
方式一:
string city = "London"; using (Entities entities = new Entities()) {var query = from c in entities.Customerswhere c.Address.City == cityselect c;foreach (Customers c in query)Console.WriteLine(c.CompanyName); }
方式二:
string city = "London"; using (Entities entities = new Entities()) {var query = entities.Customers.Where(r => r.Address.City == city);foreach (Customers c in query)Console.WriteLine(c.CompanyName); }
這兩段示例代碼中的entities.Customer的寫法隱式調(diào)用了2中示例的ObjectQuery<Customers>來(lái)進(jìn)行查
詢(關(guān)于此可以參見(jiàn)EDM的設(shè)計(jì)器文件-xxx.designer.cs)。在方式二中的Where方法傳入的是一個(gè)
Lambda表達(dá)式,你也可以傳入一條EntitySQL語(yǔ)句做參數(shù)來(lái)將LINQ與EntitySQL結(jié)合使用。如下代碼演示
其使用:
string city = "London"; using (Entities entities = new Entities()) {var query = entities.Customers.Where("r.Address.City = '"+city+"'");foreach (Customers c in query)Console.WriteLine(c.CompanyName); }
下面代碼演示使用EntitySQL的like完成模糊查詢:
context.Customer.Where("it.CustomerID LIKE @CustomerID", new?
System.Data.Objects.ObjectParameter("CustomerID","%V%"));
這個(gè)并不是只能使用EntitySQL來(lái)實(shí)現(xiàn),LINQ to Entity也可以很容易完成。如下代碼: ?
context.Customer.Where(r => r.CustomerID.Contains("V"));?
同理,"V%"、"%V"可以分別使用StartsWith()與EndsWith()函數(shù)實(shí)現(xiàn)。
?
? ? 使用LINQ to Entity需要注意的一個(gè)方面是,在完成查詢得到需要的結(jié)果后使用ToList或ToArray方
法將結(jié)果轉(zhuǎn)變?yōu)閮?nèi)存中的對(duì)象,然后使用LINQ to Objects來(lái)處理,否則處在Entity Framework的聯(lián)機(jī)模
式下對(duì)性能有很大的影響。
========
EdmGen工具
在.NET Framework 3.5的文件夾下有一個(gè)名為EdmGen的工具,Visual Studio的實(shí)體設(shè)計(jì)器就是調(diào)用這個(gè)
工具來(lái)完成EDM的生成等操作。通過(guò)直接使用這個(gè)工具的命令行選項(xiàng)我們可以進(jìn)行更多的控制。
這個(gè)命令的參數(shù)及作用如下:
EdmGen 選項(xiàng)
/mode:EntityClassGeneration 從 csdl 文件生成對(duì)象
/mode:FromSsdlGeneration 從 ssdl 文件生成 msl、csdl 和對(duì)象
/mode:ValidateArtifacts 驗(yàn)證 ssdl、msl 和 csdl 文件
/mode:ViewGeneration 從 ssdl、msl 和 csdl 文件生成映射視圖
/mode:FullGeneration 從數(shù)據(jù)庫(kù)生成 ssdl、msl、csdl 和對(duì)象
/project:<字符串> 用于所有項(xiàng)目文件的基名稱 (短格式: /p)
/provider:<字符串> 用于 ssdl 生成的 Ado.Net 數(shù)據(jù)提供程序的名稱。(短格式: /prov)
/connectionstring:<連接字符串> 您要連接到的數(shù)據(jù)庫(kù)的連接字符串 (短格式: /c)
/incsdl:<文件> 從中讀取概念模型的文件
/refcsdl:<文件> 包含 /incsdl 文件所依賴的類型的 csdl 文件
/inmsl:<文件> 從中讀取映射的文件
/inssdl:<文件> 從中讀取存儲(chǔ)模型的文件
/outcsdl:<文件> 將生成的概念模型寫入到其中的文件
/outmsl:<文件> 將生成的映射寫入到其中的文件
/outssdl:<文件> 將生成的存儲(chǔ)模型寫入到其中的文件
/outobjectlayer:<文件> 將生成的對(duì)象層寫入到其中的文件
/outviews:<文件> 將預(yù)生成的視圖對(duì)象寫入到其中的文件
/language:CSharp 使用 C# 語(yǔ)言生成代碼
/language:VB 使用 VB 語(yǔ)言生成代碼
/namespace:<字符串> 用于概念模型類型的命名空間名稱
/entitycontainer:<字符串> 用于概念模型中的 EntityContainer 的名稱
/help 顯示用法信息 (短格式: /?)
/nologo 取消顯示版權(quán)消息
使用示例:
從 Northwind 示例數(shù)據(jù)庫(kù)生成完整 Entity Model。
1 EdmGen /mode:FullGeneration /project:Northwind /provider:System.Data.SqlClient?
/connectionstring:"server=.\sqlexpress;integrated security=true; database=northwind"
從 ssdl 文件開(kāi)始生成 Entity Model。
1 EdmGen /mode:FromSSDLGeneration /inssdl:Northwind.ssdl /project:Northwind
驗(yàn)證 Entity Model。
1 EdmGen /mode:ValidateArtifacts /inssdl:Northwind.ssdl /inmsl:Northwind.msl?
/incsdl:Northwind.csdl
?
========
含有Association的EDM的使用
? ? 當(dāng)前版本的Entity Framework不支持自動(dòng)延遲加載,所有當(dāng)前未使用的關(guān)系中的相關(guān)實(shí)體默認(rèn)按不加載處理,當(dāng)我們需要通過(guò)關(guān)系獲取一個(gè)實(shí)體對(duì)象時(shí),我們可以采用兩種方法:
顯示加載
實(shí)體框架針對(duì) EntityReference 類的每個(gè)實(shí)例提供一個(gè) Load 方法。此方法可用于顯式加載與另一實(shí)體
相關(guān)的一個(gè)集合。我們只需在訪問(wèn)關(guān)系中實(shí)體之前調(diào)用其Load即可,當(dāng)然提前判斷該實(shí)體是否已經(jīng)加載
是一種比較好的實(shí)踐。如下代碼所示:
?1 using (Entities entities = new Entities())
?2 {
?3 ? var query = (from o in entities.Orders
?4 ? ? ? ? ? ? ? ?where o.Customers.CustomerID == "ALFKI"
?5 ? ? ? ? ? ? ? ?select o);
?6 ? foreach (Orders order in query)
?7 ? {
?8 ? ? if (!order.CustomersReference.IsLoaded)
?9 ? ? ? order.CustomersReference.Load();
10 ? ? Console.WriteLine(order.OrderID + " --- " +?
11 ? ? ? order.Customers.CompanyName);
12 ? }
13 }
?
預(yù)先加載
先看代碼示例
?1 using (Entities entities = new Entities())
?2 {
?3 ? var query = (from o in entities.Orders.Include("Customers")
?4 ? ? ? ? ? ? ? ?where o.ShipCountry == "USA"
?5 ? ? ? ? ? ? ? ?select o);
?6?
?7 ? foreach (Orders order in query)
?8 ? ? Console.WriteLine(order.OrderID + " --- " +?
?9 ? ? ? order.Customers.CompanyName);
10 }
查詢中針對(duì) Orders 實(shí)體調(diào)用的 Include 方法接受了一個(gè)參數(shù),該參數(shù)在本示例中將要求查詢不僅要檢
索 Orders,而且還要檢索相關(guān)的 Customers。這將生成單個(gè) SQL 語(yǔ)句,它會(huì)加載滿足 LINQ 查詢條件
的所有 Order 和 Customer。?
兩種加載關(guān)系實(shí)體的方式的選擇根據(jù):如果針對(duì)關(guān)系數(shù)據(jù)你只需做一到兩次查詢,則使用顯示加載更高
效,如果要持續(xù)訪問(wèn)關(guān)系實(shí)體中數(shù)據(jù),則使用預(yù)先加載。
關(guān)系下的添加,更新與刪除與上述操作基本相同,唯一需要注意的是刪除操作不支持級(jí)聯(lián)刪除,需要手
工遍歷所有的相關(guān)項(xiàng)并將其一一刪除。注意這里刪除操作不能使用foreach來(lái)遍歷需要?jiǎng)h除的關(guān)系實(shí)體。
取而代之的有兩種方法:
while法
1 while (result.Order_Details.Count > 0)
2 {
3 ? // 刪除操作…
4 }
?
ToList法(以非聯(lián)機(jī)方式操作)
1 var items = result.Order_Details.ToList();
2 foreach (var item in items)
3 {
4 ? // 刪除操作…
5 }
?
最新補(bǔ)充
Entity Framework在開(kāi)發(fā)中的應(yīng)用 – Entity Framework與控件
.NET Framework提供了許多xxxDataSource控件,如SqlDataSource,ObjectDataSource等,這些數(shù)據(jù)源
控件大大方便了我們的數(shù)據(jù)綁定操作。不幸的是目前還沒(méi)有針對(duì)Entity Framework的數(shù)據(jù)源控件發(fā)布,
但是將數(shù)據(jù)綁定到諸如ListBox,Grrdview或DetailsView控件也是很簡(jiǎn)單的。這源于使用ObjectContext
操作返回的IQueryable<T>對(duì)象或是使用EntityClient查詢返回的ObjectQuery對(duì)象都實(shí)現(xiàn)了IEnumerable
接口。這樣很容易將這些數(shù)據(jù)綁定到數(shù)據(jù)顯示控件。更新操作可以按上文所述在相應(yīng)的時(shí)間處理函數(shù)中
寫更新EDM的程序即可。
Entity Framework的鏈接字符串
默認(rèn)情況下(Visual Studio對(duì)Entity Framework數(shù)據(jù)項(xiàng)目的默認(rèn)設(shè)置),EDM這個(gè)XML文件被作為資源在編
譯時(shí)嵌入到程序集中。這種情況下當(dāng)更改EDM后需要重新編譯這個(gè)程序集才能使更改生效。通過(guò)更改項(xiàng)目
屬性也可以讓EDM作為三個(gè)獨(dú)立的XML文件存在于項(xiàng)目中。為了讓應(yīng)用程序可以找到EDM(無(wú)論其以什么方
式存儲(chǔ))需要一個(gè)鏈接字符串來(lái)指示EDM所在的位置。實(shí)體模型設(shè)計(jì)器生成的鏈接字符串如下所示:
1 <add name="ASSEntities"
2 connectionString="
3 metadata=res://*/ass.csdl|
4 res://*/ass.ssdl|
5 res://*/ass.msl;
6 provider=System.Data.SqlClient;
7 provider connection string="Data Source=(local);Initial Catalog=ASS;Integrated?
Security=True;MultipleActiveResultSets=True""?
8 providerName="System.Data.EntityClient" />
http://msdn.microsoft.com/zh-cn/library/cc716756.aspx
關(guān)鍵的一點(diǎn)應(yīng)用程序是怎樣找到這個(gè)字符串的,對(duì)于使用EntityClient的情況,可以直接將連接字符串
賦給EntityConnection的ConnectionString屬性,另外對(duì)于使用ObjectContext,其可以自動(dòng)由配置文件
檢索這個(gè)連接字符串。
========
Code First 一
public class Race : IEntity
{
? ? public int Id { get; set; }
? ? public string Name { get; set; }
}
public class Hero : IEntity
{
? ? public int Id { get; set; }
? ? public string Name { get; set; }
? ? public bool IsSuperHero { get; set; }
? ? public virtual Race Race { get; set; }
}
注意Hero類中的Race屬性被定義成了virtual,這是為了延遲加載。但是不同于NH的是,不寫
virtual不會(huì)報(bào)錯(cuò),而是在使用時(shí)報(bào)出空引用異常。
這里插一句,在領(lǐng)域模型中必須向ORM妥協(xié)是讓我非常不爽的地方,從使用NH的時(shí)候我就非常不喜歡
virtual這一點(diǎn)。NH lead Ayende Rahien 推薦了virtuosity,可以嘗試一下。
下一步,我們要?jiǎng)?chuàng)建自己的DbContext了
按照官方blog的walk through上的寫法,大概會(huì)寫成:
public class EfDbContext : DbContext
{
? ? public DbSet<Hero> Heros { get; set; }
? ? public DbSet<Race> Races { get; set; }
}?
但是這樣不就不能完成我們“任意類映射到數(shù)據(jù)庫(kù)上”的需求了么?難道可續(xù)新加了一個(gè)類Abc,我
們就要加一行代碼public DbSet<Abc> Abcs{get;set;}么?
解決辦法是使用DbContext類的Set方法(說(shuō)實(shí)話從方法名實(shí)在看不出這個(gè)方法是取DbSet的),用法
類似如下:
public IEnumerable<TEntity> FindAll()
{
? ? return m_dbContext.Set<TEntity>();
}
接下來(lái)就是讀寫數(shù)據(jù)了
但是在這里讓我們稍作停頓,思考一下我們的架構(gòu)。應(yīng)該讓領(lǐng)域?qū)?#xff08;及更上層)使用基礎(chǔ)結(jié)構(gòu)層的
DbContext對(duì)象么?不。有如下幾點(diǎn)原因:
上層應(yīng)該對(duì)基礎(chǔ)結(jié)構(gòu)的實(shí)現(xiàn)一無(wú)所知,而DbContext是EF的對(duì)象,暴露了太多EF的細(xì)節(jié)。
DbContext是數(shù)據(jù)庫(kù)訪問(wèn)的入口,提供了很多能力。這些能力是否應(yīng)該向上層開(kāi)放是很值得商榷的問(wèn)題。
例如DbContext的生命周期由誰(shuí)管理?如果上層可以直接使用DbContext那么意味著上層有能力new它
Dispose它,也即擁有了管理它生命周期的權(quán)利。
基于上述原因,在領(lǐng)域?qū)映橄蟪鼋涌?#xff0c;讓EF層來(lái)實(shí)現(xiàn)成了我的選擇。在這個(gè)系列的第一章里,我們
只用到了查詢,所以接口也只包含了查詢功能。
public interface IRepository<TEntity>
? ? where TEntity : IEntity
{
? ? IEnumerable<TEntity> FindAll();
}
public class EntityRepository<TEntity> : IRepository<TEntity>
? ? where TEntity : class, IEntity
{
? ? private readonly DbContext m_dbContext;
? ? public EntityRepository(DbContext dbContext)
? ? {
? ? ? ? m_dbContext = dbContext;
? ? }
? ? public IEnumerable<TEntity> FindAll()
? ? {
? ? ? ? return m_dbContext.Set<TEntity>();
? ? }
}
領(lǐng)域?qū)雍突A(chǔ)設(shè)施層的雙向依賴是一個(gè)很古老的問(wèn)題。可以用依賴注入框剪來(lái)解決。
? <object id="dbContext" type="EfSample.Model.Ef.EfDbContext ,EfSample.Model.Ef"?
scope="request">
? ? ...
? </object>
? <object id="repositoryBase" abstract="true" scope="request">
? ? <constructor-arg index="0" ref="dbContext"/>
? </object>
? <object id="heroRepository"?
type="EfSample.Model.Ef.Repositories.EntityRepository<Hero>, EfSample.Model.Ef"?
parent="repositoryBase"/>
可以看到了依賴注入框架順便替我們解決了DbContext的生命周期問(wèn)題——per-request的管理。
到這里,上層就可以使用IRepository接口來(lái)讀取數(shù)據(jù)了。但是如果你真的去嘗試,會(huì)發(fā)現(xiàn)立即拋異
常,為什么呢?因?yàn)镋F的Code Firs模式有一套默認(rèn)的convention來(lái)做類型到數(shù)據(jù)庫(kù)的映射,而我上面給
出的數(shù)據(jù)庫(kù)命名實(shí)在和默認(rèn)convention差太遠(yuǎn)了。
于是讓我們來(lái)創(chuàng)建自己的Mapping
讓我們來(lái)回顧一下我們的Mapping規(guī)則:
Type->Table
有tbl前綴
每個(gè)單詞之間用下劃線分割
全部使用小寫
Property->Column
以類名為前綴
每個(gè)單詞之間用下劃線分割
全部使用小寫
方案看似是很簡(jiǎn)單的:修改EF默認(rèn)的conventions就好了嘛~~但是EF Code Firt不提供這個(gè)能力。。
。
EF Team說(shuō)這個(gè)feature有一些易用性的問(wèn)題,但是在RC前沒(méi)時(shí)間搞所以就不提供了。。。這里有篇博文
講如何打破這種限制,其實(shí)就是用反射將自定的convention強(qiáng)行寫到Conventions集合里。本文還是不采
用這種方法了。
只能在寫自己的DbContext的時(shí)候override OnModelCreating方法。具體來(lái)講有兩種做法:
一是在這個(gè)方法里寫所有mapping信息:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{ ? ? ? ? ? ?modelBuilder.Entity<Hero>() ? ? ? ? ? ? ? ?.ToTable("tbl_hero");
? ? modelBuilder.Entity<Hero>() ? ? ? ? ? ? ? ?.Property(x => x.Id).HasColumnName
("hero_id");
? ? ?foreach (var mapping in Mappings)
? ? {
? ? ? ? mapping.RegistTo(modelBuilder.Configurations);
? ? }
}
另一種是為每個(gè)類創(chuàng)建自己的EntityTypeConfiguration,把它插入到
modelBuilder.Configurations里。本例中采用后一種辦法。一來(lái)后一種方法隔離了各種Type的Mapping
;二來(lái)后者對(duì)依賴注入也有比較好的支持。
然而modelBuilder.Configurations的Add方法并不是一個(gè)很好的API,要求傳入的泛型類型無(wú)法協(xié)變
,寫不出這樣的代碼:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
? ? foreach (var mapping in Mappings)
? ? {
? ? ? ? modelBuilder.Configurations.Add(mapping);
? ? }
}
作為妥協(xié)方案,只能讓各個(gè)mapping類把自己注冊(cè)到modelBuilder.Configurations里。代碼是這種
感覺(jué):
public class EfDbContext : DbContext
{
? ? public IList<IMapping> Mappings { get; set; }
? ??
? ? protected override void OnModelCreating(DbModelBuilder modelBuilder)
? ? {
? ? ? ? foreach (var mapping in Mappings)
? ? ? ? {
? ? ? ? ? ? mapping.RegistTo(modelBuilder.Configurations);
? ? ? ? }
? ? }
}
public class HeroMapping : EntityMappingBase<Hero>, IMapping
{
? ? public HeroMapping(IMappingStrategy<string> tableNameMappingStrategy) : base
(tableNameMappingStrategy)
? ? {
? ? ? ? Property(e => e.Id).HasColumnName(ColumnNameMappingStrategy.Value.To("Id"));
? ? ? ? Property(e => e.Name).HasColumnName(ColumnNameMappingStrategy.Value.To("Name"));
? ? ? ? Property(e => e.IsSuperHero).HasColumnName(ColumnNameMappingStrategy.Value.To
("IsSuperHero"));
? ? ? ? //Property(e => e.Race).HasColumnName
(AddUnderscoresBetweenWordsThenToLowerMappingStrategy.Value.To("Race"));
? ? ? ? HasRequired(h => h.Race).WithMany().Map(
? ? ? ? ? ? config => config.MapKey(ColumnNameMappingStrategy.Value.To("RaceId")));
? ? }
? ? #region Implementation of IMapping
? ? public void RegistTo(ConfigurationRegistrar configurationRegistrar)
? ? {
? ? ? ? configurationRegistrar.Add(this);
? ? }
? ? #endregion
}
這樣我們就可以注入EfDbContext的Mappings屬性了:
<object id="dbContext" type="EfSample.Model.Ef.EfDbContext ,EfSample.Model.Ef"?
scope="request">
? <property name="Mappings">
? ? <list element-type="EfSample.Model.Ef.Mappings.IMapping ,EfSample.Model.Ef">
? ? ? <ref object="heroMapping"/>
? ? ? <ref object="raceMapping"/>
? ? </list>
? </property>
</object>
另外,我也使用了依賴注入tableNameMappingStrategy的方式讓mapping有更多的靈活性。不過(guò)
ColumnNameMappingStrategy就比較難依賴注入了,因?yàn)橐蕾囘\(yùn)行時(shí)的TypeName作為前綴。
總結(jié)
至此,我們已經(jīng)能從數(shù)據(jù)庫(kù)中把數(shù)據(jù)取出來(lái)了。回顧一下需求,我們大體實(shí)現(xiàn)了這樣幾點(diǎn):
1、查
2、一對(duì)多
3、可以映射到現(xiàn)有數(shù)據(jù)庫(kù)上
4、可以讓任意類映射到數(shù)據(jù)庫(kù)
5、pre-request的DbContext生命周期管理。
========
Code First 二
總之這一次的目標(biāo)是:
實(shí)現(xiàn)一個(gè)完整的IRepository(添加增刪改能力)
領(lǐng)域?qū)ο蟮睦^承
事物
首先來(lái)看IRepository
我的接口如下:
public interface IRepository<TEntity>
? ? where TEntity : IEntity
{
? ? IEnumerable<TEntity> FindAll();
? ? TEntity FindById(int id);
? ? void Add(TEntity entity);
? ? void Delete(TEntity entity);
? ? void Update(TEntity entity);
}
應(yīng)該算是一個(gè)最基本的倉(cāng)儲(chǔ)接口了。
其中前幾個(gè)接口都是很好實(shí)現(xiàn)的,上次提及的DbSet對(duì)象提供了相應(yīng)的接口,直接調(diào)用即可,代碼是
類似這樣的。
protected DbSet<TEntity> DbSet
{
? ? get { return m_dbContext.Set<TEntity>(); }
}
public IEnumerable<TEntity> FindAll()
{
? ? return DbSet;
}
public TEntity FindById(int id)
{
? ? return DbSet.SingleOrDefault(entity => entity.Id == id);
}
public void Add(TEntity entity)
{
? ? DbSet.Add(entity);
? ? m_dbContext.SaveChanges();
}
public void Delete(TEntity entity)
{
? ? DbSet.Remove(entity);
? ? m_dbContext.SaveChanges();
}
關(guān)鍵問(wèn)題是最后的Update方法
DbSet對(duì)象并沒(méi)有提供相應(yīng)的接口,為什么呢?因?yàn)镋F相信自己的Self Tracking能力。也就是說(shuō),
EF認(rèn)為把一個(gè)entity從context中加載出來(lái),做一些變更,然后直接SaveChanges就可以了,不需要特意
提供一個(gè)Update方法。
但是這里有一個(gè)前提,就是“entity是從context中加載出來(lái)”。如果entity是新new出來(lái)的呢?比
如在MVC里,entity很可能是ModelBinder幫我們new出來(lái)的,context對(duì)它一無(wú)所知,直接SaveChanges顯
然不會(huì)有任何效果。
那么如何讓context可以理解一個(gè)新new出來(lái)的entity呢?這要從EF處理entity狀態(tài)開(kāi)始說(shuō)起。
EF定義了如下幾種State(注意這個(gè)枚舉是Flag)
[Flags]
public enum EntityState
{
? ? Detached = 1,
? ? Unchanged = 2,
? ? Added = 4,
? ? Deleted = 8,
? ? Modified = 16,
}
其中Detached狀態(tài),就是entity還沒(méi)有attach到context(實(shí)際上是Attach到某個(gè)DbSet上)的狀態(tài)
。具體怎么做呢?直接上代碼:
public void Update(TEntity entity)
{
? ? var entry = m_dbContext.Entry(entity);
? ? if (entry.State == EntityState.Detached)
? ? {
? ? ? ? //DbSet.Attach(entity);
? ? ? ? entry.State = EntityState.Modified;
? ? }
? ? m_dbContext.SaveChanges();
}
可以看到上面的代碼給出了兩種辦法,一種是直接修改entry的State,另一種是調(diào)用DbSet對(duì)象的
Attach方法。
注意到DbContext.Entry方法取出的DbEntityEntry對(duì)象。利用這個(gè)對(duì)象可以做很多有用的事哦~~園
子里的EF專家LingzhiSun有一篇blog,大家可以去讀讀。
不過(guò)這個(gè)實(shí)現(xiàn)有一個(gè)缺陷
我們上面談到過(guò),上面這個(gè)實(shí)現(xiàn)實(shí)際上是把entity attach到了對(duì)應(yīng)的DbSet上。但是如果你的代碼
是類似如下的,就可能產(chǎn)生問(wèn)題(沒(méi)有親試,感覺(jué)上是這樣的= =)
var heros = repository.FindAll();
var hero = heros.First(h => h.Id == 1);
var heroNew = new Hero
{
? ? Id = hero.Id,
? ? Name = hero.Name,
? ? Race = hero.Race
};
repository.Update(heroNew);
應(yīng)該是會(huì)拋出來(lái)一個(gè)異常說(shuō)“An object with the same key already exists in the?
ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
”
異常說(shuō)的很明白,你的DbSet已經(jīng)加載過(guò)一次id為1的對(duì)象了,當(dāng)試圖去attach另一個(gè)id為1的對(duì)象的
時(shí)候EF就會(huì)無(wú)所適從。
那是不是說(shuō)剛才給出的那個(gè)實(shí)現(xiàn)根本就行不通呢?不是的!事實(shí)上微軟官方的文章上就是采用這種
方法的。關(guān)鍵就在于當(dāng)你嘗試去attach一個(gè)entity的時(shí)候,要保證DbSet還沒(méi)有加載過(guò)!我們看上面那篇
微軟的文章里是如何保證這一點(diǎn)的:
public class BlogController : Controller
{
? ? BlogContext db = new BlogContext();
?
? ? //...
?
? ? [HttpPost]
? ? public ActionResult Edit(int id, Blog blog)
? ? {
? ? ? ? try
? ? ? ? {
? ? ? ? ? db.Entry(blog).State = EntityState.Modified;
? ? ? ? ? db.SaveChanges();?
? ? ? ? ? return RedirectToAction("Index");
? ? ? ? }
? ? ? ? catch
? ? ? ? {
? ? ? ? ? ? return View();
? ? ? ? }
? ? }
}
很明顯,在執(zhí)行Edit這個(gè)Action之前,DbSet沒(méi)有加載過(guò),因?yàn)镸VC幫我們保證了DbContext實(shí)例是request結(jié)
束就被銷毀的。
也就是說(shuō),結(jié)論是使用這種Update實(shí)現(xiàn)方式對(duì)context的生命周期是有要求的.當(dāng)然我的例子中
context的生命周期也是per-request的所以沒(méi)關(guān)系。
那么如果我們想使用其他的context生命周期管理方式呢?比如希望整個(gè)application只有一個(gè)
context實(shí)例?
讓我們來(lái)給出另一種實(shí)現(xiàn)
回過(guò)頭來(lái)想一想在實(shí)現(xiàn)Update這個(gè)方法的時(shí)候我們最初遇到的問(wèn)題:entity不是從context中加載的
而是直接new出來(lái)的。
那么我們手動(dòng)的來(lái)加載一次就好了么,代碼類似于這樣:
public void Update(Hero entity)
{
? ? var entry = m_dbContext.Entry(entity);
? ? if (entry.State == EntityState.Detached)
? ? {
? ? ? ? Hero entityToUpdate = FindById(entity.Id);
? ? ? ? entityToUpdate.Id = entity.Id;
? ? ? ? entityToUpdate.Name = entity.Name;
? ? ? ? entityToUpdate.Race = entity.Race;
? ? }
? ? m_dbContext.SaveChanges();
}
不過(guò)由于失去了泛型的優(yōu)勢(shì),給每個(gè)domain model都要實(shí)現(xiàn)一個(gè)Update方法比較煩,可以用一些框
架來(lái)解決這個(gè)問(wèn)題,例如EmitMapper(園子里也討論過(guò)這個(gè)東西)
public void Update(TEntity entity)
{
? ? var entry = m_dbContext.Entry(entity);
? ? if (entry.State == EntityState.Detached)
? ? {
? ? ? ? var entityToUpdate = FindById(entity.Id);
? ? ? ? EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<TEntity, TEntity>().Map
(entity, entityToUpdate);
? ? }
? ? m_dbContext.SaveChanges();
}
當(dāng)然這個(gè)實(shí)現(xiàn)也有不好的地方例如說(shuō)當(dāng)domain里有一些跟ORM沒(méi)關(guān)系的property時(shí)也會(huì)被EmitMapper改寫
掉。
下一個(gè)議題是領(lǐng)域?qū)ο蟮睦^承
讓領(lǐng)域?qū)ο髮?shí)現(xiàn)繼承的好處是不言而喻的,可以使用到多態(tài)等OO帶來(lái)的好處。相對(duì)的就對(duì)ORM提出了
更高的要求。
我們知道映射對(duì)象樹(shù)到數(shù)據(jù)庫(kù)有三種經(jīng)典的實(shí)現(xiàn)方式:Table Per Type、Table Per Hierarchy和
Table Per Concrete class,這次我們來(lái)實(shí)踐最簡(jiǎn)單的一種:Table Per Hierarchy。
回想我們上一次的類:
public class Hero : IEntity
{
? ? public int Id { get; set; }
? ? public string Name { get; set; }
? ? public bool IsSuperHero { get; set; }
? ? public virtual Race Race { get; set; }
}
把它拆成兩個(gè)有繼承關(guān)系的類:
public class Hero : IEntity
{
? ? public int Id { get; set; }
? ? public string Name { get; set; }
? ? //public bool IsSuperHero { get; set; }
? ? public virtual Race Race { get; set; }
}
public class SuperHero : Hero
{
}
在EF Code First中這種單表繼承的映射關(guān)系是這樣來(lái)寫的:
Map<Hero>(hero => hero.Requires(ColumnNameMappingStrategy.Value.To
("IsSuperHero")).HasValue(false)).ToTable(tableNameMappingStrategy.To("Hero"));
Map<SuperHero>(hero => hero.Requires(ColumnNameMappingStrategy.Value.To
("IsSuperHero")).HasValue(true)).ToTable(tableNameMappingStrategy.To("Hero"));
另外兩種方式的實(shí)現(xiàn)也不復(fù)雜,可以參考這里。這個(gè)實(shí)例還是CTP5的API,跟4.1最終版有些區(qū)別不
過(guò)應(yīng)該影響不大。
今天最后的議題是事物
可以用TransactionScope來(lái)管理,雖然看起來(lái)有些浪費(fèi),畢竟例子中不涉及Transaction傳播,連
DbContext都只有一個(gè)實(shí)例。代碼如下:
[HttpPost]
public ActionResult Edit(TEntity entity)
{
? ? try
? ? {
? ? ? ? using (var scope = new TransactionScope())
? ? ? ? {
? ? ? ? ? ? ModelRepository.Update(entity);
? ? ? ? ? ? scope.Complete();
? ? ? ? }
? ? ? ? return RedirectToAction("Index");
? ? }
? ? catch
? ? {
? ? ? ? return View();
? ? }
}
========
鏈接
http://blog.csdn.net/csh624366188/article/details/7065036/http://kb.cnblogs.com/zt/ef/
http://blog.csdn.net/bcbobo21cn/article/details/43966541
總結(jié)
以上是生活随笔為你收集整理的Entity Framework 代码模板的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: oracle存储过程模板
- 下一篇: BIOS中断相关资料和应用