如何在Entity Framework Core中使用通用类型?

13
如果我有一个类似于以下结构的领域模型:
public class Foo<T> {
    public Guid Id { get; set; }
    public string Statement { get; set; }
    public T Value { get; set; }
}

我希望能够使用它来处理内置数据类型(如字符串、整数等)以及日期。

我想要这样使用它:

var foo = new Foo<string>();
foo.Value = "Hey";

如何使用EF Core将此内容持久化到数据库中?

我想数据库表应该是这样的:

| Id | Statement | ValueAsString | ValueAsDecimal | ValueAsDate | ValueAsInt | 
| 1  | NULL      | "Hey"         |                |             |            |
| 2  | NULL      |               | 1.1            |             |            |

请看我在答案中的最新评论。那么,你会得到多少列...?然后将其更抽象化,并为您的代码创建一些转换逻辑。 - Roelant M
2个回答

16
如果您想将不同类型的值保存到单个表中并持久化到数据库,可以按照以下方式进行操作:
public interface IHasValue<T> {
    T Value { get; set; }
}

public abstract class Foo {
    public Guid Id { get; set; }
    public string Statement { get; set; }
}

public class Foostring : Foo, IHasValue<string> {
    string Value { get; set; }
}

public class FooInt : Foo, IHasValue<int> {
    int Value { get; set; }
}

在您的 DbContext 类中添加属性:
public DbSet<FooString> FooStrings { get; set: }
public DbSet<FooInt> FooInts { get; set; }

您可以在DbContextOnModelCreating方法中设置表格的列名:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // include the base class so a single table is created for the hierarchy
    // rather than a table for each child class
    modelBuilder.Entity<Foo>().ToTable("Foos");

    // Specify the column names or you will get weird names
    modelBuilder.Entity<FooString>().Property(entity => entity.Value)
        .HasColumnName("ValueAsString");
    modelBuilder.Entity<FooInt>().Property(entity => entity.Value)
        .HasColumnName("ValueAsInt");
}

这段代码将生成一个表格Foos,包含列IdStatementDiscriminatorValueAsStringValueAsInt。关于Discrimiator列的更多信息可以在此处找到。

生成表格的图像

你仍然需要为每个想要用于T的类型/列创建一个类,我认为你无法绕开这一点。

上面的代码实际上创建了2个表:FoostringFooInt。如果你强制ef在模型构建器中给这两个表取相同的名称,你会得到一个错误。要使其正常工作,您需要使用表拆分 - somethingRandom
1
我通过在模型中包含Foo基类(可以通过为Foo类创建DbSet或在OnModelCreating方法中指定它来完成)成功地使用单个表。 - somethingRandom
@somethingRandom,Foo DbSet 是否有泛型?如果有的话,你传入了什么参数? - BeniaminoBaggins
@BeniaminoBaggins 不,你可以像这样为 Foo 类创建一个 DbSetpublic DbSet<Foo> Foos { get; set: }Foo 类不是泛型,因此不需要传递任何内容。话虽如此,我发现在 OnModelCreating 方法中手动创建 Foos 表更有意义,因为你可能永远不会直接访问该表(Value 属性无法从该 DbSet 中访问)。我已更新答案,提供了有关如何实现此操作的其他信息。 - somethingRandom

10

你仍然需要一个类。你的类Foo应该是抽象的。 这样,你就可以得到:

public abstract class Foo<T> {
    public Guid Id { get; set; }
    public string Statement { get; set; }
    public T Value { get; set; }
}

那么你的实现类将是:
public class Orders: Foo<Order> {
}

现在您有了一个带有泛型类型的Orders类,可以存储它。

2
谢谢你的回答 :D 是否有其他替代方法?这样感觉有些失去了泛型的意义? - chris31389
为什么感觉是这样的呢?难道不是整个想法就是让你不必为每个(域)模型输入相同的内容吗..?那么你的数据库会是什么样子呢?或者你的DTO或其他模型呢? - Roelant M
1
我需要为每种我想要使用Foo的类型创建一个类吗?如果我说我只想用它来处理基本类型,这会让事情变得更容易吗? - chris31389
2
这会不会为每个子类型创建一个表? - BeniaminoBaggins
1
这将为每个子类型创建一个表,因为您无法告诉实体框架为Foo创建一个表,因为它是一个通用类。 - somethingRandom
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接