Alex
Alex

Reputation: 77379

Entity Framework 5 - Enum based Discriminator for derived classes

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

Answers (5)

Jonathan Ramos
Jonathan Ramos

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

Dibyodyuti Mondal
Dibyodyuti Mondal

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:

  1. MyBaseClass
  2. DerivedOne
  3. 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

Ogglas
Ogglas

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

rsenna
rsenna

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

NSGaga
NSGaga

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

Related Questions