EFCore扩展Select方法(根据实体定制查询语句)

摘要:
当EFCore扩展Select方法时,查询返回的字段通常与我们在操作数据库时定义的实体不一致。因此,为UI或界面层创建大量模型并手动对应字段是非常繁琐的。本文将通过表达式树解决这些重复的过程。首先,粘贴实现代码。Queryable类中的扩展方法Select<TSource,TResult>需要参数Expression<Func<TSource,TResult>选择器。只要构建了相应的表达式树,用户定义的映射就会使用System。可以实施集合;使用System.ComponentModel.DataAnnotations。模式;使用System.Linq。表达;使用系统。反射使用staticSystem.Linq.Expressions。表示publicstaticclassQueryableExtetions{publicstaticcQueryable选择˂TTarget>{returnQueryable.Select;}publicstaticQueryable选择{returnQueryable.Select;}publicstaticExpression˂Func˃GetLamda{varsourceType=typeof;vartargetType=typeof;varparameter=Parameter;ExpressionpropertyParameter;if(type!=null)memberBindings.Add;continue;}//如果(sourceItem==null||!sourceItem.CanRead)continue//如果(sourceItems.GetCustomAttribute()=null)continue,则判断实体的读/写权限;varsourceProperty=属性;//如果(!

EFCore扩展Select方法(根据实体定制查询语句) 

通常用操作数据库的时候查询返回的字段是跟 我们的定义的实体是不一致的,所以往往针对UI或者接口层创建大量的Model, 而且需要手动对应字段,非常繁琐。 本文将通过表达式树解决这些重复的过程。 

先贴上实现代码

  Queryable 类中 的扩展方法  Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)  需要参数 Expression<Func<TSource, TResult>> selector 只要构造相应的表达式树即可实现自定义映射

    using System.Collections;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq.Expressions;
    using System.Reflection;
    using static System.Linq.Expressions.Expression;
    public static class QueryableExtentions
    {
        public static IQueryable<TTarget> Select<TTarget>(this IQueryable<object> query)
        {
            return Queryable.Select(query, GetLamda<object, TTarget>(query.GetType().GetGenericArguments()[0]));
        }

        public static IQueryable<TTarget> Select<TSource, TTarget>(this IQueryable<TSource> query)
        {
            return Queryable.Select(query, GetLamda<TSource, TTarget>());
        }

        public static Expression<Func<TSource, TTarget>> GetLamda<TSource, TTarget>(Type type = null)
        {
            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);
            var parameter = Parameter(sourceType);
            Expression propertyParameter;
            if (type != null)
            {
                propertyParameter = Convert(parameter, type);
                sourceType = type;
            }
            else
                propertyParameter = parameter;

            return Lambda<Func<TSource, TTarget>>(GetExpression(propertyParameter, sourceType, targetType), parameter);
        }

        public static MemberInitExpression GetExpression(Expression parameter, Type sourceType, Type targetType)
        {
            var memberBindings = new List<MemberBinding>();
            foreach (var targetItem in targetType.GetProperties().Where(x => x.CanWrite))
            {
                var fromEntityAttr = targetItem.GetCustomAttribute<FromEntityAttribute>();
                if (fromEntityAttr != null)
                {
                    var property = GetFromEntityExpression(parameter, sourceType, fromEntityAttr);
                    if (property != null)
                        memberBindings.Add(Bind(targetItem, property));
                    continue;
                }

                var sourceItem = sourceType.GetProperty(targetItem.Name);
                if (sourceItem == null)//当没有对应的属性时,查找 实体名+属性
                {
                    var complexSourceItemProperty = GetCombinationExpression(parameter, sourceType, targetItem);
                    if (complexSourceItemProperty != null)
                        memberBindings.Add(Bind(targetItem, complexSourceItemProperty));
                    continue;
                }

                //判断实体的读写权限
                if (sourceItem == null || !sourceItem.CanRead)
                    continue;

                //标注NotMapped特性的属性忽略转换
                if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null)
                    continue;

                var sourceProperty = Property(parameter, sourceItem);

                //当非值类型且类型不相同时
                if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType && targetItem.PropertyType != targetType)
                {
                    //判断都是(非泛型、非数组)class
                    if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass
                        && !sourceItem.PropertyType.IsArray && !targetItem.PropertyType.IsArray
                        && !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                    {
                        var expression = GetExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        memberBindings.Add(Bind(targetItem, expression));
                    }
                    continue;
                }

                if (targetItem.PropertyType != sourceItem.PropertyType)
                    continue;

                memberBindings.Add(Bind(targetItem, sourceProperty));
            }

            return MemberInit(New(targetType), memberBindings);
        }

        /// <summary>
        /// 根据FromEntityAttribute 的值获取属性对应的路径
        /// </summary>
        /// <param name="sourceProperty"></param>
        /// <param name="sourceType"></param>
        /// <param name="fromEntityAttribute"></param>
        /// <returns></returns>
        private static Expression GetFromEntityExpression(Expression sourceProperty, Type sourceType, FromEntityAttribute fromEntityAttribute)
        {
            var findType = sourceType;
            var resultProperty = sourceProperty;
            var tableNames = fromEntityAttribute.EntityNames;
            if (tableNames == null)
            {
                var columnProperty = findType.GetProperty(fromEntityAttribute.EntityColuum);
                if (columnProperty == null)
                    return null;
                else
                    return Property(resultProperty, columnProperty);
            }

            for (int i = tableNames.Length - 1; i >= 0; i--)
            {
                var tableProperty = findType.GetProperty(tableNames[i]);
                if (tableProperty == null)
                    return null;

                findType = tableProperty.PropertyType;
                resultProperty = Property(resultProperty, tableProperty);
            }

            var property = findType.GetProperty(fromEntityAttribute.EntityColuum);
            if (property == null)
                return null;
            else
                return Property(resultProperty, property);
        }

        /// <summary>
        /// 根据组合字段获取其属性路径
        /// </summary>
        /// <param name="sourceProperty"></param>
        /// <param name="sourcePropertys"></param>
        /// <param name="targetItem"></param>
        /// <returns></returns>
        private static Expression GetCombinationExpression(Expression sourceProperty, Type sourceType, PropertyInfo targetItem)
        {
            foreach (var item in sourceType.GetProperties().Where(x => x.CanRead))
            {
                if (targetItem.Name.StartsWith(item.Name))
                {
                    if (item != null && item.CanRead && item.PropertyType.IsClass && !item.PropertyType.IsGenericType)
                    {
                        var rightName = targetItem.Name.Substring(item.Name.Length);

                        var complexSourceItem = item.PropertyType.GetProperty(rightName);
                        if (complexSourceItem != null && complexSourceItem.CanRead)
                            return Property(Property(sourceProperty, item), complexSourceItem);
                    }
                }
            }

            return null;
        }
    }

    /// <summary>
    /// 用于标注字段 来自哪个表的的哪一列(仅限于有关联的表中)
    /// </summary>
    public class FromEntityAttribute : Attribute
    {
        /// <summary>
        /// 类名(表名)
        /// </summary>
        public string[] EntityNames { get; }

        /// <summary>
        /// 字段(列名)
        /// </summary>
        public string EntityColuum { get; }

        /// <summary>
        /// 列名 + 该列的表名 + 该列的表的上一级表名
        /// </summary>
        /// <param name="entityColuum"></param>
        /// <param name="entityNames"></param>
        public FromEntityAttribute(string entityColuum, params string[] entityNames)
        {
            EntityNames = entityNames;
            EntityColuum = entityColuum;
        }
    }

 

调用方法如下,先构造测试类

    public partial class User
    {
        public int Id { get; set; }
        [Required]
        [StringLength(50)]
        public string Name { get; set; }
        public int RoleId { get; set; }

        [ForeignKey(nameof(RoleId))]
        public virtual Role Role { get; set; }
    }
    
    public partial class Role
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int DepartmentId { get; set; }

        [ForeignKey(nameof(DepartmentId))]
        public virtual Department Department  { get; set; } 
    }

    public partial class Department
    {
        public int Id { get; set; }
        [Required]
        [StringLength(50)]
        public string Name { get; set; }
    }

如上所以构造了,用户表,角色表,和部门表。  查询某个用户 的角色名和部门名 则需要关联 角色表和部门表

    public partial class UserModel
    {
        public string Name { get; set; }

        public string RoleName { get; set; }

        //[FromEntity("Name","Role")]
        //public string RoleName1 { get; set; }

        [FromEntity("Name", "Department", "Role")]
        public string DepartmentName { get; set; }

        //public virtual RoleModel Role { get; set; }

        //[FromEntity("Department", "Role")]
        //public virtual Department Department { get; set; }
    }

查询代码如下

static void Main(string[] args)
        {
            using (var context = new TestContext())
            {
                var list = context.User.Select<UserModel>().ToList();
            }
            Console.WriteLine($"------------结束--------------------");
            Console.ReadLine();
        }

生成的sql语句 如下图

EFCore扩展Select方法(根据实体定制查询语句)第1张

实体中的 DepartmentName 由于通过用户表关联角色表,再通过角色表关联 部门表得到故 需要通过特性标注

当然结果实体也可以多级关联

    public partial class UserModel
    {
        public string Name { get; set; }

        public string RoleName { get; set; }

        [FromEntity("Name","Role")]
        public string RoleName1 { get; set; }

        [FromEntity("Name", "Department", "Role")]
        public string DepartmentName { get; set; }

        public virtual RoleModel Role { get; set; }

        [FromEntity("Department", "Role")]
        public virtual Department Department { get; set; }
    }
    public partial class RoleModel
    {
        public string Name { get; set; }
        public string DepartmentName { get; set; }

        public virtual DepartmentModel Department  { get; set; } 
    }
    public partial class DepartmentModel
    {
        public string Name { get; set; }
    }

生成的查询语句如下图

EFCore扩展Select方法(根据实体定制查询语句)第2张

 

总结 此方案用在接口,精确查询字段,需要强类型视图的地方相对比较方便

作者:costyuan

GitHub地址:https://github.com/bieyuan/EFCoreSelectExtentions

地址:https://www.cnblogs.com/castyuan/p/10186619.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 
如果文中有什么错误,欢迎指出,谢谢! 

免责声明:文章转载自《EFCore扩展Select方法(根据实体定制查询语句)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Windows单击右键没有共享选项怎么办一个简单的判断文件是否存在的WIN API函数下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

SpringBoot + CXF快速实现SOAP WebService(支持Basic Auth)

唠叨两句 讲真,SOAP跟现在流行的RESTful WebService比起来显得很难用。冗余的XML文本信息太多,可读性差,它的请求信息有时很难手动构造,不太好调试。不过说归说,对某些企业用户来说SOAP的使用率仍然是很高的。 需求背景 接手维护的一个项目,最近客户想开放项目中的功能给第三方调用,而且接入方指定必须是SOAP接口。这项目原来的代码我看着头...

C# 拼接字符串的几种方式和性能

开发过程中常用到的拼接字符串的方法有三种:   1 简单 “+=” 拼接法      1 2 3 4 5 string str="a";   str+="c"+"d";   string str_1="a"+"b";      首先需要明白的是string类型,string是引用类型,保留在堆上,而不是栈上,用的时候传的是内存中的地...

[Jimmy原创] 在.NET环境下操作MySQL数据库

        由于工作需要,最近需要在我的一个.NET程序里面对MySQL数据库进行操作,最简单的方法便是用ODBC连接,这是一个通用性很强的方法,但是这样做也有其缺点,就是软件部署的时候客户机也需要安装MySQL的ODBC驱动,以及做一些设定工作,这样对一般用户来说是比较困难的,就算你提供详尽的安装指南。那么能不能像我们在.NET环境里面使用ADO.N...

C# 通过反射实现复杂对象的深拷贝(附源码)

背景   在C#中我们很多时候需要对一个对象进行深拷贝,当然如果已知当前对象类型的时候我们当然可以通过创建新对象逐一进行赋值的方式来进行操作,但是这种操作非常繁琐而且如果你在做一个顶层框架的时候要实现这样一个功能,并且深拷贝的方式复制的对象是一个object类型,这个时候这个方式就不再适用了,可能还有很多说可以通过序列化和反序列化的方式进行对象的深拷贝但还...

SQL 存储过程入门(变量)

SQL 存储过程入门(变量)(二)上一篇我们讲到了SQL存储过程的基本定义,怎么创建,使用,这篇就来讲一下变量的使用。 变量分文局部变量和全局变量 局部变量是@开头,全局变量是@@开头,这里我们主要讲局部变量,全局变量我们后面再讲。 在c# 语言中,定义一个变量很简单,例如 int i=0; --定义加赋值。 在sql中,定义一个变量需要关键字DECLAR...

curator框架的使用以及实现分布式锁等应用与zkclient操作zookeeper,简化复杂原生API

打开zookeeper集群 先体会一下原生API有多麻烦(可略过): //地址 static final String ADDR = "192.168.171.128:2181,192.168.171.129:2181,192.168.171.130:2181"; //session超时时间 static final int...