【问题标题】:EF Core Backing fields - expose property as another type?EF Core 支持字段 - 将属性公开为另一种类型?
【发布时间】:2020-10-06 02:02:07
【问题描述】:

假设我有一个 EF 实体类 Person,上面有一个 PhoneNumber。 PhoneNumber 存储为字符串类型,但我希望 Person 上的所有访问都通过具有一些不错的访问器功能的 Phone,例如验证或GetAreaCode()。我想将它作为字符串在数据库中支持,但是当查询它时,我想将它作为电话号码返回:

public class Person {
    public PhoneNumber Phone { /* Some clever get/set logic here */ }

    private string _phoneNumber; // Backing field
}

或者我可以让 PhoneNumber 将自身存储为字符串吗?如果我只是通过删除上面的支持字段将它包含在模型中,EF 会被构造函数(一个受保护的 ctor 比一个字符串更多的 args)和一个复制 ctor PhoneNumber(PhoneNumber other) 混淆。我可以让 EF 以某种方式忽略这些吗?

我对想法持开放态度...

【问题讨论】:

标签: c# entity-framework-core


【解决方案1】:

您可以使用 @nbrosz 的 answer 来解决您的问题,但如果您使用的是 EF Core 2.1,则不再需要执行这种解决方法。您可以使用 EF Core 2.1(自 2018 年 5 月 7 日起在 Release Candidate 1 中)摆脱支持字段,您可以使用 Microsoft 解释的值转换功能 here

值转换器允许在读取时转换属性值 从或写入数据库。这种转换可以来自一个值 到另一个相同类型的(例如,加密字符串)或从 一种类型的值到另一种类型的值(例如, 将枚举值与数据库中的字符串相互转换。)

因此,对于您的情况,您可以删除支持字段。你不再需要它了。你的类应该是这样的:

public class Person 
{
    public PhoneNumber Phone { /* Some clever get/set logic here */ }
}

在您的OnModelCreating 方法中,您可以像下面这样配置转换:

modelBuilder.Entity<Person>()
    .Property(p => p.Phone)
    .HasConversion(
        phone => { 
            // Here you code the logic of how to get the value to store in DB
            return ...;
        },
        dbValue => { 
            // Here you code the logic of how to construct the PhoneNumber instance from the value to store in DB
        }
    );

就是这样。实际上它在候选版本中,但微软说:

EF Core 2.1 RC1 是一个“上线”版本,这意味着一旦您测试了 您的应用程序与 RC1 一起正常工作,您可以在 生产并获得 Microsoft 的支持,但您仍然应该 更新到最终的稳定版本。

我的答案的其余部分适用于 @nbrosz,因为您正在处理枚举类型。您可以删除支持字段,也可以使用 EF Core 2.1 提供的众多内置值转换器之一。对于枚举到字符串值的转换,我们有EnumToStringConverter 类型。对于您在答案中所做的逻辑,您可以将其简化为实体:

[Display(Name = "Fire Type")]
public Enums.FireType Type { get; set; }

我们删除了属性上的NotMapped 属性,并且没有逻辑 y 进行转换。

在您的 OnModelCreating 方法中,您可以这样做:

var converter = new EnumToStringConverter<FireType>();

modelBuilder
    .Entity<Fire>()
    .Property(e => e.FireType)
    .HasConversion(converter);

您还可以使用HasConversion&lt;T&gt; 的通用版本让 EF Core 为您检测正确的转换器,如下所示:

modelBuilder
    .Entity<Fire>()
    .Property(e => e.FireType)
    .HasConversion<string>();

如果您不喜欢使用流畅的配置,您可以使用Column 数据注释属性,如下所示,EF Core 将为您进行转换:

[Column(TypeName = "nvarchar(20)")]
[Display(Name = "Fire Type")]
public Enums.FireType Type { get; set; }

【讨论】:

  • 感谢您的反馈。我非常喜欢它的外观,它确实解决了我一直很头疼的问题。太糟糕了,我们的服务器管理员不太可能很快使用 Core 2.1。我什至无法让它们超过 2.0.3。
  • @nbrosz 您应该在 .Net Core 中寻找自容器应用程序。这允许您使用运行时部署应用程序,因此您的应用程序不需要服务器来安装特定的运行时。
  • 我认为值得一提的是,自定义值转换器往往会导致客户端查询评估。
  • @MattJenkins 你可以通过使用ValueConverterSelector来解决这个问题。查看this article了解更多详情。
  • public TimeSpan DeliveryTime { get; set; }builder.Entity&lt;Product&gt;().Property(p =&gt; p.DeliveryTime).HasConversion(p =&gt; p.Ticks, p2 =&gt; TimeSpan.FromTicks(p2)); 给出“操作数类型冲突:时间与 bigint 不兼容”错误:|关于更新迁移
【解决方案2】:

我发现在 EF Core 2.0 中有效的唯一方法是使用 getter/setter 创建一个公共属性其名称与您的支持字段不匹配 并将其标记为 NotMapped,例如所以:

    [NotMapped]
    [Display(Name = "Fire Type")]
    public Enums.FireType Type
    {
        get
        {
            Enums.FireType type;
            if (!Enum.TryParse(_fireType, out type))
                type = Enums.FireType.Fire; // default

            return type;
        }
        set
        {
            _fireType = value.ToString();
        }
    }
    
    private string _fireType;

然后在您的 DbContext 的 OnModelCreating 方法中,告诉它在数据库表上创建一个类似于支持属性的列:

        // backing properties
        modelBuilder.Entity<Fire>()
            .Property<string>("FireType")
            .HasField("_fireType")
            .UsePropertyAccessMode(PropertyAccessMode.Field);

有了这个,我终于能够创建一个成功的迁移,它允许我在我的模型上拥有一个私有字段,在模型上拥有一个公共的转换属性,以及在我的数据库表中拥有一个正确命名的列。唯一的问题是公共属性和私有字段不能共享相同的名称(不共享相同的类型),但无论如何,这对你来说不是这样。

【讨论】:

  • 非常好!可惜不得不做这种诡计,但它确实解决了我的问题。谢谢!
  • 即使复制粘贴您的示例也不起作用并导致异常:The specified field '_fireType' cannot be used for the property 'Foo.FireType' because it does not match the property name.。 EF Core 3.1。
  • 同意 Krzaku - 至少在 3.1 中这不起作用,因此投反对票
  • public long _deliveryTime;[NotMapped]public TimeSpan DeliveryTime {get =&gt; TimeSpan.FromTicks(_deliveryTime); set =&gt; _deliveryTime = value.Ticks; }.Property&lt;long&gt;(nameof(Product.DeliveryTime)).HasField(nameof(Product._deliveryTime)).UsePropertyAccessMode(PropertyAccessMode.Field); 给出以下错误:“属性 'DeliveryTime' 不能添加到类型 'Product' 因为相应的 CLR 属性或字段 'TimeSpan' 的类型与指定类型'long'。”在添加迁移
  • @deadManN 在提出问题时这是一个可行的建议(早在 2018 年,对于 EF Core 2.0),但这不再是推荐的解决方案。不幸的是,人们会继续关注这个老话题并尝试老答案。我不知道有什么方法可以清楚地将其标记为不合时宜。
猜你喜欢
  • 2020-06-22
  • 1970-01-01
  • 2021-12-13
  • 1970-01-01
  • 2014-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-25
相关资源
最近更新 更多