EF6.0 生成的程式碼中沒有註釋的解決方法
目錄
初試Entity Framework6.0
之前一直在使用vs2010或者是vs2008,也一直使用的EF4.0一下的版本……在之前,也習慣了Model First的EF設計方式,因為感覺,在設計介面中可以更好的幫助構思;同時,在設計介面中也很容易的增加一些文字說明(這些說明會存在與最終生成的實體類中)。
發現問題
在安裝的vs2013之後,我趕緊試了EF6.0……此處省略好幾萬字。
打開了一個以前設計並完成的專案,當中包含了edmx檔案,我想看看ef6.0生成的程式碼到底和ef4.0有什麼不同。
結果發現:ef6.0(T4模板)生成的cs檔案中,竟然沒有包含註釋(edmx中的文件),這個讓我情何以堪啊。
先來回顧一下ef4.0生成的內容
ef4.0 關係元資料(不知道大家看不看,反正我沒仔細看過)
#region EDM 關係源元資料
[assembly: EdmRelationshipAttribute("Models", "HospitalProject", "Hospital", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(HNWMS.EFData.Hospital), "Project", System.Data.Metadata.Edm.RelationshipMultiplicity .One, typeof(HNWMS.EFData.Project))]
///... ...
[assembly: EdmRelationshipAttribute("Models", "UserMyDisk", "User", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(HNWMS.EFData.User), "MyDisk", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(HNWMS.EFData.MyDisk))]
#endregion
ef4.0 Container 宣告以
ef4.0中Container繼承自ObjectContext
建構函式(預設生成的)有三個。
/// <summary>
/// 沒有元資料文件可用。
/// </summary>
public partial class Container : ObjectContext
ef4.0 實體集合
有公共的帶有get訪問器的屬性,也有對應的私有欄位。完全面向物件的寫法;唯一不好的就是,沒有自動生成實體集合的註釋內容(summary)。
/// <summary>
/// 沒有元資料文件可用。
/// </summary>
public ObjectSet<User> Users
{
get
{
if ((_Users == null))
{
_Users = base.CreateObjectSet<User>("Users");
}
return _Users;
}
}
private ObjectSet<User> _Users;
ef4.0 AddTo方法
雖然表明了“已棄用”,但是,在其他地方呼叫的時候還是很方便,起碼很直接。
/// <summary>
/// 用於向 Users EntitySet 新增新物件的方法,已棄用。請考慮改用關聯的 ObjectSet<T> 屬性的 .Add 方法。
/// </summary>
public void AddToUsers(User user)
{
base.AddObject("Users", user);
}
ef4.0 實體宣告
加入了很多Attribute的宣告,說實在的,我從來沒有注意過這些東西,也不知道做什麼用的。
/// <summary>
/// 使用者基類(同時描述總部人員)
/// </summary>
[EdmEntityTypeAttribute(NamespaceName="Models", Name="User")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
[KnownTypeAttribute(typeof(ProjectManager))]
[KnownTypeAttribute(typeof(HospitalServicer))]
[KnownTypeAttribute(typeof(NursingWorker))]
public partial class User : EntityObject
ef4.0 實體建構函式
龐大到不敢看,引數是所有屬性的排列,然後加上了個global::用以排除型別衝突;在開發的過程中,我似乎沒有使用過實體建構函式來構造實體的例項。
public static User CreateUser(global::System.String code, global::System.String loginPassword, global::System.String realName, global::System.String mobilePhoneNO, global::System.String xType, global::System.String iDCordNO)
ef4.0 基元屬性
同實體集合一般,完全符合面向物件的寫法,有屬性,就有對應的欄位存在。另外再set訪問器中還顯示的觸發了“自跟蹤實體”的四個事件(具體什麼是自跟蹤實體,這裡不做複述。)
/// <summary>
/// 使用者編碼
/// </summary>
/// <LongDescription>
/// 編碼規則:
U開頭+4位數字順序碼
若4位順序碼達到9999則為
UA開頭+3為數字順序碼
以此類推

/// </LongDescription>
[EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String Code
{
get
{
return _Code;
}
set
{
if (_Code != value)
{
OnCodeChanging(value);
ReportPropertyChanging("Code");
_Code = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("Code");
OnCodeChanged();
}
}
}
private global::System.String _Code;
雖然,我在上邊的描述中說了提到了很多關於ef4.0中的“不好”,但是這並不表示我不認同ef4.0。下面說幾點我理解的ef的好處:
- 我還是很喜歡ef的,因為其簡單、易用,配合上mvc、WCF Data Service很容易就完成一個解決方案;
- 我更加喜歡vs提供的edmx設計器,在這裡我可以不借助其他“構思輔助”軟體,就可以完成“資料設計”以及“資料關係”的梳理工作。
- 通過edmx我可以很容易的為“實體”“屬性”“導航屬性”新增“文件”(在最終的程式碼中為summary,這也是我寫這篇文章的目的。)
因為解決方案中存在有一個edmx檔案,在其他人接手改解決方案時,很容易就可以看到資料設計,很容易就可以理解資料關係。
所以,在大家都在“喊叫”EF code first的時候,我仍然使用著model or db first。所謂“仁者見仁智者見智,蘿蔔白菜各有所愛”,大牛們別噴我。謝謝^_&.
再來看EF6.0生成的內容
ef6.0 建構函式
不同於EF4.0,在生成的Container上方,並沒有出現很多的“元資料”內容。開啟這個類的時候,給我的第一感覺就是,啊!好簡單啊。
Container繼承自DBContext,至於與ObjectContext有什麼不同,請大家找度娘或者MSDN。
public partial class Container : DbContext
ef6.0 實體集宣告
與ef4.0首先不同在,實體集不再是ObjectSet型別的了,而是DBSet型別的了;其次,它簡化了get和set訪問器(同時少了對應欄位的宣告,到時符合了隱式屬性訪問器的宣告方法)
不好再那裡呢???註釋那裡去了,ef4.0最起碼還有個 “沒有元資料文件可用。”的註釋。
public virtual DbSet<User> Users { get; set; }
ef6.0 實體宣告
與ef4.0不同在,實體的建構函式不再那麼臃腫,無引數,在內部也僅僅是為導航屬性賦予了初始值;同時,屬性的get和set也簡化了,乍一看,這樣一個類好像是誰寫出來的Demo呢。
不過,與實體集宣告 一般,註釋那裡去了,ef4.0中,可是把edmx中的“文件以及長說明”都生成在了summary中的。
public partial class User
{
public User()
{
this.SignIns = new HashSet<SignIn>();
this.MyMenus = new HashSet<MyMenu>();
this.MyDisks = new HashSet<MyDisk>();
}
public string Code { get; set; }
public string LoginPassword { get; set; }
public string RealName { get; set; }
public string MobilePhoneNO { get; set; }
public string xType { get; set; }
public string IDCordNO { get; set; }
public virtual ICollection<SignIn> SignIns { get; set; }
public virtual ICollection<MyMenu> MyMenus { get; set; }
public virtual ICollection<MyDisk> MyDisks { get; set; }
}
上面非常簡單的和膚淺的敘述了ef4.0和ef6.0之間的區別,並闡述了我個人的好惡。
問題解決的必要性
看到這裡,很多大牛可能會說“老弟,out man,大家現在都在2015,你還2013呢??”“不就是一個註釋麼?有那個必要麼?”
小弟我解釋一下,呵呵,有幾年了,沒有工作在第一線,沒有進行過code的工作,所以對於vs和其他編輯器等,都不甚熟悉了……註釋可是很重要的,尤其是符合vs要求的/// summary 註釋,因為vs有智慧提示啊,我想大家都見過也用過,當.的時候,就會出現很多該型別下的屬性啊,方法啊…….拜託都是英文,如何分辨?就算你E文好,請問,你一個人開發應用系統的?
呵呵,有點強詞奪理了。
總之,我要大吼一聲“我要註釋!不管是我寫的程式碼的註釋,還是生成的程式碼的註釋,我都要!還要看的懂得註釋。”
悄悄的告訴一些比我還菜的小鳥們,其實vs中程式碼的(符合規範的)註釋,相當有用的,比如開發完成後,你可以通過註釋生成chm型別或者help view2.x型別的幫助檔案的(這裡不多說)。
解決問題
第一步 為edmx新增“程式碼生成項”也就是“EF6.x DBContext生成器”
步驟:
- 在vs中開啟edmx
- 隨便找個空白的地方右鍵,然後選擇“新增程式碼生成項”
- 然後在隨後出現的視窗中選擇“EF6.x DBContext生成器”,確定
- 驗證:在解決方案資源管理器中,edmx檔案下方就會出現你剛剛新增的“程式碼生成內容”,一般叫做Model.Context.tt 和Model.tt(其實這兩個東西,就是T4模板;開啟它,然後按“ctrl+s”的時候,它就會自動執行,並按照edmx檔案中的設計,生成相關的Container 、DBSet、entity)
第二步 修改T4模板內容
Model.Context.tt檔案
找到內容:
public string DbSet(EntitySet entitySet,System.Collections.Generic.IEnumerable<System.Data.Entity.Core.Metadata.Edm.GlobalItem> itemCollection)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} virtual DbSet<{1}> {2} {{ get; set; }}",
Accessibility.ForReadOnlyProperty(entitySet),
_typeMapper.GetTypeName(entitySet.ElementType),
_code.Escape(entitySet), summary);
}
這個方法是幹什麼的呢?其實就是返回一段類似下發程式碼的文字
public virtual DbSet<User> Users { get; set; }
更改成為:
public string DbSet(EntitySet entitySet,System.Collections.Generic.IEnumerable<System.Data.Entity.Core.Metadata.Edm.GlobalItem> itemCollection)
{
string summary = entitySet.ElementType.Documentation == null ? "(error message:not have summary in edmx.)" : entitySet.ElementType.Documentation.Summary;
return string.Format(
CultureInfo.InvariantCulture,
"/// <summary>\r\n\t/// {3}的集合\r\n\t/// </summary>\r\n\t{0} virtual DbSet<{1}> {2} {{ get; set; }}",
Accessibility.ForReadOnlyProperty(entitySet),
_typeMapper.GetTypeName(entitySet.ElementType),
_code.Escape(entitySet), summary);
}
呵呵,你可能覺得,我寫的Format中,不該美觀。。。。。。我的解釋是,反正這個T4最終不會被編譯到程式集中,無所謂啦。
這樣,最終產生的內容就類似與:
/// <summary>
/// 使用者基類(同時描述總部人員)的集合
/// </summary>
public virtual DbSet<User> Users { get; set; }
Model.tt檔案
找到內容:
foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection))
{
fileManager.StartNewFile(entity.Name + ".cs");
BeginNamespace(code);
#>
這段程式碼,就是根據實體的型別名稱,產生一個獨立的cs檔案,併產生Namespace宣告,若為型別添加註釋,不就是在Namespace下邊麼……
更改成為:
string summary=string.Empty;
foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection))
{
fileManager.StartNewFile(entity.Name + ".cs");
BeginNamespace(code);
summary = entity.Documentation == null ? entity.Name : entity.Documentation.Summary;
#>
/// <summary>
/// <#=summary#>
/// </summary>
還沒有完成,繼續,找到
public string Property(EdmProperty edmProperty)
{
return string.Format(CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}
public string NavigationProperty(NavigationProperty navProp)
{
var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType());
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)),
navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
_code.Escape(navProp),
_code.SpaceAfter(Accessibility.ForGetter(navProp)),
_code.SpaceAfter(Accessibility.ForSetter(navProp)));
}
更改為:
public string Property(EdmProperty edmProperty)
{
return string.Format(CultureInfo.InvariantCulture,
"/// <summary>\r\n\t/// {5}\r\n\t/// </summary>\r\n\t{0} {1} {2} {{ {3}get; {4}set; }}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)),
edmProperty.Documentation == null ? "" : edmProperty.Documentation.Summary
);
}
public string NavigationProperty(NavigationProperty navProp)
{
var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType());
return string.Format(
CultureInfo.InvariantCulture,
"/// <summary>\r\n\t/// {5}\r\n\t/// </summary>\r\n\t{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)),
navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
_code.Escape(navProp),
_code.SpaceAfter(Accessibility.ForGetter(navProp)),
_code.SpaceAfter(Accessibility.ForSetter(navProp)),
navProp.Documentation == null ? "" : navProp.Documentation.Summary
);
}
然後“儲存”,產生的cs檔案中就類似與下邊了:
/// <summary>
/// 使用者基類(同時描述總部人員)
/// </summary>
public partial class User
{
......
/// <summary>
/// 使用者編碼
/// </summary>
public string Code { get; set; }
......
}
OK!這樣就搞定了edmx生成內容中無註釋的問題了。
再次大吼“註釋有用!”
總結
總之,我個人覺得,在使用一個(別人能夠歸納與一個系列的)新東西的時候,一定要從設計者的角度去考慮,每一樣東西都是有用的。