首页 > 学院 > 开发设计 > 正文

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

2019-11-17 02:28:10
字体:
来源:转载
供稿:网友

细说 Fireasy Entity 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 Express
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表