EF Core Backing字段 - 将属性公开为另一种类型?

时间:2018-03-09 11:57:30

标签: c# entity-framework-core

假设我有一个EF实体类Person,上面有PhoneNumber。 PhoneNumber存储为字符串类型,但我希望Person上的所有访问都通过Phone,它具有一些很好的访问器功能,例如验证或GetAreaCode()。我希望将它作为字符串备份在db中,但是在查询它时我想将其作为PhoneNumber返回:

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以某种方式忽略这些吗?

我对创意持开放态度......

2 个答案:

答案 0 :(得分:4)

您可以使用@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是一个“go live”版本,这意味着一旦你测试了它   您的应用程序与RC1正常工作,您可以使用它   制作并获得微软的支持,但你仍然应该   一旦可用,就更新到最终的稳定版本。

我的答案的其余部分是 @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<T>的通用版本让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; }

答案 1 :(得分:3)

我发现可行的唯一方法是创建一个公共属性,其中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);

有了这个,我终于能够创建一个成功的迁移,允许我在我的模型上拥有一个私有字段,在模型上有一个公共变换属性,在我的数据库表中有一个正确命名的列。唯一的问题是公共财产和私人领域不能共享相同的名称(没有共享相同的类型),但无论如何,情况并非如此。