chris31389
chris31389

Reputation: 9356

How can I use a generic type with entity framework core?

If I have a domain model that looks something like this:

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

I want to use it for built in Data Types (string, int, etc...) as well as date. I want to use it like:

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

how can I persist this to a database using EF Core?

I imagine the database table would look like

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

Upvotes: 15

Views: 20292

Answers (2)

Henk Kok
Henk Kok

Reputation: 383

If you want to persist different values types to the database in a single table similar to the one in your question, you can do it like this:

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; }
}

In your DbContext class add properties:

public DbSet<FooString> FooStrings { get; set: }
public DbSet<FooInt> FooInts { get; set; }

You can set the column names for the table in the OnModelCreating method of your DbContext:

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");
}

This code will generate a table Foos with columns Id, Statement, Discriminator, ValueAsString and ValueAsInt. More info on the Discrimiator column can be found here

Image of the resulting table

You still need to create a class for each Type/column you want to use for T, I don't think you can get around that.

Upvotes: 18

Roelant M
Roelant M

Reputation: 1706

you should still have a class. Your class Foo should be abstract. So you would get":

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

then your implementation class would be:

public class Orders: Foo<Order> {
}

now you have your Orders class with your generic type which can be stored.

Upvotes: 11

Related Questions