I'm trying to write a generic IEntityTypeConfiguration for an entity that has a generic type parameter.
I have the following class:
public class ItemSpecs<TItemConstitution> : Entity, IItemSpecs<TItemConstitution>
where TItemConstitution : Enum
{
public Guid ItemSpecsId { get; set; }
public decimal OtherProperty { get; set; }
public virtual TItemConstitution Property { get; set; }
}
public enum ParentItemProperty
{
OnlyParent,
MultiParent
}
public enum ChildItemProperty
{
OnlyChild,
MultipleChild
}
public interface IItemSpecs<TItemConstitution>
{
public TItemConstitution Property { get; set; }
}
Some items will be ItemSpecs<ParentItemProperty>
others will be ItemSpecs<ChildItemProperty>
. Since both instances have an Enum Property and Enums get stored as ints in the Db, my idea was to store them in the same DbSet.
When I want to configure ItemSpecs<TItemConstitution>
through IEntityTypeConfiguration:
public class ItemSpecsTypeConfig : IEntityTypeConfiguration<ItemSpecs<Enum>>
{
public void Configure(EntityTypeBuilder<ItemSpecs<Enum>> builder)
{
builder.HasKey(x => x.ItemSpecsId);
}
}
Things seem to be okay, but when I want to Add an item to
public DbSet<ItemSpecs<Enum>> ItemSpecs{ get; set; }
I get the following
InvalidOperationException : The entity type 'ItemSpecs<ParentItemProperty>' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'.
I'm a little confused by that exception, since the ItemSpecs-class is referenced to in the ItemSpecsTypeConfig-class and has a Key defined. I don't intend to use keyless entitytype.
Can anyone explain this error, as to where it's coming from and how to work with it? Official Documentation makes no mention of this use case at all.
Do I need an IEntityTypeConfiguration<IItemSpecs<>> for each enum and write to same table or can I make a generic IEntityTypeConfiguration for all implementation? I'd like to avoid having DbSets for every type of ItemSpecs. Thanks for any information or example
Update:
The Exception I was getting
InvalidOperationException : The entity type 'ItemSpecs<ParentItemProperty>' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'.
Was caused by (Thank you @Eldar) not having an implementation for IEntityTypeConfiguration<ItemSpecs<ParentItemProperty>
and IEntityTypeConfiguration<ItemSpecs<ChildItemProperty>
After I added those, the exception changed to
InvalidOperationException : The entity type 'ItemSpecs<Enum>' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'
which comes from my DbContext public
public DbSet<ItemSpecs<Enum>> ItemSpecs{ get; set; }
It makes sense to me that DbSet<ItemSpecs<Enum>>
is not a correct definition, but refactoring to having a DbSet<ItemSpecs<TEnum>>
would require me to know this Generic Type Argument everytime I inject the DbContext (right?)
So this is my question:
How do I define a DbSet for an entity with a generic type parameter? The goal is to have 1 DbSet for all possible generic implementations. Querying and persisting happens (mostly) through reflection, so not really an issue for this question. Also, the ItemSpecs class is always a nested class.
The solution is to work with an abstract base class (and separate IEntityTypeConfiguration) to configure the keys:
public abstract class ItemSpecsBase
{
public Guid ItemSpecsId { get; set; }
public decimal OtherProperty { get; set; }
}
public class ItemSpecsBaseTypeConfig : IEntityTypeConfiguration<ItemSpecsBase>
{
public void Configure(EntityTypeBuilder<ItemSpecsBase> builder)
{
builder.HasKey(x => x.ItemSpecsBaseId);
}
}
To also add IEntityTypeConfigurations per TEnum, so we can write to the same database table:
public class ParentItemSpecsBaseTypeConfig : IEntityTypeConfiguration<ParentItemProperty>
{
public void Configure(EntityTypeBuilder<ParentItemProperty> builder)
{
builder.ToTable("ItemSepcs");
}
}
public class ChildItemSpecsBaseTypeConfig : IEntityTypeConfiguration<ChildItemProperty>
{
public void Configure(EntityTypeBuilder<ChildItemProperty> builder)
{
builder.ToTable("ItemSepcs");
}
}
Then make the class inherit from the base class
public class ItemSpecs<TItemConstitution> : ItemSpecsBase, IItemSpecs<TItemConstitution>
where TItemConstitution : Enum
{
public virtual TItemConstitution Property { get; set; }
}
In DbContext, we make DbSets per TEnum. Since we write them to the same database table, this does not really impact the DB
public DbSet<ItemSpecs<ParentItemProperty>> ParentItemSpecs { get; set; }
public DbSet<ItemSpecs<ChildItemProperty>> ChildItemSpecs { get; set; }
Interface can stay the same, but is not really relevant to this question
public interface IItemSpecs<TItemConstitution>
where TItemConstitution : Enum
{
TItemConstitution Property { get; set; }
Guid ItemSpecsId { get; set; }
decimal OtherProperty { get; set; }
}