在前面幾篇關(guān)于Entity Framework 實體框架的介紹里面凿歼,已經(jīng)逐步對整個框架進(jìn)行了一步步的演化,以期達(dá)到統(tǒng)一冗恨、高效答憔、可重用性等目的,本文繼續(xù)探討基于泛型的倉儲模式實體框架方面的改進(jìn)優(yōu)化掀抹,使我們大家能夠很好理解其中的奧秘虐拓,并能夠達(dá)到通用的項目應(yīng)用目的。本篇主要介紹實體數(shù)據(jù)模型 (EDM)的處理方面的內(nèi)容傲武。
1蓉驹、實體數(shù)據(jù)模型 (EDM)的回顧
前面第一篇隨筆,我在介紹EDMX文件的時候揪利,已經(jīng)介紹過實體數(shù)據(jù)模型 (EDM)态兴,由三個概念組成:概念模型由概念架構(gòu)定義語言文件 (.csdl)來定義;映射由映射規(guī)范語言文件 (.msl)疟位;存儲模型(又稱邏輯模型)由存儲架構(gòu)定義語言文件 (.ssdl)來定義瞻润。
這三者合在一起就是EDM模式。EDM模式在項目中的表現(xiàn)形式就是擴(kuò)展名為.edmx的文件甜刻。這個文件本質(zhì)是一個xml文件绍撞,可以手工編輯此文件來自定義CSDL、MSL與SSDL這三部分得院。
CSDL定義了EDM或者說是整個程序的靈魂部分 – 概念模型傻铣。這個文件完全以程序語言的角度來定義模型的概念。即其中定義的實體尿招、主鍵矾柜、屬性阱驾、關(guān)聯(lián)等都是對應(yīng)于.NET Framework中的類型就谜。
SSDL這個文件中描述了表、列里覆、關(guān)系丧荐、主鍵及索引等數(shù)據(jù)庫中存在的概念。
MSL這個文件即上面所述的CSDL與SSDL的對應(yīng)喧枷,主要包括CSDL中屬性與SSDL中列的對應(yīng)虹统。
2弓坞、EDMX文件的處理
我們在編譯程序的時候,發(fā)現(xiàn)EDMX文件并沒有生成在Debug目錄里面车荔,而EF框架本身是需要這些對象的映射關(guān)系的渡冻,那肯定就是這些XML文件已經(jīng)通過嵌入文件的方式加入到程序集里面了,我們從數(shù)據(jù)庫連接的字符串里面也可以看到端倪忧便。
<add name="sqlserver" connectionString="metadata=res:///Model.sqlserver.csdl|res:///Model.sqlserver.ssdl|res://*/Model.sqlserver.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=WinFramework;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
我們看到族吻,這里面提到了csdl、ssdl珠增、msl的文件超歌,而且這些是在資源文件的路徑,我們通過反編譯程序集可以看到蒂教,其實是確實存在這三個文件的巍举。
但是我們并沒有把edmx文件進(jìn)行拆分啊,而且也沒有把它進(jìn)行文件的嵌入處理的澳狻懊悯?有點奇怪!
我們知道梦皮,一般這種操作可能是有針對性的自定義工具進(jìn)行處理的定枷,我們看看這個文件的屬性進(jìn)行了解下。
這個edmx文件的屬性届氢,已經(jīng)包含了【自定義工具】欠窒,這個工具應(yīng)該是生成對應(yīng)的數(shù)據(jù)訪問上下文類代碼和實體類代碼的了,那么生成操作不是編譯或者內(nèi)容退子,而是EntityDeploy是什么處理呢岖妄,我們通過搜索了解下。
EntityDeploy操作:一個用于部署 Entity Framework 項目的生成任務(wù)寂祥,這些項目是依據(jù) .edmx 文件生成的荐虐。 可將這些項目作為資源嵌入,或?qū)⑦@些項目寫入文件丸凭。
根據(jù)這句話福扬,我們就不難解釋,為什么編譯后的程序集自動嵌入了三個csdl惜犀、ssdl铛碑、msl的xml文件了。
如果我們想自己構(gòu)建相關(guān)的數(shù)據(jù)訪問上下文類虽界,以及實體類的代碼生成(呵呵汽烦,我想用自己的代碼生成工具統(tǒng)一生成,可以方便調(diào)整注釋莉御、命名撇吞、位置等內(nèi)容)俗冻,雖然可以調(diào)整T4、T5模板來做這些操作牍颈,不過我覺得那個模板語言還是太啰嗦和復(fù)雜了迄薄。
這樣我把這個自定義工具【EntityModelCodeGenerator】置為空,也就是我想用自己的類定義格式煮岁,自己的生成方式去處理噪奄。當(dāng)置為空的時候,我們可以看到它自動生成的類代碼刪除了人乓,呵呵勤篮,這樣就挺好。
3色罚、EF框架的多數(shù)據(jù)庫支持
在前面的例子里面碰缔,我們都是以默認(rèn)SqlServer數(shù)據(jù)庫為例進(jìn)行介紹EDMX文件,這個文件是映射的XML文件戳护,因此對于不同的數(shù)據(jù)庫金抡,他們之間的映射內(nèi)容是有所不同的,我們可以看看SqlServer的edmx文件內(nèi)容(以TB_City表為例)腌且。
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="3.0" xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="WinFrameworkModel.Store" Provider="System.Data.SqlClient" ProviderManifestToken="2005" Alias="Self"
xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation"
xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
<EntityType Name="TB_City">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="bigint" StoreGeneratedPattern="Identity" Nullable="false" />
<Property Name="CityName" Type="nvarchar" MaxLength="50" />
<Property Name="ZipCode" Type="nvarchar" MaxLength="50" />
<Property Name="ProvinceID" Type="bigint" />
</EntityType>
<EntityContainer Name="WinFrameworkModelStoreContainer">
<EntitySet Name="TB_City" EntityType="Self.TB_City" Schema="dbo" store:Type="Tables" />
</EntityContainer>
</Schema></edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema Namespace="EntityModel"
Alias="Self" annotation:UseStrongSpatialTypes="false"
xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation"
xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation"
xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityType Name="City">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
<Property Name="CityName" Type="String" MaxLength="50" FixedLength="false" Unicode="true" />
<Property Name="ZipCode" Type="String" MaxLength="50" FixedLength="false" Unicode="true" />
<Property Name="ProvinceID" Type="Int32" />
</EntityType>
<EntityContainer Name="SqlEntity" annotation:LazyLoadingEnabled="true">
<EntitySet Name="City" EntityType="EntityModel.City" />
</EntityContainer>
</Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
<EntityContainerMapping StorageEntityContainer="WinFrameworkModelStoreContainer" CdmEntityContainer="SqlEntity">
<EntitySetMapping Name="City">
<EntityTypeMapping TypeName="EntityModel.City">
<MappingFragment StoreEntitySet="TB_City">
<ScalarProperty Name="ID" ColumnName="ID" />
<ScalarProperty Name="CityName" ColumnName="CityName" />
<ScalarProperty Name="ZipCode" ColumnName="ZipCode" />
<ScalarProperty Name="ProvinceID" ColumnName="ProvinceID" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
</edmx:Runtime>
.........其他內(nèi)容
</Designer>
</edmx:Edmx>
而對MySql而言梗肝,它的映射關(guān)系也和這個類似,主要是SSDL部分的不同铺董,因為具體是和數(shù)據(jù)庫相關(guān)的內(nèi)容巫击。下面是Mysql的SSDL部分的內(nèi)容,從下面XML內(nèi)容可以看到精续,里面的數(shù)據(jù)庫字段類型有所不同坝锰。
<edmx:Edmx Version="3.0" xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="testModel.Store" Provider="MySql.Data.MySqlClient" ProviderManifestToken="5.5" Alias="Self" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation" xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
<EntityType Name="tb_city">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="int" Nullable="false" />
<Property Name="CityName" Type="varchar" MaxLength="50" />
<Property Name="ZipCode" Type="varchar" MaxLength="50" />
<Property Name="ProvinceID" Type="int" />
</EntityType>
<EntityContainer Name="testModelStoreContainer">
<EntitySet Name="tb_city" EntityType="Self.tb_city" Schema="test" store:Type="Tables" />
</EntityContainer>
</Schema>
</edmx:StorageModels>
從以上的對比,我們可以考慮重付,以一個文件為藍(lán)本顷级,然后在代碼生成工具里面,根據(jù)不同的數(shù)據(jù)類型确垫,映射成不同的XML文件弓颈,從而生成不同的EDMX文件即可,實體類和數(shù)據(jù)訪問上下文的類删掀,可以是通用的翔冀,這個一點也不影響概念模型的XML內(nèi)容了,所有部分變化的就是SSDL數(shù)據(jù)存儲部分的映射XML內(nèi)容爬迟。
為了測試驗證橘蜜,我增加了Mysql菊匿、Oracle共三個的EDMX文件付呕,并且通過不同的配置來實現(xiàn)不同數(shù)據(jù)庫的訪問調(diào)用计福。
我們知道,數(shù)據(jù)上下文的類構(gòu)建的時候徽职,好像默認(rèn)是指向具體的配置連接的象颖,如下代碼所示(注意紅色部分)。
/// <summary>
/// 數(shù)據(jù)操作上下文
/// </summary>
public partial class DbEntities : DbContext
{
//默認(rèn)的構(gòu)造函數(shù)
public DbEntities() : base("name=DbEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<City> City { get; set; }
public virtual DbSet<Province> Province { get; set; }
public virtual DbSet<DictType> DictType { get; set; }
}
如果我們需要配置而不是通過代碼硬編碼方式姆钉,那么是否可以呢说订?否則硬編碼的方式,一次只能是指定一個特定的數(shù)據(jù)庫潮瓶,也就是沒有多數(shù)據(jù)庫的配置的靈活性了陶冷。
找了很久,發(fā)現(xiàn)真的還是有這樣人提出這樣的問題毯辅,根據(jù)他們的解決思路埂伦,修改代碼如下所示,從而實現(xiàn)了配置的動態(tài)性思恐。
/// <summary>
/// 數(shù)據(jù)操作上下文
/// </summary>
public partial class DbEntities : DbContext
{
//默認(rèn)的構(gòu)造函數(shù)
//public DbEntities() : base("name=DbEntities")
//{
//}
/// <summary>
/// 動態(tài)的構(gòu)造函數(shù)
/// </summary>
public DbEntities() : base(nameOrConnectionString: ConnectionString())
{
}
/// <summary>
/// 通過代碼方式沾谜,獲取連接字符串的名稱返回。
/// </summary>
/// <returns></returns>
private static string ConnectionString()
{
//根據(jù)不同的數(shù)據(jù)庫類型胀莹,構(gòu)造相應(yīng)的連接字符串名稱
AppConfig config = new AppConfig();
string dbType = config.AppConfigGet("ComponentDbType");
if (string.IsNullOrEmpty(dbType))
{
dbType = "sqlserver";
}
return string.Format("name={0}", dbType.ToLower());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<City> City { get; set; }
public virtual DbSet<Province> Province { get; set; }
public virtual DbSet<DictType> DictType { get; set; }
}
我通過在配置文件里面基跑,指定ComponentDbType配置項指向那個連接字符串就可以了。
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="mssqllocaldb" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="Oracle.ManagedDataAccess.Client" type="Oracle.ManagedDataAccess.EntityFramework.EFOracleProviderServices, Oracle.ManagedDataAccess.EntityFramework" />
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6"></provider>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
<connectionStrings>
<add name="oracle" connectionString="metadata=res://*/Model.oracle.csdl|res://*/Model.oracle.ssdl|res://*/Model.oracle.msl;provider=Oracle.ManagedDataAccess.Client;provider connection string="DATA SOURCE=ORCL;DBA PRIVILEGE=SYSDBA;PASSWORD=whc;PERSIST SECURITY INFO=True;USER ID=WHC"" providerName="System.Data.EntityClient" />
<add name="sqlserver" connectionString="metadata=res://*/Model.sqlserver.csdl|res://*/Model.sqlserver.ssdl|res://*/Model.sqlserver.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=WinFramework;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
<add name="mysql" connectionString="metadata=res://*/Model.mysql.csdl|res://*/Model.mysql.ssdl|res://*/Model.mysql.msl;provider=MySql.Data.MySqlClient;provider connection string="server=localhost;user id=root;password=root;persistsecurityinfo=True;database=test"" providerName="System.Data.EntityClient" />
</connectionStrings>
<appSettings>
<add key="ComponentDbType" value="mysql" />
</appSettings>
OK描焰,這樣就很好解決了媳否,支持多數(shù)據(jù)庫的問題了。
4荆秦、框架分層結(jié)構(gòu)的提煉
我們在整個業(yè)務(wù)部分的項目里面逆日,把一些通用的內(nèi)容可以抽取到一個Common目錄層(如BaseBLL/BaseDAL等類或接口),這樣我們在BLL萄凤、DAL温治、IDAL纫谅、Entity目錄層,就只剩下一些和具體表相關(guān)的對象或者接口了,這樣的結(jié)構(gòu)我們可能看起來會清晰一些胚鸯,具體如下所示。
但是這樣雖然比原先清晰了一些乖坠,不過我們?nèi)绻麑惤涌谶M(jìn)行調(diào)整的話矮台,每個項目都可能導(dǎo)致不一樣了,我想把它們這些通用的基類內(nèi)容抽取到一個獨立的公用模塊里面(暫定為WHC.Framework.EF項目)漾月,這樣我在所有項目里面引用他就可以了病梢,這個做法和我在Enterprise Library框架的做法一致,這樣可以減少每個項目都維護(hù)公用的部分內(nèi)容,提高代碼的重用性蜓陌。
基于這個原則觅彰,我們重新設(shè)計了項目的分層關(guān)系,如下所示钮热。
這樣我們既可以減少主體項目的類數(shù)量填抬,也可以重用公用模塊的基類內(nèi)容,達(dá)到更好的維護(hù)隧期、使用的統(tǒng)一化處理飒责。