Open alexandis opened 3 months ago
This issue is lacking enough information for us to be able to fully understand what is happening. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.
Please find attached the SIMPLIFIED version. However, it looks like the root cause is the same. Just in case, I am also adding front-end part - the error is reproduced when trying to run the site.
The error in backend: "Invalid column name 'Discriminator'".
The expected and required behavior: both ExtendedGood
and Good
classes work without a hitch with the same DB table.
And - yes, we tried to use builder.HasOne<Tenant>().WithOne()
approach (as someone suggested for the this kind of issues) for TenantInner
entity, but it did not work for all the scenarios. To me, it looks more like a hack since there is no TenantInner
which "has" Tenant
or vice versa: Tenant
"is" TenantInner
.
@alexandis I don't see any error when running your code. Can you post the full exception and stack trace?
Sure. When running front-end - https://localhost:4300
(pulling data from back-end) - getting this. You will see this exception in the console of the running host:
info: HttpApi.Controllers.GoodController[0] Trying to retrieve goods fail: Microsoft.EntityFrameworkCore.Database.Command[20102] Failed executing DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT [g].[Id], [g].[Description], [g].[Discriminator], [g].[Name] FROM [Good] AS [g] fail: Microsoft.EntityFrameworkCore.Query[10100] An exception occurred while iterating over the results of a query for context type 'EntityFramework.SashaTestDbContext'. Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'Discriminator'. at Microsoft.Data.SqlClient.SqlCommand.<>c.
b__211_0(Task 1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask
2.InnerInvoke() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) --- End of stack trace from previous location --- at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func
4 operation, Func4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable
1.AsyncEnumerator.MoveNextAsync() ClientConnectionId:349f16e3-ab59-4b68-9184-a0459b2e47b6 Error Number:207,State:1,Class:16 Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'Discriminator'. at Microsoft.Data.SqlClient.SqlCommand.<>c.b__211_0(Task 1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask
2.InnerInvoke() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) --- End of stack trace from previous location --- at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func
4 operation, Func4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable
1.AsyncEnumerator.MoveNextAsync() ClientConnectionId:349f16e3-ab59-4b68-9184-a0459b2e47b6 Error Number:207,State:1,Class:16
@alexandis You configuration creates a mapped inheritance hierarchy using TPH mapping:
Model:
EntityType: ExtendedGood Base: Good
EntityType: Good
Properties:
Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
Description (string)
Discriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(13)
Name (string) Required
Keys:
Id PK
This means that the table needs to have a discriminator column in order to determine which of the two types to create when executing a query. EF Core doesn't support having multiple types mapped to the same table, but without any inheritance mapping or relationship between the two types. This is because EF cannot determine the type to create for each row read.
Note for team: should we allow this with shared-type entity types?
should we allow this with shared-type entity types?
No. We still need a discriminator to determine which entity type to use. If we were to allow instantiating either then it would become a particular case of https://github.com/dotnet/efcore/issues/15310
Hmm... So what is the way out in my situation? I have a base type in Nuget package. And I have the inherited types in the solutions which consume this Nuget package. Each of the inherited types have Navigation properties of the types, which I cannot move to the Nuget package. From DB perspective, the base type and the inherited types still reference the same DB table. Pretty common situation, IMHO...
Any update here?
@alexandis I don't believe there is any way to map those entity types. EF needs to understand the relationship between the types in order to determine what types to create and which navigations we refer to which types.
To me it looks like a dead end now. Or i don't understand some obvious solution.
I've simplified the example as much as I could to demonstrate the issue.
Type BaseA
resides in the solution A (NuGet).
Type ExtendedA
resides in the solution B (which consumes A) and inherites BaseA
. So it still IS a BaseA
, but has navigation properties that also reside in the solution B.
I've tried to use a hack builder.ApplyConfiguration(new BaseAConfig<BaseA>(builder => builder.HasOne<ExtendedA>().WithOne().HasPrincipalKey<ExtendedA>(e => e.Id)))
as recommended by someone, but it made updating or creating ExtendedA
complicated and raising exceptions.
Putting on the backlog to consider if we should do any kind of model validation against this.
NET8. I will try to describe the issue as briefly as possible.
1) Nuget package EntityConfig contains model-DB mappings; 2) Nuget package Base contains entity
TenantInner
and consumes EntityConfig: entityTenantInner
is bound to tableTENANT
; 3) Solution MySolution consumes packages EntityConfig and Base and contains entityTenant
which inheritsTenantInner
.Tenant
only contains navigation properties and naturally references the same table -TENANT
.The reason why I splitted
TenantInner
andTenant
:TenantInner
is used in many solutions.Tenant
contains several navigation properties - we do not want to pull them all into Base, since they are needed in MySolution only.Problem: I cannot make this structure work, getting either "A key cannot be configured on 'Tenant' because it is a derived type" or "ORA-00904: "C1"."DISCRIMINATOR": invalid identifier" (this one happens when trying to construct EF query joining
DbSet<Tenant>
property in DbContext) exceptions, depending on the way I've tried to resolve the issue.My latest setup (I don't think
TenantInner
structure is relevant here, so skipping):============ EntityConfig Nuget package ===========================================
============ MySolution ===========================================
I don't understand why it should not work: as I see it, when
DbSet<Tenant>
is referenced - the EF should construct the query usingTENANT
table and its navigation properties tables (if included -COMPANY
etc.). IfDbSet<TenantInner>
is referenced (in Base package) - the sameTENANT
table should be used. However, I don't know how to let EF know that "discriminator" is not relevant here.