细说 Fireasy Entity Linq解析的几个独创之处_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > 细说 Fireasy Entity Linq解析的几个独创之处

细说 Fireasy Entity Linq解析的几个独创之处

 2015/4/4 22:47:36  fireasy  程序员俱乐部  我要评论(0)
  • 摘要:FireasyEntity的linq内核解析是参考自iqtoolkit源码的,作者熟读源码并吸收其博大精深的思想后,结合项目中的一些需求,对它进行了几处改进。一、逻辑删除标记做管理系统的开发者可能会习惯于对数据做逻辑删除处理,即将数据打这一个标记,查询的时候将这些数据过滤掉。在FireasyEntity的元数据定义PropertyMapInfo类中,有IsDeletedKey这样一个属性,表示使用此列作为逻辑删除标记。对应的,在查询数据的时候,需要把这个标记拼到LINQ里去
  • 标签:解析

     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这样一个属性。

发表评论
用户名: 匿名