實現本身的Linq to Sql

以前寫過一篇《統一的倉儲接口》,爲了方便使用不一樣的倉儲。在咱們的項目中使用的是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是其餘人作的,感受在公司已經多餘了,工資也不是看漲的樣子,畢竟要買房,養家餬口。

相關文章
相關標籤/搜索