动态 LINQ 查询生成器抛出异常

问题描述 投票:0回答:1

我正在尝试使用 Business-Class-Linq-Query 查询我的数据库实体。我知道我需要转换 LINQ 查询,以便它可以针对实体类运行。

我的问题是,我不明白为什么会出现以下异常:

这是完整的(测试)代码,您可以看到 Person 和 PersonEntity 几乎相同。

using System.Linq.Expressions;

namespace ConsoleApp1
{
    class Person
    {
        public int Age { get; set; }
        public string? Name { get; set; }
    }

    class PersonEntity
    {
        public int Id { get; set; }


        public int Age { get; set; }
        public string? Name { get; set; }
    }

    static class ExpressionTranslator
    {
        public static Expression<Func<PersonEntity, bool>> Translate(Expression<Func<Person, bool>> expression)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(PersonEntity), "e");
            Expression body = new ReplaceVisitor(expression.Parameters[0], parameter).Visit(expression.Body);

            return Expression.Lambda<Func<PersonEntity, bool>>(body, parameter);
        }

        private class ReplaceVisitor : ExpressionVisitor
        {
            private readonly ParameterExpression _oldParameter;
            private readonly ParameterExpression _newParameter;

            public ReplaceVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
            {
                _oldParameter = oldParameter;
                _newParameter = newParameter;
            }

            protected override Expression VisitParameter(ParameterExpression node)
            {
                return node == _oldParameter ? _newParameter : base.VisitParameter(node);
            }
        }
    }



    internal class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<Person, bool>> actualQuery = x => x.Age > 18;
            IEnumerable<Person> result = QueryPersons(actualQuery);

            foreach (var p in result)
            {
                Console.WriteLine($"{p.Name}, {p.Age}");
            }
        }

        private static IEnumerable<Person> QueryPersons(Expression<Func<Person, bool>> query)
        {
            var db = new List<PersonEntity>(new[] {
                    new PersonEntity() { Id= 1, Age=15, Name="John" },
                    new PersonEntity() { Id= 2, Age=25, Name="Sally" },
                    new PersonEntity() { Id= 3, Age=35, Name="Jack" },
                });


            Expression<Func<PersonEntity, bool>> entityQuery = ExpressionTranslator.Translate(query);


            return db.AsQueryable().Where(entityQuery).Select(x => new Person() { Age = x.Age, Name = x.Name }).ToList();
        }
    }
}
c# expression-trees
1个回答
0
投票

我的问题是,我不明白为什么会出现以下异常:

System.ArgumentException:“未为类型“ConsoleApp1.PersonEntity”(参数“属性”)定义属性“Int32 Age””

好吧,虽然用其他表达式替换表达式树的部分内容与

string.Replace
有相似之处,但并不完全相同。它通常用于用另一个相同类型的表达式来模拟表达式的“调用”或成员访问。如果您确实像这里一样将其替换为不同类型的表达式,则还有很多额外的工作要做。如果它是泛型方法的参数,则整个方法调用(包括其他参数)必须重新绑定到新类型。对于原始类型的成员(方法、属性、字段)也是如此。 在你的情况下,问题是这个表达式

x.Age

在表达式术语中是 
MemberExpression

,其中 ExpressionParameterExpression,名称

x
(不重要)和类型
Person
(非常重要),并且(也非常重要)
Member
是反射 MemberInfo(在这种情况下,
PropertyInfo
),
DeclaringType
等于
Person
。请注意,最后一个不是名字。因此,当您将
x
替换为
e
类型的
PersonEntity
时,会员信息不再有效,因此出现异常。
因此,正如我之前所说,您需要做更多的工作才能支持这种情况。一些第三方库提供了这样的功能,例如 AutoMapper 以及按名称的“自动”映射属性(您实际上在这里想要做什么)还允许通过配置指定替换表达式。

为了至少支持这种按名称简化的成员映射,参数替换访问者是不够的,您至少需要访问者它也覆盖

VisitMember

并在那里进行映射。 例如,如果我们用泛型概括你的示例,它可能是这样的

static class ExpressionTranslator { public static Expression<Func<TDestination, bool>> Translate<TSource, TDestination>( this Expression<Func<TSource, bool>> expression) { var parameter = Expression.Parameter(typeof(TDestination), "e"); var body = new TranslateVisitor(expression.Parameters[0], parameter).Visit(expression.Body); return Expression.Lambda<Func<TDestination, bool>>(body, parameter); } private class TranslateVisitor : ExpressionVisitor { private readonly ParameterExpression _oldParameter; private readonly ParameterExpression _newParameter; public TranslateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter) { _oldParameter = oldParameter; _newParameter = newParameter; } protected override Expression VisitParameter(ParameterExpression node) { return node == _oldParameter ? _newParameter : base.VisitParameter(node); } protected override Expression VisitMember(MemberExpression node) { if (node.Expression?.Type == _oldParameter.Type) { // Map source member by name var expression = Visit(node.Expression); var member = Expression.PropertyOrField(expression, node.Member.Name); return member; } return base.VisitMember(node); } } }

在您的示例中使用(工作):

var entityQuery = query.Translate<Person, PersonEntity>();

© www.soinside.com 2019 - 2024. All rights reserved.