如何使用 Entity Framework Core 3.1 将 ValueObject 的方法转换为 SQL

问题描述

我有这个值对象

public class ProductReference : ValueObject
{
   protected ProductReference(){}
   public ProductReference(string value){}
   public string Value{get; protected set;}
}

我在我的实体中使用它作为:

public class Product : Entity<long> 
{
   protected Product(){} 
   public ProductReference Reference{get; protected set;}   
}

在我定义的 OnModelCreatingDbContext 中:

modelBuilder.Entity<Product>(entity => {
                     entity.Property(a => a.Reference)
                    .HasColumnName("Reference")
                    .HasConversion(
                        a => a.Value,s => new ProductReference (s);
 });

当我这样做时:

await dbcontext.Products.Where(p=>p.Reference.Value.Contains("some text")).toArrayAsync();

出现异常

表达式无法转换为有效的 sql 语句

我肯定知道有一种方法可以创建自定义表达式转换器,但我找不到一个好的、简单且与 EF Core 3.1 兼容的示例来处理我的问题并清楚地解释了我错过的概念。

我发现这个非常有趣的项目 https://github.com/StevenRasmussen/EFCore.SqlServer.NodaTime 但它太先进了,我无法仅在我的用例中重现它。

[编辑] ValueObject ans Entity 来自 CSharpFunctionalExtensions nuget 包,我认为它们与我的问题无关。

解决方法

首先是关于幕后发生的事情的一些背景信息,以及为什么即使内置简单的转换器(如 BoolToZeroOneConverter)它也不起作用。

这里的问题是您在转换 new ProductReference(s) 时调用。这是一种方法,您可以在其中做任何想做的事情。例如,如果在 Select 语句中使用它,它将再次失败。例如:

await dbcontext.Products
.Select(x=>new ProductReference(x.Value))
.toArrayAsync();

原因很明显,它将无法翻译。但是为什么它不能将其转换为查询?

因为你正在传递一个构造函数。在这个构造函数中,你可以进行 API 调用或使用反射来为你的对象设置变量,几乎任何东西。这当然不能在 SQL 查询中进行翻译。

转换器通常用于内存中,但它们也可用于数据库操作。这意味着您将需要这样的东西:

await dbcontext.Products
.Select(x=>new ProductReference() // empty constructor that does nothing
{
    Property1 = x.Property1 // I don't know how the constructor maps them
})
.toArrayAsync();

使用这种类型的表达式允许您将表达式实际转换为 SQL 语句,而不是在 SQL DB 上而不是在内存中进行转换。

现在在您的特定情况下使用:

.HasConversion(
                        a => a.Value,s => new ProductReference (){};
 });

应该可以解决您的问题,但我不明白您为什么要将 ProductReference 初始化或转换为 ProductReference