Skip to content

InMemory and SQL Server provider seems to access TPH shared columns differently #25210

Closed as not planned

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions