[原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(前篇)...
.NET 業(yè)務框架開發(fā)實戰(zhàn)之十 第一階段總結,深入淺出,水到渠成(前篇)
前言:這個系列有段時間沒有動了。主要是針對大家的反饋在修改代碼。在修改的過程中,也有了一些新的體會,這里和大家分享一下,同時也發(fā)布一下業(yè)務框架的第一個版本。在本篇文章中,學習到的不是僅僅只是代碼,而是設計的思想和實現這種思想的方法。在寫本篇時有個感觸:把一個東西徹底的講清楚,不容易。希望大家
多提意見。而且在寫本篇的時候,我個人也是很興奮的,至于原因相信大家在看完之后就知道了。J
本篇的議題如下:
1.????? 打通業(yè)務層和數據層
2.????? 打通方法的選擇和實現
3.????? 再次借鑒.NET Framework設計思想
4.????? 水到渠成
5.????? 代碼的版本說明
?
系列文章鏈接:
?[原創(chuàng)].NET 分布式架構開發(fā)實戰(zhàn)之一 故事起源
[原創(chuàng)].NET 分布式架構開發(fā)實戰(zhàn)之二 草稿設計
[原創(chuàng)].NET 分布式架構開發(fā)實戰(zhàn)之三 數據訪問深入一點的思考
[原創(chuàng)].NET 分布式架構開發(fā)實戰(zhàn)之四 構建從理想和實現之間的橋梁(前篇)
[原創(chuàng)].NET 分布式架構開發(fā)實戰(zhàn)五 Framework改進篇
[原創(chuàng)].NET 業(yè)務框架開發(fā)實戰(zhàn)之六 DAL的重構
[原創(chuàng)].NET 業(yè)務框架開發(fā)實戰(zhàn)之七 業(yè)務層初步構想
[原創(chuàng)].NET 業(yè)務框架開發(fā)實戰(zhàn)之八 業(yè)務層Mapping的選擇策略
[原創(chuàng)].NET 業(yè)務框架開發(fā)實戰(zhàn)之九 Mapping屬性原理和驗證規(guī)則的實現策略
[原創(chuàng)].NET 業(yè)務框架開發(fā)實戰(zhàn)之十 第一階段總結,深入淺出,水到渠成(前篇)
[原創(chuàng)].NET 業(yè)務框架開發(fā)實戰(zhàn)之十 第一階段總結,深入淺出,水到渠成(后篇)
1.??? 打通業(yè)務層和數據層
首先,回顧之前的文章一直討論的問題:
1. 如何使得數據層”以不變應萬變”。
2. 條件對象如何實現
3. 如何在業(yè)務層和數據層之間mapping數據
本篇就告訴大家,如何切切實實的解決上面三個問題。
?
首先,從一個圖示開始講述。
?
?
從上面的圖中可以看出,架起在BLL和DAL之前的橋梁的就是中間的那個條件對象。正是因為有了這個,所以上面的提出的問題才得以解決。
下面先從操作上總體來講述一下這個圖的具體流程:
a.?????? 在業(yè)務類中創(chuàng)建一個方法。例如在業(yè)務類User中,定義如下:
現在只看GetUserByAge這個方法:在方法中構造出一個條件對象,大家第一眼能看出來:是Linq的實現。其實最后的實現只是借用了Linq的思想,僅此而已。
b.?????? 解析條件對象。在上面的構造的條件對象中,Age,Name等,都是業(yè)務類User的字段,這些字段的值肯定最終是從數據庫中的表字段中獲取的(或者是通過數據庫中的值算出來的),所以在解析條件對象的時候,就要知道這些業(yè)務屬性對應數據庫中哪個表的哪個字段。因此在業(yè)務類中聲明每個屬性的時候就要同時保留它所對應的數據庫字段的信息。一旦業(yè)務類的屬性中保存了這些信息
c.?????? 數據層操作SQL語句。在解析條件對象的時候,就會最終得到相對應的SQL語句,然后在數據層中執(zhí)行這些SQL語句。
可能上面講的比較的抽象,因為本篇的要講述的東西確實比較的多(Word中寫了超過了10頁),上面講述的三個步驟也是先按大家有個印象。不是很懂也沒有關系。
?
?2.?打通方法的選擇和實現
?????? 接下來就是方法的探索和思考,以及實現的過程。我是想帶著個大家跟著一起看看,為什么最后會采用這個解決方案的。
首先,就從條件對象開始看起。
在實現條件對象(條件對象和查詢對象的區(qū)別之前講過,這里重述一下:查詢對象只是條件對象的一個子集,查詢對象用來在查詢的使用構造查詢條件;條件對象不僅僅在查詢時構造條件,而且在增加,刪除,,修改時候也使用,例如:只修改Name=”admin”的數據,在修改數據庫的時候也用了一定的條件。所以條件對象>查詢對象)的時候,也是參看了其他開源框架的一些實現(Nhibernate中查詢對象的,CSLA)。
同時要明白一點:設計出來的框架是給開發(fā)人員使用的,所以要考慮如何使得開發(fā)人員最快最好的使用框架,所以要從開發(fā)人員的角度看(這一點也是很重要的)。例如在Nhibernate中查詢對象,使用的方法如下(僅僅是簡單的舉例而已):
代碼 ?IListCustomer>?customers?=?_session.CreateCriteria(typeof(Customer))????????.Add(Restrictions.Like("Firstname",?"Xiaoyang%"))
????????.Add(Restrictions.Between("Lastname",?"A%",?"Y%"))
????????.ListCustomer>(); ? 其實本系列中的業(yè)務框架之前的條件對象的構造也是參看了Nhibernate中查詢對象的方法來實現和使用的,如下:
?
ICriteria?condition=CriteriaFactory.Create(typeof(ProductBL).Where("ProductName",?Operation.Equal,"book");?因為現在.NET中的開發(fā)人員對Linq的一些操作比較的熟悉,而且如果把條件對象的使用方式改為下面的方式:
ICriteria<ProductBL>?condition=CriteriaFactory.Create<ProductBL>(o?=>?o.ProductName?==?"book");?
那么開發(fā)人員的學習成本就幾乎為零(因為他們熟悉Linq,如果條件對象也采用這種比較統(tǒng)一的方法實現,他們就可以采用”以此類推”的思想來使用框架),更多的好處我就不說了,大家可以自己體會或者參看本系列之前的文章。
?
接下來就探索實現條件對象的方法(Linq to XXX篇)。
熟悉Linq的朋友可以看出:可以使條件對象實現IQueryable接口,然后采用實現linq to XXX的方法,實現自己的Linq Provider。這個方法是否可行,下面,我們就深入linq to XXX的來看一看,看完之后,結果就很清楚了。
首先來看下面兩個接口:?
代碼 ??public?interface?IQueryable?:?IEnumerable?{????????????????Type?ElementType?{?get;?}
????????Expression?Expression?{?get;?}
????????IQueryProvider?Provider?{?get;?}
????}
????public?interface?IQueryable<T>?:?IEnumerable<T>,?IQueryable,?IEnumerable?{} ? ?public interface IQueryProvider {
??????? IQueryable CreateQuery(Expression expression);
??????? IQueryable<TElement> CreateQuery<TElement>(Expression expression);
??????? object Execute(Expression expression);
??????? TResult Execute<TResult>(Expression expression);
??? }
??
Linq的出現,使得很多東西都和linq扯上了關系:Linq to sql, Linq to Google, Linq to javascript, Linq to Nhibernate...... 所列出來的這些,我們統(tǒng)稱為linq to XXX。這些Linq to XXX都是實現了上面兩個接口。
下面就通過自己實現linq to sql來舉例分析。
從總體來看:linq to sql的本質就是:把操作轉換為sql語句,然后用ADO.NET執(zhí)行,最后把結果轉換為實體返回。
其實下面列出了這么多的代碼,其中最關鍵的其實就是QueryProvider中的Execute方法:這個方法負責把你的操作進行解析,其實真正負責解析的QueryTranslator。
?
代碼 ?public?class?Query<T>?:?IQueryable<T>,?IQueryable,?IEnumerable<T>,?IEnumerable,?IOrderedQueryable<T>,?IOrderedQueryable?{
????????QueryProvider?provider;
????????Expression?expression;
?
????????public?Query(QueryProvider?provider)?{
????????????if?(provider?==?null)?{
????????????????throw?new?ArgumentNullException("provider");
????????????}
????????????this.provider?=?provider;
????????????this.expression?=?Expression.Constant(this);
????????}
?
????????public?Query(QueryProvider?provider,?Expression?expression)?{
????????????if?(provider?==?null)?{
????????????????throw?new?ArgumentNullException("provider");
????????????}
????????????if?(expression?==?null)?{
????????????????throw?new?ArgumentNullException("expression");
????????????}
????????????if?(!typeof(IQueryable<T>).IsAssignableFrom(expression.Type))?{
????????????????throw?new?ArgumentOutOfRangeException("expression");
????????????}
????????????this.provider?=?provider;?
????????????this.expression?=?expression;
????????}
?
????????Expression?IQueryable.Expression?{
????????????get?{?return?this.expression;?}
????????}
?
????????Type?IQueryable.ElementType?{
????????????get?{?return?typeof(T);?}
????????}
?
????????IQueryProvider?IQueryable.Provider?{
????????????get?{?return?this.provider;?}
????????}
?
????????public?IEnumerator<T>?GetEnumerator()?{
????????????return?((IEnumerable<T>)this.provider.Execute(this.expression)).GetEnumerator();
????????}
?
????????IEnumerator?IEnumerable.GetEnumerator()?{
????????????return?((IEnumerable)this.provider.Execute(this.expression)).GetEnumerator();
????????}
?
????????public?override?string?ToString()?{
????????????return?this.provider.GetQueryText(this.expression);
????????}
????}
?
?
代碼 public?abstract?class?QueryProvider?:?IQueryProvider?{????????protected?QueryProvider()?{
????????}
?
????????IQueryable<S>?IQueryProvider.CreateQuery<S>(Expression?expression)?{
????????????return?new?Query<S>(this,?expression);
????????}
?
????????IQueryable?IQueryProvider.CreateQuery(Expression?expression)?{
????????????Type?elementType?=?TypeSystem.GetElementType(expression.Type);
????????????try?{
????????????????return?(IQueryable)Activator.CreateInstance(typeof(Query<>).MakeGenericType(elementType),?new?object[]?{?this,?expression?});
????????????}
????????????catch?(TargetInvocationException?tie)?{
????????????????throw?tie.InnerException;
????????????}
????????}
?
????????S?IQueryProvider.Execute<S>(Expression?expression)?{
????????????return?(S)this.Execute(expression);
????????}
?
????????object?IQueryProvider.Execute(Expression?expression)?{
????????????return?this.Execute(expression);
????????}
?
????????public?abstract?string?GetQueryText(Expression?expression);
????????public?abstract?object?Execute(Expression?expression);
????}
?
?
?
代碼 internal?class?QueryTranslator?:?ExpressionVisitor?{????StringBuilder?sb;
?
????internal?QueryTranslator()?{
????}
?
????internal?string?Translate(Expression?expression)?{
????????this.sb?=?new?StringBuilder();
????????this.Visit(expression);
????????return?this.sb.ToString();
????}
?
????private?static?Expression?StripQuotes(Expression?e)?{
????????while?(e.NodeType?==?ExpressionType.Quote)?{
????????????e?=?((UnaryExpression)e).Operand;
????????}
????????return?e;
????}
?
????protected?override?Expression?VisitMethodCall(MethodCallExpression?m)?{
????????if?(m.Method.DeclaringType?==?typeof(Queryable)?&&?m.Method.Name?==?"Where")?{
????????????sb.Append("SELECT?*?FROM?(");
????????????this.Visit(m.Arguments[0]);
????????????sb.Append(")?AS?T?WHERE?");
????????????LambdaExpression?lambda?=?(LambdaExpression)StripQuotes(m.Arguments[1]);
????????????this.Visit(lambda.Body);
????????????return?m;
????????}
????????throw?new?NotSupportedException(string.Format("The?method?'{0}'?is?not?supported",?m.Method.Name));
????}
?
????protected?override?Expression?VisitUnary(UnaryExpression?u)?{
????????switch?(u.NodeType)?{
????????????case?ExpressionType.Not:
????????????????sb.Append("?NOT?");
????????????????this.Visit(u.Operand);
????????????????break;
????????????default:
????????????????throw?new?NotSupportedException(string.Format("The?unary?operator?'{0}'?is?not?supported",?u.NodeType));
????????}
????????return?u;
????}
?
????protected?override?Expression?VisitBinary(BinaryExpression?b)?{
????????sb.Append("(");
????????this.Visit(b.Left);
????????switch?(b.NodeType)?{
????????????case?ExpressionType.And:
????????????????sb.Append("?AND?");
????????????????break;
????????????case?ExpressionType.Or:
????????????????sb.Append("?OR");
????????????????break;
????????????case?ExpressionType.Equal:
????????????????sb.Append("?=?");
????????????????break;
????????????case?ExpressionType.NotEqual:
????????????????sb.Append("?<>?");
????????????????break;
????????????case?ExpressionType.LessThan:
????????????????sb.Append("?<?");
????????????????break;
????????????case?ExpressionType.LessThanOrEqual:
????????????????sb.Append("?<=?");
????????????????break;
????????????case?ExpressionType.GreaterThan:
????????????????sb.Append("?>?");
????????????????break;
????????????case?ExpressionType.GreaterThanOrEqual:
????????????????sb.Append("?>=?");
????????????????break;
????????????default:
????????????????throw?new?NotSupportedException(string.Format("The?binary?operator?'{0}'?is?not?supported",?b.NodeType));
????????}
????????this.Visit(b.Right);
????????sb.Append(")");
????????return?b;
????}
?
????protected?override?Expression?VisitConstant(ConstantExpression?c)?{
????????IQueryable?q?=?c.Value?as?IQueryable;
????????if?(q?!=?null)?{
????????????//?assume?constant?nodes?w/?IQueryables?are?table?references
????????????sb.Append("SELECT?*?FROM?");
????????????sb.Append(q.ElementType.Name);
????????}
????????else?if?(c.Value?==?null)?{
????????????sb.Append("NULL");
????????}
????????else?{
????????????switch?(Type.GetTypeCode(c.Value.GetType()))?{
????????????????case?TypeCode.Boolean:
????????????????????sb.Append(((bool)c.Value)???1?:?0);
????????????????????break;
????????????????case?TypeCode.String:
????????????????????sb.Append("'");
????????????????????sb.Append(c.Value);
????????????????????sb.Append("'");
????????????????????break;
????????????????case?TypeCode.Object:
????????????????????throw?new?NotSupportedException(string.Format("The?constant?for?'{0}'?is?not?supported",?c.Value));
????????????????default:
????????????????????sb.Append(c.Value);
????????????????????break;
????????????}
????????}
????????return?c;
????}
?
????protected?override?Expression?VisitMemberAccess(MemberExpression?m)?{
????????if?(m.Expression?!=?null?&&?m.Expression.NodeType?==?ExpressionType.Parameter)?{
????????????sb.Append(m.Member.Name);
????????????return?m;
????????}
????????throw?new?NotSupportedException(string.Format("The?member?'{0}'?is?not?supported",?m.Member.Name));
????}
}
?
??
對于實現了IQueryable的對象,在他們進行的每一個操作(也就是調用實現這個接口的類上的方法)其實都沒有立刻去執(zhí)行,而且把進行的操作記錄下來了,放在一個稱為Expression Tree表達式數的數據結構中,然后再真正執(zhí)行的時候(就是調用Execute來執(zhí)行), QueryTranslator對象就遍歷表達式樹,對操作進行解析,例如linq to sql就是把表達式樹中操作解析為對數據庫進行的操作,以sql語句的形式體現出來.
?
?????? 當把操作解析為了sql語句之后,就是用ADO.NET的方法來執(zhí)行SQL操作,然后通過反射,把ADO.NET執(zhí)行的結果轉換為數據實體。如下:???????
代碼 internal?class?ObjectReader<T>?:?IEnumerable<T>,?IEnumerable?where?T?:?class,?new()?{
????Enumerator?enumerator;
?
????internal?ObjectReader(DbDataReader?reader)?{
????????this.enumerator?=?new?Enumerator(reader);
????}
?
????public?IEnumerator<T>?GetEnumerator()?{
????????Enumerator?e?=?this.enumerator;
????????if?(e?==?null)?{
????????????throw?new?InvalidOperationException("Cannot?enumerate?more?than?once");
????????}
????????this.enumerator?=?null;
????????return?e;
????}
?
????IEnumerator?IEnumerable.GetEnumerator()?{
????????return?this.GetEnumerator();
????}
?
????class?Enumerator?:?IEnumerator<T>,?IEnumerator,?IDisposable?{
????????DbDataReader?reader;
????????FieldInfo[]?fields;
????????int[]?fieldLookup;
????????T?current;
?
????????internal?Enumerator(DbDataReader?reader)?{
????????????this.reader?=?reader;
????????????this.fields?=?typeof(T).GetFields();
????????}
?
????????public?T?Current?{
????????????get?{?return?this.current;?}
????????}
?
????????object?IEnumerator.Current?{
????????????get?{?return?this.current;?}
????????}
?
????????public?bool?MoveNext()?{
????????????if?(this.reader.Read())?{
????????????????if?(this.fieldLookup?==?null)?{
????????????????????this.InitFieldLookup();
????????????????}
????????????????T?instance?=?new?T();
????????????????for?(int?i?=?0,?n?=?this.fields.Length;?i?<?n;?i++)?{
????????????????????int?index?=?this.fieldLookup[i];
????????????????????if?(index?>=?0)?{
????????????????????????FieldInfo?fi?=?this.fields[i];
????????????????????????if?(this.reader.IsDBNull(index))?{
????????????????????????????fi.SetValue(instance,?null);
????????????????????????}
????????????????????????else?{
????????????????????????????fi.SetValue(instance,?this.reader.GetValue(index));
????????????????????????}
????????????????????}
????????????????}
????????????????this.current?=?instance;
????????????????return?true;
????????????}
????????????return?false;
????????}
?
????????public?void?Reset()?{
????????}
?
????????public?void?Dispose()?{
????????????this.reader.Dispose();
????????}
?
????????private?void?InitFieldLookup()?{
????????????Dictionary<string,?int>?map?=?new?Dictionary<string,?int>(StringComparer.InvariantCultureIgnoreCase);
????????????for?(int?i?=?0,?n?=?this.reader.FieldCount;?i?<?n;?i++)?{
????????????????map.Add(this.reader.GetName(i),?i);
????????????}
????????????this.fieldLookup?=?new?int[this.fields.Length];
????????????for?(int?i?=?0,?n?=?this.fields.Length;?i?<?n;?i++)?{
????????????????int?index;
????????????????if?(map.TryGetValue(this.fields[i].Name,?out?index))?{
????????????????????this.fieldLookup[i]?=?index;
????????????????}
????????????????else?{
????????????????????this.fieldLookup[i]?=?-1;
????????????????}
????????????}
????????}
????}
}
?
?
?????? 上面簡單的介紹了如何實現linq to sql具體的實現代碼,大家可以自己過后慢慢的看或者參看我的另外的一個linq系列,現在我們繼續(xù)后面的話題。?
?????? 現在我們回到之前的話題:條件對象是否可以采用這種方式實現???????
?????? 大家看到上面的代碼Query<T>其中的T,和Execute方法的返回值。在上面的代碼中,如果T是User類,即,Query<User>,那么最后的Execute方法返回的一定會是User的集合。也就是說:Execute方法已經對數據源(這里是數據庫)進行了操作,并且把結果以數據實體的形式已經返回了,并且返回的數據實體的類型就是T,例如User。
?????? 但是我們這里的要實現的條件對象只是想把條件構造出來,不是立刻去執(zhí)行。至于具體的執(zhí)行數據操作者(DataProvider)可以任意選擇的:使用ADO.NET方法,還是EF的方法,還是Nhibernate,都是可以配置的。如下:??????
代碼 ????public?List<User>?GetUserByAge()
????????{
????????????ICriteria<User>?conditionPerson?=
???????????????CriteriaFactory.Create<User>().Where(u?=>?u.Age?<?this.Age).OrderBy<string>(u?=>?u.Name).Skip(8).Take(8);
????????????return?DataPortal.Query(conditionPerson);
????????}
???????
上面的代碼中,Create方法就是實例化一個ICriteria<User>,此時我們想做的僅僅只是一件事:把在 ICriteria<User>上的操作記錄下來而已。然后把記錄下來的結果解析,解析的最終結果就是一條sql命令,然后再給不同的DataProvider去執(zhí)行。也就是說,在DataPortal內部可以配置用什么方法來執(zhí)行數據操作:是直接使用ADO.NET執(zhí)行sql命令,還是把sql命令給Entity Framework...通過配置決定。如果ICriteria<T>是從IQueryable接口進行了繼承,那么在ICriteria實現這個結果的過程中就必須要去數據庫中進行執(zhí)行,因為Execute方法返回的是T的集合,而不是sql命令(字符串)。?
?????? 大家可能想到:那就在Execute方法中去實現不同的DataProvider,例如之前的例子在ObjectReader用ADO.NET實現了,那么也可以在ObjectReader中用EF實現數據操作。這個方法確實可以,也很不錯。但是這個方法在分布式開發(fā)中(特別是在WCF中)有一點的局限性。例如你有一個界面,上面可以有很多的選項,如下:
?
?
在服務接口那邊,你肯定不想定義N多差不多的接口方法:如
GetUserByName(string?username);GetUserByEmail(string?email);
?
或者
GetUserByCondition(string?username,string?password,string?email?.....);?
? 這樣都是很不靈活的,如果User的屬性減少了或者增多了,那么如果要在服務器那邊暴露的接口的方法也要修改,這樣終究是不好。如下采用下面的方法:?
GetUserByCondition(Critera?condition);??
其中,Critera是條件對象。那么我們在客戶端就可以任意構造條件對象,這個條件對象就把在它上面進行的操作記錄下來,然后統(tǒng)一的交給GetUserByCondition方法去服務器解釋并執(zhí)行。此時,這個條件對象就是在客戶端生成的,而且這個條件對象此時是不用去數據庫中去執(zhí)行的。如果條件對象是從IQueryable接口繼承的,那么在客戶端構造完條件對象之后,就要去數據庫中執(zhí)行了,如果再在ObjectReader搞個分布式調用,難度不說,也很別扭,這不是我們所要的。
?????? 所以,綜合上面的一些考慮,那么可以確定:條件對象不繼承IQueryable接口。但是我們又希望采用類似linq的操作,那么只有自己實現了。?
本篇就暫時寫到這里,因為太長了,所以分為前篇和后篇發(fā)布,因為博客園不能在一小時內發(fā)兩篇,所以后篇將會在9點左右發(fā)布。希望大家見諒。
版權為小洋和博客園所有,,歡迎轉載,轉載請標明出處給作者。
? http://www.cnblogs.com/yanyangtian
?
總結
以上是生活随笔為你收集整理的[原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(前篇)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 教你轻松恢复Linux口令
- 下一篇: 常用命令示例