我正在尝试有效地将实体映射到模型上。
我的实体是:
public class ParentEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ChildEntity Child { get; set; }
}
public class ChildEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
我的模型是:
public class ParentModel
{
public int Id { get; set; }
public string Name { get; set; }
public ChildModel Child { get; set; }
}
public class ChildModel
{
public int Id { get; set; }
public string Name { get; set; }
}
(实际上,这些类之间会存在差异,但为了简化,这里不存在差异。)
我编写了一个扩展方法来进行映射:
public static IQueryable<ParentModel> ToParentModel (this IQueryable<ParentEntity> parentEntities)
{
return parentEntities.Select(p => new ParentModel
{
Id = p.Id,
Name = p.Name,
Child = new ChildModel { Id = p.Child.Id, Name = p.Child.Name.ToLower()}
});
}
ToLower()
是为了突出显示问题。
我可以运行它:
var parents = _context.Set<ParentEntity>().ToParentModel().ToArray();
生成的SQL为:
SELECT "p"."Id", "p"."Name", "c"."Id", lower("c"."Name") AS "Name"
FROM "Parents" AS "p"
LEFT JOIN "Children" AS "c" ON "p"."ChildId" = "c"."Id"
即小写处理在数据库中完成。
到目前为止一切都很好,除了关注点分离不太好。初始化
ChildModel
的代码与初始化 ParentModel
的代码位于同一位置。
我尝试在
ChildModel
中使用构造函数:
public ChildModel(ChildEntity ent)
{
Id = ent.Id;
Name = ent.Name.ToLower();
}
并在扩展方法中:
return parentEntities.Select(p => new ParentModel
{
Id = p.Id,
Name = p.Name,
Child = new ChildModel (p.Child)
});
这可行,但生成的 SQL 不包含
lower
。转换为小写是在程序中完成的。
有什么办法可以边吃边吃蛋糕吗?
我仍然可以将 C# 代码转换为 SQL,但仍然以模块化方式构建我的 C# 代码吗?
EF Core 的
IQueryable
实现利用 ExpressionTrees 将尽可能多的工作推迟到数据库。
来自微软:
表达式树表示树状数据结构中的代码,其中每个节点都是一个表达式,例如方法调用或二元运算,例如 'x < y'
当您将
Select
与模型 setters
一起使用时:
return parentEntities.Select(p => new ParentModel
{
Id = p.Id,
Name = p.Name,
Child = new ChildModel { Id = p.Child.Id, Name = p.Child.Name.ToLower()}
});
这转化为一个表达式树,看起来像这样:
Expression-> Func<Parent, ParentModdel>
-- BinaryExpression.Assign-> ParentModel.Id->setter, Parent.Id->getter
-- BinaryExpression.Assign-> ParentModel.Name->setter, Parent.Name->getter
-- BinaryExpression.Assign-> ParentModel.Child->setter, Expression->Func<Parent, ChildModel>
// Expression->Func<Parent, ChildModel> looks something like:
BinaryExpression.Assign-> ChildModel.Id->setter, Parent.Child.Id->getter
BinaryExpression.Assign-> ChildModel.Name->setter, Parent.Child.Name->getter
我使用伪代码来简化树状结构的说明,因为 ExpressionTree 语法非常冗长并且可能非常令人困惑,并且坦率地说超出了当前问题的范围。请参阅在此处分配表达式。
当您使用构造函数语法时:
Child = new ChildModel (p.Child)
无法有效推断其内容。构造函数被有效地视为方法,这意味着您可以将它们的参数、返回类型和调用描述为表达式,但不能描述其内容。因此 EF 没有选择,只能创建一个简单的表达式树,从 SQL 中获取 Parent 对象,然后在内存中执行
ChildModel
构造函数,从而调用 string.ToLower() 操作。
注意:可以读取方法内容,但这涉及反编译 IL,这对于数据库查询来说过于密集且缓慢。这就是 VS 在检查引用库中的方法时所做的事情
AutoMapper 在程序启动时运行其重型反射来生成对象映射,正如您可能猜到的那样,它代表
ExpressionTree
。默认情况下,它使用命名约定,即它将映射 Id
-> Id
和 ChildId
-> ChildId
或 Child.Id
。它还公开 API 来为更复杂的场景定义您自己的自定义映射。