草庐IT

C# 表达式树实现匿名类型到列表返回模型的自动映射

嬴の政 2023-03-28 原文

前言:

在我们的业务中,展示列表时经常会联表查询,比如说我们有学生表和班级表,表结构如下:包含了学生表、班级表以及列表返回模型

        /// <summary>
        /// 学生表
        /// </summary>
        public class StudentInfo 
        {
            /// <summary>
            /// 标识
            /// </summary>
            public Guid Id { get; set; }

            /// <summary>
            /// 学号
            /// </summary>
            public string Number { get; set; }

            /// <summary>
            /// 姓名
            /// </summary>
            public string Name { get; set; }

            /// <summary>
            /// 班级标识
            /// </summary>
            public Guid ClassId { get; set; }
        }

        /// <summary>
        /// 班级表
        /// </summary>
        public class ClassInfo
        {
            /// <summary>
            /// 标识
            /// </summary>
            public Guid Id { get; set; }


            /// <summary>
            /// 班级总人数
            /// </summary>
            public int TotalNumber { get; set; }
        }

        /// <summary>
        /// 列表返回模型
        /// </summary>
        public class ClassStudentModel 
        {
            /// <summary>
            /// 学生标识
            /// </summary>
            public Guid Id { get; set; }

            /// <summary>
            /// 学号
            /// </summary>
            public string Number { get; set; }

            /// <summary>
            /// 学生名称
            /// </summary>
            public string Name { get; set; }

            /// <summary>
            /// 班级总人数
            /// </summary>
            public int TotalNumber { get; set; }
        }

 

 

   普通的查询语句,例子中的返回模型字段并不多,所以写4个字段也不费劲,但如果是十几二十个那就挺麻烦的了,如下:

 

    var query = from s in dbStore.Set<StudentInfo>()
                       from c in dbStore.Set<ClassInfo>().Where(c => c.Id == s.ClassId).DefaultIfEmpty()
                       select new { s, c };

    // 一般一些业务中应该会有更多的查询条件,这里就省略了
            // 比如: query = query.Where(e => e.s.Name == options.Name);
            var result = query.Select(e => new ClassStudentModel 
            {
                Id = e.s.Id,
                Number = e.s.Number,
                Name = e.s.Name,
                TotalNumber = e.c.TotalNumber
            }).ToList();

 

  

 

  所以我想有没有什么方法能够让这个匿名类型中的字段自动映射到列表的返回模型中呢?

正文:

  使用表达式树+反射可以实现此需求,通过反射将各模型中的字段名与列表返回模型中的各字段进行对应,再利用表达式树进行拼接构造函数。有了这个思路之后呢,还有个问题,就是可能各模型中有很多相同的字段都可以和返回模型中的字段对应,比如说学生表里有Id,班级表中也有Id,那么到底是哪个Id和返回模型中的Id呢,为此,我又加了一个特性自动映射ParentAnonymousAttribute,通过此特性来判别取哪个模型中的Id,自动映射方法和特性代码如下:

    /// <summary>
    /// 父级匿名特性
    /// </summary>
    public class ParentAnonymousAttribute : Attribute
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public ParentAnonymousAttribute(string parentName)
        {
            ParentName = parentName;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentName"></param>
        /// <param name="name"></param>
        public ParentAnonymousAttribute(string parentName, string name)
        {
            ParentName = parentName;
            Name = name;
        }

        /// <summary>
        /// 父级匿名名称
        /// </summary>
        public string ParentName { get; set; }

        /// <summary>
        /// 属性名称
        /// </summary>
        public string Name { get; set; }
    }

  

        /// <summary>
        /// 自动select
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TGraph"></typeparam>
        /// <param name="query"></param>
        /// <param name="graph"></param>
        /// <returns></returns>
        public static IQueryable<TGraph> AutoSelect<T, TGraph>(this IQueryable<T> query, TGraph graph)
        {
            var defaultCtor = typeof(TGraph).GetConstructor(Type.EmptyTypes);
            if (defaultCtor == null)
            {
                throw new Exception();
            }
            var constructor = Expression.New(defaultCtor);
            var bindings = new List<MemberAssignment>();
            var tExp = Expression.Parameter(typeof(T), "t");
            var typeT = typeof(T);
            var tProperties = typeT.GetProperties();
            var graphProperties = typeof(TGraph).GetProperties().Where(e => e.SetMethod != null).ToList();
            var graphPointParentProps = graphProperties.Where(e => e.GetCustomAttribute<ParentAnonymousAttribute>() != null).ToList();
            var graphNoPointParentProps = graphProperties.Where(e => e.GetCustomAttribute<ParentAnonymousAttribute>() == null).ToList();
            foreach (var item in graphPointParentProps)
            {
                var attr = item.GetCustomAttribute<ParentAnonymousAttribute>();
                var pExp = Expression.Property(tExp, attr.ParentName);
                var realExp = Expression.Property(pExp, attr.Name ?? item.Name);
                bindings.Add(Expression.Bind(item, realExp));
            }
            foreach (var prop in tProperties)
            {
                var anoyClass = prop.PropertyType.GetProperties();
                foreach (var item in anoyClass)
                {
                    if (graphNoPointParentProps.Any(a => a.Name == item.Name))
                    {
                        var property = graphNoPointParentProps.Where(a => a.Name == item.Name).FirstOrDefault();
                        if (property.PropertyType != item.PropertyType)
                        {
                            continue;
                        }
                        if (bindings.Any(e => e.Member.Name == property.Name))
                        {
                            continue;
                        }
                        var pExp = Expression.Property(tExp, prop.Name);
                        var realExp = Expression.Property(pExp, item.Name);
                        bindings.Add(Expression.Bind(property, realExp));
                    }
                }
            }

            var init = Expression.MemberInit(constructor, bindings);
            var final = Expression.Lambda<Func<T, TGraph>>(init, tExp);
            return query.Select(final);
        }

 

  先给列表返回模型的字段加上特性:

    /// <summary>
    /// 列表返回模型
    /// </summary>
    public class ClassStudentModel
    {
        /// <summary>
        /// 学生标识
        /// </summary>
        [ParentAnonymous("s")]
        public Guid Id { get; set; }

        /// <summary>
        /// 学号
        /// </summary>
        public string Number { get; set; }

        /// <summary>
        /// 学生名称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 班级总人数
        /// </summary>
        public int TotalNumber { get; set; }
    }

 

  然后再改一下查询方法:

            var query = from s in dbStore.Set<StudentInfo>()
                        from c in dbStore.Set<ClassInfo>().Where(c => c.Id == s.ClassId).DefaultIfEmpty()
                        select new { s, c };

            var result = query.AutoSelect(new ClassStudentModel()).ToList();

  

 

至此,自动映射,大功告成

有关C# 表达式树实现匿名类型到列表返回模型的自动映射的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  5. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  6. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  7. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  8. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  9. ruby - RVM 使用列表[0] - 2

    是否有类似“RVMuse1”或“RVMuselist[0]”之类的内容而不是键入整个版本号。在任何时候,我们都会看到一个可能包含5个或更多ruby的列表,我们可以轻松地键入一个数字而不是X.X.X。这也有助于rvmgemset。 最佳答案 这在RVM2.0中是可能的=>https://docs.google.com/document/d/1xW9GeEpLOWPcddDg_hOPvK4oeLxJmU3Q5FiCNT7nTAc/edit?usp=sharing-知道链接的任何人都可以发表评论

  10. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

随机推荐