码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-04-04 22:35:13      阅读:182      评论:0      收藏:0      [点我收藏+]

标签:

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

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

标签:

原文地址:http://www.cnblogs.com/fireasy/p/4393013.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!