15

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            |             |            |
1
  • see my latest comment on my answer. How many columns would you then get...? then make it more abstract, and create some casting-logic to your code. Commented Oct 19, 2017 at 9:09

2 Answers 2

18

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.

Sign up to request clarification or add additional context in comments.

4 Comments

The above code actually creates 2 tables: Foostring and FooInt. If you force ef to give both tables the same name in the model builder you will get an error. To get this to work you would have to use table splitting.
I managed to get it working with a single table by including the Foo base class in the model (which can be done either by create a DbSet for the Foo class or by specifying it in the OnModelCreating method).
@somethingRandom did the Foo DbSet have a generic? If so what did you pass into it?
@BeniaminoBaggins No, you create a DbSet for the Foo class like this public DbSet<Foo> Foos { get; set: }; the Foo class is not generic so you don't pass anything to it. Having said that, I found it made more sense to create the Foos table manually in the OnModelCreating method since you will probably never access that table directly (the Value properties are not accessible from that DbSet). I have updated the answer will additional info on how to implement this.
11

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.

9 Comments

Thanks for the answer :D Is there an alternative way? It feels like it defeats the point of generics?
why does it feel like that? Isn't the whole idea so you wouldn't have to type every letter for every (domain) model for the same things..? How would your database would look like then? Or your DTO's or other models?
I'll have to create a class for every type that I want to use Foo with? If I said I only want to use this for base types does that make it easier?
Doesn't this create one table per subtype?
This creates one table per subtype since you cannot tell entity framework to create a table for the Foo since its a generic class.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.