Reusing a column for a required property with Entity Framework 6.0, Fluent API, and DataAnnotations
Asked Answered
J

2

2

I have a base class

public class BaseClass
{
    public int Id {get; set;}
}

and two derived classes

public class Foobar: BaseClass
{
    [Required]
    public int Whatever {get; set;}
}

public class Snafu: BaseClass
{
    [Required]
    public int Whatever {get; set;}
}

I'm using Table Per Hierarchy inheritance and trying to cut down on my duplicate columns, so with Fluent API I've mapped them like so:

        modelBuilder.Entity<Foobar>().Property(fb => fb.Whatever).HasColumnName("Whatever");
        modelBuilder.Entity<Snafu>().Property(sf => sf.Whatever).HasColumnName("Whatever");

However, this results in

(137,10) : error 3023: Problem in mapping fragments starting at line 137:Column BaseClass.Whatever in table BaseClass must be mapped: It has no default value and is not nullable.

In EF6 this type of mapping seems to work fine if I take off the [Required] attribute from both subclasses. Adding a [DefaultValue(0)] attribute to both derived classes does not fix the problem.

Any idea how to get these properties to share a column in the database while maintaining their required attribute?

Jaimeejaimes answered 11/12, 2013 at 22:7 Comment(3)
I'm not sure why you don't have "Whatever" in the base class? This property would then be available in both your sub types.Immunochemistry
These classes are just for demonstration purposes. In the real application out of the several derived classes only a couple share a property that isn't in the base class, and on those derived classes that property is enforced by an interface.Jaimeejaimes
Find a verified bug and still don't get an upvote :\Jaimeejaimes
C
3

This is actually a bug in EF6. In EF5 the scenario used not to work at all (we would throw an exception in the lines of "column names need to be unique"). While in EF6 we did some work to enable it, but apparently we missed the fact that the shared column has to be nullable in the database even if the property is required in the derived types. The latter is because unless the base class is abstract, you need to be able to store an instance of the base type and for any instance of the base type the column should be null.

I have filed the issue in our bug database:

https://entityframework.codeplex.com/workitem/1924

Feel free to vote for it.

As for a workaround, if having an intermediary type is not an option, you can mark the column as nullable explicitly appending a call to .IsOptional() on the entity configurations. This won't give you exactly what you want because for the purpose of EF data validation this call to IsOptional() on the fluent API will override the [Required] data annotation. However, other flavors of data validation, such as MVC's validation will still honor the attribute.

There are other possible workarounds that I haven't tried, maybe if it is acceptable to use TPT and have both derived types have Whatever live in a different table this would work. I believe any approach that relies on setting a default value won't help because the bug is not only about the table schema not being able to hold an instance of the base class, it is also about the EF mapping generated by Code First not being valid.

UPDATE: This will be fixed in Entity Framework version 6.1.0 which is currently available in beta.

Cobelligerent answered 12/12, 2013 at 22:37 Comment(0)
P
0

Introducing another type, which contains the required property shared by the other two accomplishes what you're looking for. The entities then look this:

public class BaseClass
{
    public int Id { get; set; }
}
public abstract class BaseIntermediaryClass : BaseClass
{
    [Required]
    public int Whatever { get; set; }

}
public class Foobar : BaseIntermediaryClass
{
}

public class Snafu : BaseIntermediaryClass
{
}

And the mappings like this:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<BaseIntermediaryClass>().Property(fb => fb.Whatever).HasColumnName("Whatever");
        base.OnModelCreating(modelBuilder);
    }

Full code of working example can be found here: https://gist.github.com/trayburn/7923392

Proliferous answered 12/12, 2013 at 5:1 Comment(1)
This is a solution I had considered, but I really don't want to have to subclass a subclass over a single property, I'd rather just use an interface (maybe that's bad practice?). At this point more than anything I just want to understand why I'm able to do what I'm trying to do for non-required properties- even primitives- but not for required properties. It even works if I mark it as required on one class but not the other.Jaimeejaimes

© 2022 - 2024 — McMap. All rights reserved.