创建实体框架核心值转换器:属性“备注”属于“字符串”类型,当前数据库提供程序不支持

Posted

技术标签:

【中文标题】创建实体框架核心值转换器:属性“备注”属于“字符串”类型,当前数据库提供程序不支持【英文标题】:Creating an Entity Framework Core Value Converter: The property 'Remark' is of type 'string' which is not supported by the current database provider 【发布时间】:2021-11-12 07:46:01 【问题描述】:

我有一个内部域,它有一个string 属性备注。在我的数据库中,我想将一个空的string 存储为NULL,当我从数据库加载一个NULL 时,我希望这是一个空的string

我想我会创建一个ValueConverter 并注册它,就是这样。

public class StringEmptyToNullConverter : ValueConverter<string, string>

    public StringEmptyToNullConverter() : base(ToDatabaseValue, ToDomainValue) 

    private static readonly Expression<Func<string, string>> ToDatabaseValue =
        domainValue => string.IsNullOrWhiteSpace(domainValue) ? null : domainValue;

    private static readonly Expression<Func<string, string>> ToDomainValue = databaseValue =>
        databaseValue ?? string.Empty;

在我的上下文配置中,我将其注册为builder.Property(x =&gt; x.Remark).HasConversion(new StringEmptyToNullConverter());,它仍然在数据库中存储一个空的string,或者返回一个NULL 值。基本上,这会被忽略。

或者我注册为builder.Property(x =&gt; x.Opmerking).HasConversion&lt;StringEmptyToNullConverter&gt;();,会报错:

“备注”属性属于“字符串”类型,当前数据库提供程序不支持。更改属性 CLR 类型,或使用“[NotMapped]”属性或使用“OnModelCreating”中的“EntityTypeBuilder.Ignore”忽略该属性。

有人知道为什么我的转换器做错了吗?

补充:当我在 lambda 语句中放置断点时,断点永远不会被命中。

我还尝试直接注册 lambda:...HasConversion(domainValue =&gt; string.IsNullOrWhiteSpace(domainValue) ? null : domainValue, databaseValue =&gt; databaseValue ?? string.Empty);,这与第一次注册的结果相同。

【问题讨论】:

这是一个糟糕的想法。空字符串绝对是 NOT null。它有一个非常具体的值,即空字符串。在 SQL 和 .NET 中,这两者完全不同。在 SQL 中,您不能按值搜索空值(您可以对空字符串执行此操作),因此将空字符串转换为 NULL 会中断查询。你必须写 .Where(p=&gt;p.ThatField == null) 而不是 .Where(=&gt;p.ThatField = "") 备注字段不是可搜索字段。我需要这样做的原因是为了保持域正确并且架构师希望数据库尽可能小(已经有很多巨大的数据库),因此不应将空字符串持久化到数据库。 架构师应该知道这是一个坏主意,节省的成本可以忽略不计。如果您真的关心空间,请使用表压缩。这在所有受支持的 SQL Server 版本和版本中都可用,甚至是 Express 和 LocalDB。您将最终提高性能,因为 IO 将减少。如果您有用于报告的大型表,columstore 聚集索引也会减少存储(并提高性能) 【参考方案1】:

有两个问题阻止了它的工作。给定:

modelBuilder.Entity<Customer>()
            .Property(c => c.Name)
            .HasConversion(
                ev => ev==""?null:ev, 
                dv => dv==null?"":dv
                );

首先,查询的值转换不会将谓词转换为is null,它只是生成= null,在标准SQL 中它永远不会匹配任何行,因为null = null 的计算结果为unknown,例如

这样的查询:

var q2 = from cust in db.Customers
        where cust.Name == ""
        select cust;

翻译成

  SELECT [c].[Id], [c].[Name]
  FROM [Customers] AS [c]
  WHERE [c].[Name] = NULL

二、值转换器无法将数据库中的null转换为.NET中的“”:

此外,值转换器通常不允许 null 的转换 到其他一些值。这是因为相同的值转换器可以 用于可空和不可空类型,非常有用 对于 PK/FK 组合,其中 FK 通常可以为空,而 PK 是 不是。我们将讨论是否添加对转换的支持 空值。

https://github.com/dotnet/efcore/issues/13850

这目前根本行不通。因此,不要将空字符串和 null 混合在一起(你也不会),如果你想要实体和数据库之间的一致性,你唯一的选择就是在这两个地方都使用 null。

【讨论】:

备注字段不是可搜索字段。我需要这样做的原因是为了保持域正确并且架构师希望数据库尽可能小(已经有很多巨大的数据库),因此不应将空字符串持久化到数据库中。我确实注意到它确实在数据库中放入了一个null 值,但将null 放入了我的属性中。它似乎没有将 db null 转换为空的 string。我已经在代码中放置了断点并且它们没有被命中。会不会是该属性有一个支持字段而不是自动属性? 啊,值转换目前不允许将null 转换为其他值。转换器根本不针对空值运行。 查看更新的答案。 对于那些寻找答案的人。我创建了一个带有如下支持字段的属性:public string Remark get =&gt; _remark ?? string.Empty; set =&gt; _remark = value?.Trim() 因为“备注字段并不意味着可搜索字段”,这是一个很好的解决方案。

以上是关于创建实体框架核心值转换器:属性“备注”属于“字符串”类型,当前数据库提供程序不支持的主要内容,如果未能解决你的问题,请参考以下文章

如何从Core Data中的字符串获取Processed字符串

NSPredicate 使用核心数据实体的属性

实体框架核心 1.1.1 为连接字符串引发 ArgumentNullException

如何从 DBContext(实体框架核心)中的 App.config(不是 .net 核心应用程序)读取值

实体框架 6 - 数据库优先 - 缺少字符串长度属性

从核心数据创建 json 字符串,反之亦然?