Description
File a bug
While trying to find a way to access a shared column between multiple classes using TPH (see StackOverflow post here) I learned that there is a difference in how the SqlServer and InMemory providers handle accessing a shared column between multiple classes in TPH.
In SqlServer provider, I can access the shared columns for all overrides using a single cast of one of the types.
In InMemory provider, it is more specific so I need to add each override manually (actually more correct imo)
Depending on what is the proper way forward here, this issue is about:
a) Fixing a bug to ensure feature parity between the two providers so that
a1) They both need to be specific for each type (like InMemory today)
a2) They both will access the shared column when using a single type (like SqlServer today)
or
b) request a feature to access the shared column in a future proof way, perhaps with EF.Property as below (which does not currently work)
What do you suggest from the EF team?
Include your code
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 will access Message column in all overrides in SqlServer but not with InMemory
Message = (a as ActionB).Message,
// this works for both providers - 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; }
}
}
Expected output
Running the above code with any valid access method, I expect the output to be
1A
2B
3
for both providers.
Include provider and version information
EF Core version: 5.0.7 or 6.0.0-preview.5.21301.9
Database provider: SqlServer and InMemory
Target framework: .NET 5.0