程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 細說 Fireasy Entity Linq解析的幾個獨創之處,fireasylinq

細說 Fireasy Entity Linq解析的幾個獨創之處,fireasylinq

編輯:C#入門知識

細說 Fireasy Entity Linq解析的幾個獨創之處,fireasylinq


     Fireasy Entity的linq內核解析是參考自iqtoolkit源碼的,作者熟讀源碼並吸收其博大精深的思想後,結合項目中的一些需求,對它進行了幾處改進。

 

     一、邏輯刪除標記

     做管理系統的開發者可能會習慣於對數據做邏輯刪除處理,即將數據打這一個標記,查詢的時候將這些數據過濾掉。在Fireasy Entity的元數據定義PropertyMapInfo類中,有IsDeletedKey這樣一個屬性,表示使用此列作為邏輯刪除標記。對應的,在查詢數據的時候,需要把這個標記拼到LINQ裡去,不然每次都要這樣的條件,多麻煩:

var list = db.Customers.Where(c => c.DelFlag == 0);

     我的做法是,定義一個FakeDeleteFlagRewriter類,對表達式進行重寫,將具有IsDeletedKey屬性的字段等於0的條件加進去。

namespace Fireasy.Data.Entity.Linq.Translators
{
    /// <summary>
    /// 用於為具有假刪除標記的查詢表達式添加標記條件。
    /// </summary>
    public class FakeDeleteFlagRewriter : DbExpressionVisitor
    {
        private ColumnExpression fakeColumn;

        public static Expression Rewrite(Expression expression)
        {
            return new FakeDeleteFlagRewriter().Visit(expression);
        }

        /// <summary>
        /// 訪問 <see cref="SelectExpression"/>。
        /// </summary>
        /// <param name="select">要訪問的表達式。</param>
        /// <returns></returns>
        protected override Expression VisitSelect(SelectExpression select)
        {
            if (select.From != null && select.From.NodeType == (ExpressionType)DbExpressionType.Table)
            {
                var table = (TableExpression)select.From;
                //首先要找到具有假刪除標記的列表達式
                foreach (var column in select.Columns)
                {
                    base.Visit(column.Expression);
                }

                if (fakeColumn != null && fakeColumn.Alias.Equals(table.Alias))
                {
                    var where = select.Where;

                    var condExp = fakeColumn.Equal(Expression.Constant(0.ToType(fakeColumn.Type)));
                    return select.Update(select.From,
                        where != null ? Expression.And(where, condExp) : condExp,
                        select.OrderBy, select.GroupBy, select.Skip, select.Take,
                        select.Segment, select.IsDistinct, select.Columns, select.IsReverse);
                }
            }
            else if (select.From != null)
            {
                var from = base.Visit(select.From);
                return select.Update(from, select.Where, select.OrderBy, select.GroupBy, select.Skip, select.Take,
                        select.Segment, select.IsDistinct, select.Columns, select.IsReverse);
            }

            return select;
        }

        /// <summary>
        /// 訪問 <see cref="ColumnExpression"/>。
        /// </summary>
        /// <param name="column">要訪問的表達式。</param>
        /// <returns></returns>
        protected override Expression VisitColumn(ColumnExpression column)
        {
            //記錄下具有假刪除標記的列表達式。
            if (fakeColumn == null && column.MapInfo != null && column.MapInfo.IsDeletedKey)
            {
                fakeColumn = column;
            }

            return column;
        }
    }
}

      這樣,在使用查詢的時候,再不也需要考慮有沒有忘記寫DelFlag == 0了,是不是很方便。

 

      二、對Join表達式中一端具有Group謂詞的改進

      某天,發現一個有趣的問題,我需要join一個先被Group後的序列,在join的on條件中,使用到了IGrouping<,>中的Key屬性,問題來了,Key不能被識別,怎麼辦?

            using (var context = new DbContext())
            {
                var group = context.ZjPayments
                    .GroupBy(s => s.GcConId)
                    .Select(s => new
                        {
                            s.Key,
                            PaymentMoney = s.Sum(o => o.PaymentMoney),
                            PaymentCount = s.Count()
                        });

                var list = context.ConLists
                    .Where(s => s.ItemId == itemId)
                    .Segment(pager)
                    .OrderBy(sorting, u => u.OrderBy(t => t.SignDate))
                    .Join(group.DefaultIfEmpty(), s => s.Id, s => s.Key, (s, t) => new
                        {
                            s.Id,
                            s.SectId,
                            s.SectName,
                            s.ConName,
                            s.ConMoney,
                            t.PaymentCount,
                            t.PaymentMoney
                        });
            }

      其實不難發現,只要將Key替換成Group語句中的KeySelector就可以了,並且還要考慮keySelector為匿名對象的情況。

      一樣的,定義一個GroupKeyReplacer類,對表達式進行重寫。

namespace Fireasy.Data.Entity.Linq.Translators
{
    /// <summary>
    /// 如果 <see cref="JoinExpression"/> 表達式中的一邊具有 Group 子表,則需要將連接條件中的 Key 表達式替換為相應的 <see cref="ColumnExpression"/> 對象。
    /// </summary>
    public class GroupKeyReplacer : DbExpressionVisitor
    {
        private MemberInfo member = null;
        private Expression finder = null;

        public static Expression Replace(Expression expression)
        {
            var replacer = new GroupKeyReplacer();
            replacer.Visit(expression);
            return replacer.finder ?? expression;
        }

        protected override Expression VisitMember(MemberExpression memberExp)
        {
            if (member == null)
            {
                member = memberExp.Member;
            }

            Visit(memberExp.Expression);
            return memberExp;
        }

        protected override Expression VisitNew(NewExpression newExp)
        {
            if (newExp.Type.IsGenericType &&
               newExp.Type.GetGenericTypeDefinition() == typeof(Grouping<,>))
            {
                Visit(newExp.Arguments[0]);
                return newExp;
            }

            return base.VisitNew(newExp);
        }

        protected override Expression VisitColumn(ColumnExpression column)
        {
            if (member != null && (member.Name == "Key" || member.Name == column.Name))
            {
                finder = column;
            }

            return column;
        }
    }
}

      在QueryBinder類中找到BindJoin方法,修改原來的代碼,應用GroupKeyReplacer重寫表達式:

            var outerKeyExpr = GroupKeyReplacer.Replace(Visit(outerKey.Body));
            var innerKeyExpr = GroupKeyReplacer.Replace(Visit(innerKey.Body));

 

      三、實體All的擴展

      相信大家在使用Select謂詞的時候都有這樣的感觸,如果要加一個屬性返回,是不是要把實體類的大部分屬性全列出來,實體屬性少點還好,太多了會不會漏了某一個屬性。

        [TestMethod]
        public void TestAllColumns()
        {
            var list = db.Orders.Select(s => new
                {
                    s.CustomerID,
                    s.EmployeeID,
                    s.OrderID,
                    s.OrderDate,
                    s.Freight,
                    ShortName = s.Customers.CompanyName.Substring(0, 1)
                }).ToList();
        }

      這只是一個簡單的示例,現實業務中,這個實體要返回的屬性可能不止這幾個,我一直在想,如果可以把這麼繁瑣的工作省略去那該多好。其實仔細的想一想,那也容易辦到。

      對IEntity(所有的實體類型都實現了IEntity)擴展一個All方法:

        /// <summary>
        /// 返回實體的所有屬性,以及 <paramref name="selector"/> 表達式中的字段。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        public static dynamic All(this IEntity entity, Expression<Func<object, object>> selector)
        {
            return null;
        }

      在QueryBinder類裡增加一個BindAllFields方法,如下: 

        public Expression BindAllFields(Expression source, LambdaExpression selector)
        {
            if (selector.Body.NodeType != ExpressionType.New)
            {
                throw new ArgumentException(SR.GetString(SRKind.MustBeNewExpression));
            }

            var newExp = (NewExpression)Visit(selector.Body);
            var arguments = newExp.Arguments.ToList();
            var members = newExp.Members.ToList();

            foreach (var property in PropertyUnity.GetPersistentProperties(source.Type))
            {
                var columnExp = new ColumnExpression(property.Type, __alias, property.Name, property.Info);
                arguments.Add(columnExp);
                members.Add(property.Info.ReflectionInfo);
            }

            var keyPairArray = new Expression[members.Count];
            var constructor = typeof(KeyValuePair<string, object>).GetConstructors()[0];
            for (var i = 0; i < members.Count; i++ )
            {
                keyPairArray[i] = Expression.New(constructor, Expression.Constant(members[i].Name), Expression.Convert(arguments[i], typeof(object)));
            }

            var arrayExp = Expression.NewArrayInit(typeof(KeyValuePair<string, object>), keyPairArray);

            return Expression.Convert(arrayExp, typeof(DynamicExpandoObject));
        }

      這樣,剛剛的查詢語句可以寫成這樣的了,省掉了多少工作:

        [TestMethod]
        public void TestAllColumns()
        {
            var list = db.Orders.Select(s => s.All(t => new
                {
                    ShortName = s.Customers.CompanyName.Substring(0, 1)
                }));
        }

      不過請注意,使用All方法後,對象就變成dynamic類型了,序列元素既包含了Order的所有屬性,還包含ShortName這樣一個屬性。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved