前一陣我總結過一篇關於IRepository的文章:我所理解的IRepository,當時只是純屬從單一接口上來做分享,但畢竟接口並不是單獨存在的,它需要很好的融入到項目中去,這就是一個員工需要融入到團隊中一樣。而要想讓IRepositoyr在項目中發揮得當,最好根據自己項目的需求來做相應調整,畢竟適應自己公司大環境的解決方案才是最有可能成功的應用,比如我們是EntityFrame+MVC。
第一:在原來接口的基礎上做些補充。
1:增加了一個支持分頁的方法。這個其實是非常見的方法,只需要在原來查詢接口中做些封裝即可。接口定義如下:
客戶可以這樣調用:
注意:這個分頁方法的參數,最好傳表達式樹,讓其動態構建SQL,如果傳Func這種泛型委托的話,一定要在調用Context時將其轉換成表達式樹,否則就無法在服務器端完成分頁了。
2:為Update方法增加了一個重載方法,定義如下:
其實T Update(T entity)這個接口也能完成更新操作,但需要傳入的實體參數自身包含主鍵相關查詢字段信息,否則無法找到需要更新的記錄是哪條。之所以增加一個重載方法,我個人認為新增加的方法更加符合平時的操作習慣,跟SQL的更新語法是比較符合的,第一步是確認需要更新的字段,第二步是選擇更新的記錄是哪條。調用示例如下:
第二:對IRepository的應用注入一些自定義的事件。
在應用EntityFramword對其表進行CRUD時,在某些環境下,表中的一部分字段屬於系統字段(比如記錄的創建日期CreatedOn,修改日期ModifiedOn),為了簡化操作,我們可以在操作數據時將這些系統字段由人工處理變成自動處理,即客戶端調用IRepository時不傳遞這些系統字段的信息,只傳和業務邏輯緊密相關的字段(比如學生表中的姓名,學號,地址等),然后IRepository會自動的將這些系統字段的值填充上。這樣從某種程度上可以使我們的領域模型變得更加簡單,也可以減輕程序員對領域模型的維護成本。
以前聽說過一句話:代碼的行數和出錯誤的概率是成正比的,這里同樣的道理,領域模型的字段數量與出錯誤的概率是成正比的。
解決方案:在Context上訂閱自定義事件,這樣在正式提交數據庫之前會先執行我們定義的自定義事件之后再提交數據庫,所以我們可以利用EntityObject提供的MarkCreated方法來自動的將我們定義的一些系統字段給加上去。
注意:這里之所以可以這樣做,也需要取決於我們的數據庫設計,數據表都遵守一定的約束,比如所有表都會具備相同的系統字段。只有這樣我們才能放開手腳使用下面的解決方案。
public void ContextPersistent( object sender, EventArgs e)
{
ObjectContext oc = sender as ObjectContext;
ObjectStateManager osm = oc.ObjectStateManager;
IEnumerable<ObjectStateEntry> newEntities = osm.GetObjectStateEntries(EntityState.Added);
foreach (ObjectStateEntry ose in newEntities)
{
EntityObject newAdded = (EntityObject)context.GetObjectByKey(ose.EntityKey);
newAdded.MarkCreated(CurrentUser);
}
}
public static void MarkCreated( this EntityObject eo, Guid user)
{
Type type = eo.GetType();
PropertyInfo pInfo = type.GetProperty( " CreatedOn ");
if (pInfo != null)
pInfo.SetValue(eo, DateTime.UtcNow, null);
PropertyInfo mInfo = type.GetProperty( " ModifiedOn ");
if (mInfo != null)
mInfo.SetValue(eo, DateTime.UtcNow, null);
}
第三:如何解決IRepository,數據庫模型和ViewModel之間的關系。
我們的項目是asp.net mvc項目,所以我們有ViewModel的概念,而我們采用了數據庫優先的方式調用EntityFramwork,在添加完edmx后,我們也就有了數據庫對象,這些數據庫對象都是系統自動生成的,當然和數據庫中的字段是一對一的關系,但ViewModel並不一定和數據表字段是一對一的關系。
問題:既然我們定義了IRepository接口,那么意味着MVC在調用數據時也會遵守此接口的契約,問題在於,IRepository在處理數據時,參數是數據庫對象,而在MVC調用時我們是不太可能直接傳遞數據庫對象的,(數據庫對象屬於DataAccess層,MVC屬於UI層,this.entitySet.FirstOrDefault(filter);這其中的filter表達式中的實體需要是數據庫對象。)它只有ViewModel的概念,所以我們需要將Query(Expression<Func<ViewModel, bool>> filter);轉換成Query(Expression<Func<DataBaseModel, bool>> filter);
解決方案:利用自定義表達式來完成轉達式樹之間的轉換,原理就是將ViewModel佣有的屬性在DataBaseModel中遍歷,如果有相同屬性,就賦值。
比如:ViewModel為StudentModel,它有一個Name屬性,DataBaseModel為Student,它也有一個Name屬性,目標就是將p=>p.Name=="張三"(p的類型是StudentModel)轉換成p=>p.Name=="張三"(p的類型是Student).
{
// Methods
public static Expression<Func<TToB, TR>> Convert<TFrom, TR>(Expression<Func<TFrom, TR>> e)
{
ParameterExpression oldParameter = e.Parameters[ 0];
ParameterExpression newParameter = Expression.Parameter( typeof(TToB), oldParameter.Name);
ConversionVisitor<TToB> visitor = new ConversionVisitor<TToB>(newParameter, oldParameter);
return Expression.Lambda<Func<TToB, TR>>(visitor.Visit(e.Body), new ParameterExpression[] { newParameter });
}
// Nested Types
private class ConversionVisitor : ExpressionVisitor
{
// Fields
private readonly ParameterExpression newParameter;
private readonly ParameterExpression oldParameter;
// Methods
public ConversionVisitor(ParameterExpression newParameter, ParameterExpression oldParameter)
{
this.newParameter = newParameter;
this.oldParameter = oldParameter;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression != this.oldParameter)
{
return base.VisitMember(node);
}
Expression expression = this.Visit(node.Expression);
MemberInfo member = this.newParameter.Type.GetMember(node.Member.Name).First<MemberInfo>();
return Expression.MakeMemberAccess(expression, member);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return this.newParameter;
}
}
}
感嘆表達樹是多么的強大,幾十行代碼就能完成一些意想不到的效果,總之以上是我從自己項目出發,對於IRepository應用上的一些理解。