以前寫過一篇《統一的倉儲接口》,爲了方便使用不一樣的倉儲。在咱們的項目中使用的是EF4.0,可是這個版本的EF有一些性能問題沒有解決,又不想升級到EF6,具體EF6有沒有解決暫時不清楚。咱們的項目以前運行的都不錯,忽然一天數據庫服務器CPU 100%,IIS服務器CPU又正常,過幾個小時以後又恢復正常,每一個星期一早上都這樣,能夠確定就是用戶同時操做併發過多形成,查找以後,是一個表的數據被鎖住。報錯was deadlocked on lock,解決辦法就是查詢sql上加上 with nolock,可是EF不支持,但有不想放棄linq to sql的優點,無奈只能本身實現,方案是以前已有功能併發很少的地方保持不變,依然使用EF,在併發多的地方使用本身實現的linq to sql。sql
目前並無完整實現linq to sql,只是實現了單表的狀況,對於有關聯引用因爲時間限制並未實現。數據庫
實現倉儲,須要實現兩個對象,[IObjectContext、IOrderedQueryable]express
上篇說到的上下文對象IObjectContext設計模式
public interface IObjectContext:IDisposable { string Name { get; set; } IDbConnection CreateConnection(); bool SaveChanges(); }
考慮到項目中有些特殊狀況須要使用sql,因此增長了CreateConnection方法返回一個IDbConnection,固然在逼不得已的狀況下使用。服務器
DbContext的實現比較簡單,重點管理一下鏈接池,配置,大概以下,具體的實現因爲篇幅省略,源碼會在篇尾附上。閉包
public class DbContext : IDisposable, IObjectContext { public EntityStateManager Manager { get; private set; } public string DbType { get; private set; } public string ConnectionString { get; set; } public int PoolSize { get; set; } public bool AllowUpdateWithNoExp { get; set; } public bool AllowDeleteWithNoExp { get; set; } List<System.Data.IDbConnection> connPool = new List<System.Data.IDbConnection>(); public DbContext(); public DbContext(string connectionString, string dbType); public DbContext(string connectionString, string dbType, bool allowUpdateWithNoExp, bool allowDeleteWithNoExp); void InitContext(string connectionString, string dbType, int poolSize, bool allowUpdateWithNoExp, bool allowDeleteWithNoExp); public EntitySet<T> GetEntitySet<T>(string tableName = null, bool noLock = false, bool noTracking = false); public int SubmitChange(); void IDisposable.Dispose(); string IObjectContext.Name { get; set; } bool IObjectContext.SaveChanges(); public System.Data.IDbConnection CreateConnection(); System.Data.IDbConnection IObjectContext.CreateConnection();
另外一個對象則是倉儲EntitySet<T>,大概代碼以下併發
public class EntitySet<T> : IOrderedQueryable<T> { public DbContext Context { get; private set; } string TableName { get; set; } bool NoLock { get; set; } bool NoTracking { get; set; } DbQueryProvider MyProvider { get; set; } Expression MyExpression { get; set; } public EntitySet(string tableName, bool noLock, bool noTracking, DbContext context); public EntitySet(string tableName, bool noLock, bool noTracking, Expression expression, DbContext context); void InitEntitySet(string tableName, bool noLock, bool noTracking, Expression expression, DbContext context); public Expression Expression { get { return this.MyExpression; } } public Type ElementType { get { return typeof(T); } } public IQueryProvider Provider { get { return this.MyProvider; } } public IEnumerator<T> GetEnumerator(); IEnumerator IEnumerable.GetEnumerator(); void MyProvider_OnExecuted(object sender, EventArgs e); void TrackEntity(object result); void notifyT_PropertyChanged(object sender, PropertyChangedEventArgs e); public void Add(T item, bool INSERT_IDENTITY = false); public void Update(Expression<Func<T, bool>> exp, object obj); public void Remove(T item); public void Remove(Expression<Func<T, bool>> exp);
在倉儲裏面我增長了我須要的東西,好比Add能夠插入標識,Update能夠根據表達式更新對象,而不須要把所須要的對象先取出來,修改再保存,數據量大的時候EF性能有問題,Remove也一樣如此,爲了防止誤操做,因此在以前DbContext中增長了配置,是否容許無條件刪除、更新數據。另一個重點也就是增長nolock支持,固然這個在生成sql的時候加上就行,很是簡單。ide
說到這裏其實只不過是個大概,這裏面的操做無非就是四種CRUD。我設計了四個Command來解決InsertCommand、UpdateCommand、SelectCommand、DeleteCommand,他們都繼承EntityCommand只要實現一個方法public abstract int Execute(IDbConnection conn,IDbTransaction tran);性能
到這裏其實InsertCommand、UpdateCommand、DeleteCommand實現都很是簡單,由於有了實現SelectCommand的基礎代碼,解析Expression就簡單的多了,能夠說解析Expression纔是整個的關鍵。this
解析的代碼沒有那麼多複雜的東西,我我的的原則就是儘可能簡單,不爲了追求設計而增長多餘的東西,雖然我對設計模式也很癡迷。
上圖就是解析的所有代碼,這其中代碼其實並不重要,重要的是解決的思路,這裏面有兩個重要的對象QueryExpressionClosure(查詢表達式閉包)、QueryCommnClosure(查詢通用閉包)。
咱們知道IQueryable其實就是expression tree,我記得之前很早的時候有人實現解析表達式的時候,是一邊解析一邊生產sql,這樣的作法很是不科學,會形成不少沒必要要的sql閉包,
一個簡單的查詢:好比q.Where(c=>c.Age>20).OrderBy(c=>c.Id);就會生成最少三個閉包大概sql是這樣 select xxx from (select * from (select xxx from tablex) as t1 where t1.Age>20) as t2 order by t2.Id。可是正常來講應該是select xxx from tablex where Age>20 order by Id。
因此正確的應該是先將表達式樹解析成表達式閉包,在將表達式閉包解析成sql。
如何將表達式解析成表達式閉包?
稍微分析一下就能夠看出,當遇到take、SKIP、sum、max、min、average、any、contains、distinct、first、firstordefault、longcount、count的時候就是另一個sql閉包了,
大概代碼以下:
protected override Expression VisitMethodCall(MethodCallExpression m) { var methodName = m.Method.Name.ToLower(); if (this.CurrQueryExpressionClosure == null && this.QueryExpressionClosures.Count == 0 && Utility.ToListMethods.Contains(methodName)) { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.CurrQueryExpressionClosure.MethodName = "tolist"; this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); } switch (methodName) { case "orderbydescending": case "orderby": case "thenby": case "thenbydescending": { if (!Utility.IgnoreOrderByMethods.Contains(this.CurrQueryExpressionClosure.MethodName)) { this.CurrQueryExpressionClosure.OrderByExpressions.Add(m); } break; } case "groupby": { this.CurrQueryExpressionClosure.GroupByExpressions.Add(m.Arguments[1]); break; } case "where": { var l = ((m.Arguments[1] as UnaryExpression).Operand as LambdaExpression); this.CurrQueryExpressionClosure.WhereExpressions.Add(l); break; } case "take": { if (this.CurrQueryExpressionClosure.MethodName != "skip") { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); this.CurrQueryExpressionClosure.MethodName = methodName; } this.CurrQueryExpressionClosure.Take = System.Convert.ToInt32((m.Arguments[1] as ConstantExpression).Value); break; } case "skip": { if (this.CurrQueryExpressionClosure.MethodName != "take") { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); this.CurrQueryExpressionClosure.MethodName = methodName; } this.CurrQueryExpressionClosure.Skip += System.Convert.ToInt32((m.Arguments[1] as ConstantExpression).Value); break; } case "sum": case "max": case "min": case "average": { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.CurrQueryExpressionClosure.MethodName = methodName; this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); if (m.Arguments.Count > 1) { this.CurrQueryExpressionClosure.EvalNumericExpression = m.Arguments[1]; } break; } case "any": { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.CurrQueryExpressionClosure.MethodName = methodName; this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); if (m.Arguments.Count > 1) { var l = ((m.Arguments[1] as UnaryExpression).Operand as LambdaExpression); this.CurrQueryExpressionClosure.WhereExpressions.Add(l); } break; } case "contains": { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.CurrQueryExpressionClosure.MethodName = methodName; this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); if (m.Arguments.Count > 1) { this.CurrQueryExpressionClosure.WhereExpressions.Add(m.Arguments[1]); } break; } case "distinct": { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.CurrQueryExpressionClosure.MethodName = methodName; this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); break; } case "select": { var lambdaExp = ((m.Arguments[1] as UnaryExpression).Operand as LambdaExpression); this.CurrQueryExpressionClosure.QuerySelectExpressions.Add(lambdaExp); break; } case "first": case "firstordefault": { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.CurrQueryExpressionClosure.MethodName = methodName; this.CurrQueryExpressionClosure.Take = 1; this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); if (m.Arguments.Count > 1) { this.CurrQueryExpressionClosure.WhereExpressions.Add(m.Arguments[1]); } break; } case "longcount": case "count": { this.CurrQueryExpressionClosure = new QueryExpressionClosure(); this.CurrQueryExpressionClosure.MethodName = methodName; this.QueryExpressionClosures.Add(this.CurrQueryExpressionClosure); if (m.Arguments.Count > 1) { this.CurrQueryExpressionClosure.WhereExpressions.Add(m.Arguments[1]); } break; } } return base.VisitMethodCall(m); }
有了表達式閉包以後,這個時候理解起來就清晰多了,就能夠經過一個ParserContext梳理一遍表達式閉包,生成一個通用閉包,而且獲得須要的信息。
通用閉包大概:
public class QueryCommnClosure { public int Take { get; set; } public int Skip { get; set; } public bool NoLock { get; set; } public string MethodName { get; set; } public string TableName { get; set; } public List<string> SelectColumns { get; set; } //public List<SelectTypeConstructor> SelectTypes { get; set; } public Dictionary<string, string> OrderBys { get; set; } public List<string> GroupBys { get; set; } public List<string> WhereSQLs { get; set; } public string EvalNumericSQL { get; set; } public string TableAlias { get; set; } public QueryCommnClosure() { this.SelectColumns = new List<string>(); //this.SelectTypes = new List<SelectTypeConstructor>(); this.OrderBys = new Dictionary<string, string>(); this.GroupBys = new List<string>(); this.WhereSQLs = new List<string>(); } public void Generate(ParserContext context) { ...篇幅限制省略 } }
另一個元數據,其實這個很是簡單,我爲了靈活,支持解析EF的edmx(msl、csdl)、Attribute(鬆散靈活的,實體上能夠加Attribute,也能夠不加)兩種。有了這個元數據就能夠作到實體、表的映射。
人快30了,成家卻未能立業,作了一年多的項目由於省領導政策的緣由失敗,說實話幹這個行當不知道對不對,多是有着一張不老的臉,在別人眼裏,都覺得是才2四、5歲,對我也是不夠信任,可是實際幹起來別人才知道我實力如何,但老闆不知道。老是乾的最多,拿的只能算個通常,呵呵...。
昆明有看上俺的能夠聯繫下我,求出路,目前公司也不是說要倒閉什麼的,其實也很穩定,可是這個項目完完了,另外一個穩定gps是其餘人作的,感受在公司已經多餘了,工資也不是看漲的樣子,畢竟要買房,養家餬口。