问题
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!
回答1:
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>();
回答2:
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*
...
回答3:
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));
}
}
回答4:
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.
回答5:
As of EF Core you can use Enums directly on Fluent API. If your MyBaseClass is not mapped, you can remove the first HasValue line describing the base discriminator.
modelBuilder.Entity<MyBaseClass>()
.HasDiscriminator<MyEnum>("MyEnum")
.HasValue<MyBaseClass>(MyEnum.Value0)
.HasValue<DerivedOne>(MyEnum.Value1)
.HasValue<DerivedTwo>(MyEnum.Value2);
来源:https://stackoverflow.com/questions/15859452/entity-framework-5-enum-based-discriminator-for-derived-classes