When using shared columns in an EF Core TPH setup, is it possible to access the shared column during projection?
class Program
{
public static readonly ILoggerFactory MyLoggerFactory
= LoggerFactory.Create(builder => {
builder.AddConsole();
});
static async Task Main(string[] args)
{
using (var context = new ClientContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var actions = await context.Actions
.Select(a => new
{
Id = a.Id,
// this works - but really messy and complex in real world code
Message = (a as ActionA).Message ?? (a as ActionB).Message,
// this throws "Either the query source is not an entity type, or the specified property does not exist on the entity type."
// is there any other way to access the shared column Message?
// Message = EF.Property<string>(a, "Message"),
})
.ToListAsync();
actions.ForEach(a => Console.WriteLine(a.Id + a.Message));
}
}
public class ActionBase
{
public int Id { get; set; }
// ... other shared properties
}
public class ActionA : ActionBase
{
// shared with B
[Required]
[Column("Message")]
public string Message { get; set; }
// ... other specific properties
}
public class ActionB : ActionBase
{
// shared with A
[Required]
[Column("Message")]
public string Message { get; set; }
// ... other specific properties
}
public class ActionC : ActionBase
{
public string SomethingElse { get; set; }
// ... other specific properties
}
class ClientContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// TO USE SQL
//optionsBuilder
// .UseLoggerFactory(MyLoggerFactory)
// .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TPHSharedColumn;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30")
// .EnableSensitiveDataLogging(false);
// TO USE INMEMORY
optionsBuilder
.UseLoggerFactory(MyLoggerFactory)
.UseInMemoryDatabase(Guid.NewGuid().ToString());
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ActionA>().HasData(new ActionA()
{
Id = 1,
Message = "A"
});
builder.Entity<ActionB>().HasData(new ActionB()
{
Id = 2,
Message = "B"
});
builder.Entity<ActionC>().HasData(new ActionC()
{
Id = 3,
SomethingElse = "C"
});
}
public DbSet<ActionBase> Actions { get; set; }
}
}
In this simple example, it would of course be possible to move Message
to the base class - but that would make it possible to accidentally add an ActionC
with a Message
since I would need to remove the Required
attribute.
I also know I could add a ActionWithRequiredMessage
intermediate class to inherit ActionA
and ActionB
with, but again - in the much more complex real world example this is not feasible since there are also other shared columns and C# does not allow inheriting from multiple classes - and EF Core does not seem to like to use interfaces for this.
I simply would like to find a way to directly access the shared column - and use it in a projection.
Anyone know if this is possible?
I can't find it documented, but in EF Core 5.x you can access the shared column using any of the derived entities having a property mapped to it, e.g. all these work
Message = (a as ActionA).Message,
Message = (a as ActionB).Message,
Message = ((ActionA)a).Message,
Message = ((ActionB)a).Message,