Reputation: 77379
I have the following (abbreviated for clarity) - an enum, a base class with that enum, and two derived classes that set the enum to a specific value.
public enum MyEnum
{
Value1, Value2
}
public class MyBaseClass
{
public MyEnum { get; protected set; }
}
public class DerivedOne: MyBaseClass
{
public DerivedOne { MyEnum = MyEnum.Value1; }
}
public class DerivedTwo: MyBaseClass
{
public DerivedTwo { MyEnum = MyEnum.Value2; }
}
What I want to do, is have Entity Framework 5 automatically distinguish between DerivedOne and DerivedTwo, with a MyEnum value based discriminator. I should be able to do this as, by convention, every MyEnum == MyEnum.Value1 represents DerivedOne, and MyEnum == MyEnum.Value2 represents DerivedTwo.
I tried this in my DbContext:
public class MyDbContext : DbContext
{
DbSet<MyBaseClass> MyBaseClass { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyBaseClass>()
.Map<DerivedOne>(m => m.Requires(x => x.MyEnum == MyEnum.Value1));
base.OnModelCreating(modelBuilder);
}
}
However, this throws the following InvalidOperationException:
The expression 'x => (Convert(x.MyEnum) == 0)' is not a valid property expression. The expression should represent a property (...)
Edit: I believe I got a little farther using this:
modelBuilder.Entity<MyBaseClass>().Map<DerivedOne>(m => m.Requires("MyEnum")
.HasValue((Int32)MyEnum.Value1));
Now I get this EntityCommandCompilationException:
Problem in mapping fragments starting at line (...) Condition member 'MyBaseClass.MyEnum' with a condition other than 'IsNull=False' is mapped. Either remove the condition on MyBaseClass.MyEnum or remove it from the mapping.
Any hints on how I can solve this? Thanks!
Upvotes: 16
Views: 8923
Reputation: 2161
As of EF Core you can use Enums directly using Fluent API. If your MyBaseClass is not mapped (is an abstract class), you can remove the first HasValue line describing the base discriminator. Try the next code in your ApplicationDbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyBaseClass>()
.HasDiscriminator<MyEnum>("MyEnum")
.HasValue<MyBaseClass>(MyEnum.Value0)
.HasValue<DerivedOne>(MyEnum.Value1)
.HasValue<DerivedTwo>(MyEnum.Value2);
}
Upvotes: 15
Reputation: 191
I wonder if adding a third value to MyEnum
to represent the base class will help.
And then set MyBaseClass.MyEnum
to that particular 'default' enum
value in the constructor.
I think the Table-per-heirarchy structure needs that EVERY type must have a valid discriminator. So, you have 3 types:
MyBaseClass
DerivedOne
DerivedTwo
Even if your application won't use MyBaseClass in its base form ever, EF still needs a valid discriminator mapping for it.
Upvotes: 0
Reputation: 70184
Based on the answer of @rsenna but updated with mapping based on Microsofts Fluent Api original documentation.
https://msdn.microsoft.com/en-us/library/jj591617%28v=vs.113%29.aspx?f=255&MSPPError=-2147217396
public enum MyEnum
{
Value1, Value2
}
public class MyBaseClass
{
[NotMapped]
public MyEnum MyEnum { get; protected set; }
}
public class DerivedOne: MyBaseClass
{
public DerivedOne()
{
MyEnum = MyEnum.Value1;
}
}
public class DerivedTwo: MyBaseClass
{
public DerivedTwo()
{
MyEnum = MyEnum.Value2;
}
}
public class MyDbContext : DbContext
{
DbSet<MyBaseClass> MyBaseClass { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyBaseClass>()
.Map<DerivedOne>(x => x.Requires("MyEnum").HasValue((int)MyEnum.Value1))
.Map<DerivedTwo>(x => x.Requires("MyEnum").HasValue((int)MyEnum.Value2));
}
}
Upvotes: 3
Reputation: 11982
As of EF 6.1, I was in fact able to use an enum as a discriminator column, in spite of this error:
Additional information: Values of type 'MyEnum' cannot be used as type discriminator values. Supported types include byte, signed byte, bool, int16, int32, int64, and string.
All I have to do was something like this:
public enum MyEnum
{
Value1, Value2
}
public class MyBaseClass
{
public MyEnum { get; protected set; }
}
public class DerivedOne: MyBaseClass
{
public DerivedOne()
{
MyEnum = MyEnum.Value1;
}
}
public class DerivedTwo: MyBaseClass
{
public DerivedTwo()
{
MyEnum = MyEnum.Value2;
}
}
public class MyDbContext : DbContext
{
DbSet<MyBaseClass> MyBaseClass { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations
.Add(new DerivedOneConfiguration())
.Add(new DerivedTwoConfiguration());
}
}
public class DerivedOneConfiguration : EntityTypeConfiguration<DerivedOne>
{
public DerivedOneConfiguration()
{
Map<DerivedOne>(_ => _.Requires("MyEnum").HasValue((int)MyEnum.Value1).IsRequired());
}
}
public class DerivedTwoConfiguration : EntityTypeConfiguration<DerivedTwo>
{
public DerivedTwoConfiguration()
{
Map<DerivedTwo>(_ => _.Requires("MyEnum").HasValue((int)MyEnum.Value2).IsRequired());
}
}
So the secret was using (int)MyEnum.Value*
instead of MyEnum.Value*
...
Upvotes: 4
Reputation: 14312
As far as I know you cannot do that. Doing the explicit Requires
to specify the disciminator is only to give it a name
- not to connect it to your property.
As far as I know that always results in that error (later) that you're describing. If you want to specify discriminator it has to be 'automatic'one (at least I never managed to define it that way)
But you don't need that really. The 'enum' and discriminator is built into
the type you get back - based on the discriminator values, EF/CF is constructing either 'Base` or 'DerivedOne' or DerivedTwo.
So to implement what you want you can do the following...
public class MyBaseClass
{
[NotMapped()]
public virtual MyEnum MyEnum { get { return MyEnum.Base; } }
}
public class DerivedOne: MyBaseClass
{
public string OneProp { get; set; }
public override MyEnum MyEnum { get { return MyEnum.One; } }
}
public class DerivedTwo: MyBaseClass
{
public string TwoProp { get; set; }
public override MyEnum MyEnum { get { return MyEnum.Two; } }
}
Or just use is
instead (if it works for you)...
if (entity is MyBaseClass) // instead of enum
or Query by...
.OfType<MyBaseClass>();
Upvotes: 7