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

时间: 2024-08-06 00:19:13

细说 Fireasy Entity Linq解析的几个独创之处的相关文章

[读书笔记]C#学习笔记六: C#3.0Lambda表达式及Linq解析

前言 最早使用到Lambda表达式是因为一个需求:如果一个数组是:int[] s = new int[]{1,3,5,9,14,16,22};例如只想要这个数组中小于15的元素然后重新组装成一个数组或者直接让s返回一个新数组该怎么截取? 最开始的想法就是将这个s遍历一遍然后判断下再来重新组装成新的数组.好麻烦是不是? 于是便百度到了一个叫做Lambda的东西, 所以用了之后效果如下: 1 class Program 2 { 3 static void Main(string[] args) 4

C#学习笔记五: C#3.0Lambda表达式及Linq解析

最早使用到Lambda表达式是因为一个需求:如果一个数组是:int[] s = new int[]{1,3,5,9,14,16,22};例如只想要这个数组中小于15的元素然后重新组装成一个数组或者直接让s返回一个新数组该怎么截取? 最开始的想法就是将这个s遍历一遍然后判断下再来重新组装成新的数组.好麻烦是不是? 于是便百度到了一个叫做Lambda的东西, 所以用了之后效果如下: 1 class Program 2 { 3 static void Main(string[] args) 4 { 5

linq 解析 带命名空间的xml

前言:xml的操作方式有多种,但要论使用频繁程度,博主用得最多的还是Linq to xml的方式,觉得它使用起来很方便,就用那么几个方法就能完成简单xml的读写.之前做的一个项目有一个很变态的需求:C#项目调用不知道是什么语言写的一个WebService,然后添加服务引用总是失败,通过代理的方式动态调用也总是报错,最后没办法,通过发送原始的WebRequest请求直接得到对方返回的一个xml文件.注意过webservice的wsdl文件的朋友应该知道这个是系统生成的xml文件,有点复杂,研究了半

.NET深入解析LINQ框架(六:LINQ执行表达式)

阅读目录: 1.LINQ执行表达式 在看本篇文章之前我假设您已经具备我之前分析的一些原理知识,因为这章所要讲的内容是建立在之前的一系列知识点之上的,为了保证您的阅读顺利建议您先阅读本人的LINQ系列文章的前几篇或者您已经具备比较深入的LINQ原理知识体系,防止耽误您的宝贵时间. 到目前为止我们对LINQ的执行原理已经很清楚了,从它的前期构想到它真正为我们所用都有足够的证据,但是似乎问题并没有我们想的那么简单,问题总是在我们使用中频频出现尤其是新技术的使用,当然有问题才能有进步. 一:LINQ执行

Fireasy版本发布 1.5.40.42030

开发指南 代码生成 1.5.40.42030  2015-4-1 ** Fireasy.Common1.完善To方法,可以对可枚举类型进行转换2.完善Json序列化对动态类型的支持 ** Fireasy.Data3.增加Update方法的另一个版本 ** Fireasy.Data.Entity4.增强Linq扩展方法Order和ThenBy5.实体增加All扩展方法,可以简便返回所有属性6.仓储增加Include.Associate和Batch方法,EntityContext增加Apply方法7

通过实例学习Fireasy开发(上篇)

Fireasy一直在发布新版本,但是怎么用,到底好不好用,估计大家都有这样的疑惑.所以,今天花点时间通过一个简单的示例一步一步来介绍fireasy的用法. 首先有必要介绍一下Fireasy的组件构成: Fireasy.Common 公共组件库,主要包含缓存管理.日志管理.序列化.动态编译.扩展方法等. Fireasy.Data 数据库组件库,提供数据库的操作,语法.批量插入.数据库构架等. Fireasy.Data.Entity 实体组件库,ORMapper.LINQ解析.数据上下文.树实体持久

UWP开发之ORM实践:如何使用Entity Framework Core做SQLite数据持久层?

选择SQLite的理由 在做UWP开发的时候我们首选的本地数据库一般都是Sqlite,我以前也不知道为啥?后来仔细研究了一下也是有原因的: 1,微软做的UWP应用大部分也是用Sqlite.或者说是微软推荐使用Sqlite吧! 2,简单!就只有一个类库没有多余的参照什么的.不像其他数据库还得做复杂配置什么的麻烦! 3,不需要数据库服务,数据服务和客户都在同一个进程里面.如下图: 4,作为存储系统它只支持一个用户一个数据实体. 5,跨平台跨结构,这个好! Sqlite主要使用内容 如果想充分使用好S

20141129 LinQ

ORMO-Object对象R-Relation关系M-Mapping映射 对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换.从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”. O -- M -- R 表名-类名列名-属性名表的关系-类的成员对象 LinQ 集成化查询语言 SQL-结构化查询语言 LINQ,语言集成查询(Languag

Linq Mysql GroupBy语句的问题处理

语句如下: var resumeList = db.ChannelResume.Where(model); var groupValues = resumeList.GroupBy(t => new {t.AgentId, t.AgentName}); var statistics = groupValues.Select(c => new ResumeStatisticsViewModel() { Date = dateSpan, AgentId = c.Key.AgentId, Agent