我正在尝试使用 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();
}
}
}
我的问题是,我不明白为什么会出现以下异常:
System.ArgumentException:“未为类型“ConsoleApp1.PersonEntity”(参数“属性”)定义属性“Int32 Age””
好吧,虽然用其他表达式替换表达式树的部分内容与
string.Replace
有相似之处,但并不完全相同。它通常用于用另一个相同类型的表达式来模拟表达式的“调用”或成员访问。如果您确实像这里一样将其替换为不同类型的表达式,则还有很多额外的工作要做。如果它是泛型方法的参数,则整个方法调用(包括其他参数)必须重新绑定到新类型。对于原始类型的成员(方法、属性、字段)也是如此。
在你的情况下,问题是这个表达式
x.Age
在表达式术语中是MemberExpression
,其中 Expression 是 ParameterExpression
,名称
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>();