Same property name in several child entities with Entity Framework Core 2.0

2.2k Views Asked by At

I'm currently using the simple model below. It's pretty straightforward: we have resources and they can be Room, EmptyOffice (...) or Service.

Room and EmptyOffice can have a capacity, but NOT Service.

public abstract class Resource : Entity
{
    public string Name { get; set; }
}

public class Room : Resource
{
    public int Capacity { get; set; }
}

public class EmptyOffice : Resource
{
    public int Capacity { get; set; }
}

public class Service : Resource
{ }

To get the data from my SQL view, I use the mappings:

builder.Entity<Resource>(m =>
{
    m.ToTable("resource", "facility");
    m.HasKey(x => x.Id);
    m.Property(x => x.Id)
        .HasColumnName("ResourceId");
    m.Property(x => x.Type)
        .HasColumnName("ResourceTypeId");

    m.HasDiscriminator(x => x.Type)
        .HasValue<Room>(ResourceType.Room)
        .HasValue<EmptyOffice>(ResourceType.EmptyOffice)
        .HasValue<Service>(ResourceType.Service);
});

builder.Entity<Room>();
builder.Entity<EmptyOffice>();
builder.Entity<Service>();

When I run my code, EF Core throws the following exception:

System.Data.SqlClient.SqlException: 'Invalid column name 'Room_Capacity'.'

If I rename the Capacity property to Room_Capacity, it works but it's horrible.

How can I force EF Core 2.0 to target the capacity property for each of my child entities?

Thank you Sebastien

3

There are 3 best solutions below

4
Ricardo Peres On BEST ANSWER

You can't do that as the only inheritance pattern available in EF Core is table per class hierarchy. If you go with interfaces instead of base classes, you can, but each entity will be mapped to a different table. Mark any property you want to exclude with [NotMapped], or, using the code, with Ignore.

0
Yrrol On

This worked for me:

builder.Entity<Room>().Property(a => a.Capacity).HasColumnName("Capacity");

builder.Entity<EmptyRoom>().Property(a => a.Capacity).HasColumnName("Capacity");
0
grinay On

I did in project next code to make it more generic way.

        private static void FindAndConfigureBackgroundJobResultTypes(ModelBuilder modelBuilder)
        {
            var backgroundJobResultTypes = typeof(BackgroundJobResult).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(BackgroundJobResult))).ToList();

            var sameTypeAndNameProperties = backgroundJobResultTypes
                .SelectMany(x => x.GetProperties())
                .GroupBy(d => new {d.Name, d.PropertyType})
                .Select(grp => new
                {
                    PropertyType = grp.Key.PropertyType,
                    PropertyName = grp.Key.Name,
                    Count = grp.Count()
                })
                .Where(x => x.Count > 1).ToList();


            foreach (var backgroundJobResultType in backgroundJobResultTypes)
            {

                //Set base type , instead of exposing this type by DbSet
                modelBuilder.Entity(backgroundJobResultType).HasBaseType(typeof(BackgroundJobResult));

                //Map properties with the same name and type into one column, EF Core by default will create separate column for each type, and make it really strange way. 
                foreach (var propertyInfo in backgroundJobResultType.GetProperties())
                {
                    if (sameTypeAndNameProperties.Any(x => x.PropertyType == propertyInfo.PropertyType && x.PropertyName == propertyInfo.Name))
                    {
                        modelBuilder.Entity(backgroundJobResultType).Property(propertyInfo.PropertyType, propertyInfo.Name).HasColumnName(propertyInfo.Name);
                    }
                }
            }
        }