你的位置:首页 > ASP.net教程

[ASP.net教程]细说 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 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这样一个属性。