【问题标题】:Method that return a query from a query in argument从参数中的查询返回查询的方法
【发布时间】:2018-11-03 13:10:51
【问题描述】:

演示文稿

我有一个带有一些属性的ContactProfileModel 实体类:

  • 名字
  • 姓氏
  • 出生日期等。

我还有其他拥有ContactProfileModel 外键的实体。示例:RegistrationModel.Contact.

需要

我想创建一个带有以下签名的方法:

public static Expression<Func<TModel, string>> Contact<TModel>(Expression<Func<TModel, ContactProfileModel>> contact)

并以这种方式使用它:

DisplayQuery.Contact<RegistrationModel>(m => m.ContactProfile))

相当于

m => m.ContactProfile.FirstName + " " + m.ContactProfile.FirstName + " " + m.ContactProfile.BirthDate.ToShortTimeString()

目标

目标是返回一个 linq 查询,其中 result 是一个字符串,包含联系人的不同信息。示例:“John Doe (10/10/90)”

注意

我与一些人讨论过,他们告诉我使用Expression.CallExpression.Property,但不幸的是我没有足够的知识来正确使用它。

在这里我没有额外的细节就暴露了我的问题,但我有理由只以这种方式创建我的方法。

提前致谢。

【问题讨论】:

    标签: c# .net entity-framework linq reflection


    【解决方案1】:

    这是一个完整的工作实现:代码运行并输出您所期望的。

    我的时间有点短,所以我就这样吧。如果您想澄清,请在 cmets 中询问,我会尽力回答。

    public class Program
    {
        private static readonly MethodInfo stringConcatMethod = typeof(string).GetMethod("Concat", new[] { typeof(string[]) });
        private static readonly MethodInfo toShortTimeStringMethod = typeof(DateTime).GetMethod("ToShortTimeString");
        private static readonly PropertyInfo firstNameProperty = typeof(ContactProfileModel).GetProperty("FirstName");
        private static readonly PropertyInfo lastNameProperty = typeof(ContactProfileModel).GetProperty("LastName");
        private static readonly PropertyInfo birthDateProperty = typeof(ContactProfileModel).GetProperty("BirthDate");
    
        public static void Main()
        {
            var result = Contact<RegistrationModel>(x => x.ContactProfile);
    
            // Test it
            var model = new RegistrationModel()
            {
                ContactProfile = new ContactProfileModel()
                {
                    FirstName = "First",
                    LastName = "Last",
                    BirthDate = DateTime.Now,
                }
            };
            var str = result.Compile()(model);
        }
    
        public static Expression<Func<TModel, string>> Contact<TModel>(Expression<Func<TModel, ContactProfileModel>> contact)
        {
            // We've been given a LambdaExpression. It's got a single
            // parameter, which is the 'x' above, and its body
            // should be a MemberExpression accessing a property on
            // 'x' (you might want to check this and throw a suitable
            // exception if this isn't the case). We'll grab the
            // body of the LambdaExpression, and use that as the
            // 'm.ContactProfile' expression in your question. 
            // At the end, we'll construct a new LambdaExpression
            // with our body. We need to use the same ParameterExpression
            // given in this LambdaExpression.
            var modelParameter = contact.Parameters[0];
            var propertyAccess = (MemberExpression)contact.Body;
    
            // <contact>.FirstName
            var firstNameAccess = Expression.Property(propertyAccess, firstNameProperty);
            // <contact>.LastName
            var lastNameAccess = Expression.Property(propertyAccess, lastNameProperty);
            // <contact>.BirthDate
            var birthDateAccess = Expression.Property(propertyAccess, birthDateProperty);
            // <contact>.BirthDate.ToShortTimeString()
            var birthDateShortTimeStringCall = Expression.Call(birthDateAccess, toShortTimeStringMethod);
    
            // string.Concat(new string[] { <contact>.FirstName, " ", etc }
            var argsArray = Expression.NewArrayInit(typeof(string), new Expression[]
            {
                firstNameAccess,
                Expression.Constant(" "),
                lastNameAccess,
                Expression.Constant(" "),
                birthDateShortTimeStringCall
            });
            var concatCall = Expression.Call(stringConcatMethod, argsArray);
    
            // Turn it all into a LambdaExpression
            var result = Expression.Lambda<Func<TModel, string>>(concatCall, modelParameter);
            // Note: if you inspect 'result.DebugView' in a debugger at this 
            // point, you'll see a representation of the expression we've built
            // up above, which is useful for figuring out where things have gone
            // wrong.
            return result;
        }
    }
    
    public class ContactProfileModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
    }
    
    public class RegistrationModel
    {
        public ContactProfileModel ContactProfile { get; set; }
    }
    

    可能是 EF 不喜欢调用 String.Concat - 在这种情况下,您可能不得不使用一组 Expression.Add 来代替。

    【讨论】:

    • 感谢您提供了很好的解决方案。了解如何处理 Expression 模块以生成我需要的内容对我有很大帮助,我可以告诉你 Concat 在“LINQ to SQL”查询中被接受。
    【解决方案2】:

    StackOverflow 上的第一个答案,请善待 ;)

    我试图解决这个问题,但表达式并不容易使用。感谢canton7的回答。如果您想在表达式中使用 .ToString() 方法,我编辑了我的答案以显示解决方案。

    public class ContactProfileModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
    
        public override string ToString()
        {
            return $"{FirstName} {LastName} {BirthDate.ToShortDateString()}";
        }
    }
    
    public class RegistrationModel
    {
        public ContactProfileModel ContactProfile { get; set; }
    }
    
    public class Program
    {
        static void Main(string[] args)
        {
            var registration = new RegistrationModel
            {
                ContactProfile = new ContactProfileModel
                {
                    FirstName = "John",
                    LastName = "Doe",
                    BirthDate = DateTime.Now
                }
            };
    
            var expression = Contact<RegistrationModel>(m => m.ContactProfile);
    
            Console.WriteLine(expression.Compile()(registration));
            Console.ReadKey();
        }
    
        public static Expression<Func<TModel, string>> Contact<TModel>(Expression<Func<TModel, ContactProfileModel>> contact)
        {
            var propertyAccess = (MemberExpression)contact.Body;
            var toString = typeof(ContactProfileModel).GetMethod("ToString");
            var toStringValue = Expression.Call(propertyAccess, toString);
    
            return Expression.Lambda<Func<TModel, string>>(toStringValue, contact.Parameters[0]);
        }
    }
    

    【讨论】:

    • 嗯.. 不,我不能,因为我需要直接返回 Expression&lt;Func&lt;TModel, string&gt;&gt; 而不是 string。但无论如何感谢您的第一次贡献:)
    • 这里的潜在问题(使用您的编辑)是实体框架将采用此表达式并将其转换为 SQL(我假设,来自问题中的实体框架标签)。该 SQL 将无法访问 ToString 的 C# 实现(即使可以,它也无法将 C# 代码转换为 SQL)。
    • 你可能是对的,我测试了我的代码,但没有使用实体框架。它可能会起作用,但可能不会在数据库上评估 ToString。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-04
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    相关资源
    最近更新 更多