Entity Framework 實體框架的形成之旅--實體數(shù)據(jù)模型 (EDM)的處理(4)

在前面幾篇關(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=&quot;DATA SOURCE=ORCL;DBA PRIVILEGE=SYSDBA;PASSWORD=whc;PERSIST SECURITY INFO=True;USER ID=WHC&quot;" 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=&quot;data source=.;initial catalog=WinFramework;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" 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=&quot;server=localhost;user id=root;password=root;persistsecurityinfo=True;database=test&quot;" 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)一化處理飒责。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市仆潮,隨后出現(xiàn)的幾起案子宏蛉,更是在濱河造成了極大的恐慌,老刑警劉巖性置,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件檐晕,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚌讼,警方通過查閱死者的電腦和手機(jī)辟灰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篡石,“玉大人芥喇,你說我怎么就攤上這事』巳” “怎么了继控?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胖眷。 經(jīng)常有香客問我武通,道長,這世上最難降的妖魔是什么珊搀? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任冶忱,我火速辦了婚禮,結(jié)果婚禮上境析,老公的妹妹穿的比我還像新娘囚枪。我一直安慰自己,他們只是感情好劳淆,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布链沼。 她就那樣靜靜地躺著,像睡著了一般沛鸵。 火紅的嫁衣襯著肌膚如雪括勺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音疾捍,去河邊找鬼奈辰。 笑死,一個胖子當(dāng)著我的面吹牛拾氓,可吹牛的內(nèi)容都是我干的冯挎。 我是一名探鬼主播底哥,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼咙鞍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了趾徽?” 一聲冷哼從身側(cè)響起续滋,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孵奶,沒想到半個月后疲酌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡了袁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年朗恳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片载绿。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡粥诫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崭庸,到底是詐尸還是另有隱情怀浆,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布怕享,位于F島的核電站执赡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏函筋。R本人自食惡果不足惜沙合,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望跌帐。 院中可真熱鬧灌诅,春花似錦、人聲如沸含末。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佣盒。三九已至挎袜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盯仪。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工紊搪, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人全景。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓耀石,卻偏偏與公主長得像,于是被迫代替她去往敵國和親爸黄。 傳聞我的和親對象是個殘疾皇子滞伟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容