You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have lots of entities for which I need to track when and by whom they were created and last updated. I have solved this by putting an owned type UserTimestamp on tracked entities as Created and Updated. Something like this:
This worked well until I needed to add tracking to the User model, probably because of the self referencing that this introduces. If I put Created and Updated as above on the User model, EF throws during model construction.
Below is a minimal reproduction:
usingMicrosoft.EntityFrameworkCore;usingvarcontext=newMyContext();Console.WriteLine(context.Model.ToDebugString());publicclassUser{publicintId{get;set;}publicUserTimestampCreated{get;set;}publicUserTimestampUpdated{get;set;}}[Owned]publicclassUserTimestamp{publicintUserId{get;set;}publicUserUser{get;set;}publicDateTimeTimestamp{get;set;}}publicclassMyContext:DbContext{publicDbSet<User>Users{get;set;}protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptions){options.UseSqlServer();}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<User>().OwnsOne(u =>u.Created, o =>o.WithOwner().HasForeignKey("OwnerUserId"));modelBuilder.Entity<User>().OwnsOne(u =>u.Updated, o =>o.WithOwner().HasForeignKey("OwnerUserId"));}}
which throws
Unhandled exception. System.InvalidOperationException: The object 'UserTimestamp' has been removed from the model.
at Microsoft.EntityFrameworkCore.Metadata.Internal.EntityType.get_Builder()
at Microsoft.EntityFrameworkCore.Metadata.Internal.EntityType.Microsoft.EntityFrameworkCore.Metadata.IConventionEntityType.get_Builder()
at Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationshipDiscoveryConvention.ProcessNavigationAdded(IConventionNavigationBuilder navigationBuilder, IConventionContext`1 context)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnNavigationAdded(IConventionNavigationBuilder navigationBuilder)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnNavigationAddedNode.Run(ConventionDispatcher dispatcher)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.DelayedConventionScope.Run(ConventionDispatcher dispatcher)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ConventionBatch.Run()
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ConventionBatch.Run(IConventionForeignKey foreignKey)
at Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1.OwnsOneBuilder[TRelatedEntity](TypeIdentity ownedType, MemberIdentity navigation)
at Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1.OwnsOne[TRelatedEntity](Expression`1 navigationExpression, Action`1 buildAction)
at MyContext.OnModelCreating(ModelBuilder modelBuilder) in .../Program.cs:line 33
at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
at Microsoft.EntityFrameworkCore.DbContext.get_Model()
at Program.<Main>$(String[] args) in .../Program.cs:line 4
However, if I remove the Updated property from the type and the configuration, the following model is created, which looks as expected:
Model:
EntityType: User
Properties:
Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
Navigations:
Created (UserTimestamp) ToDependent UserTimestamp
Keys:
Id PK
EntityType: UserTimestamp Owned
Properties:
OwnerUserId (no field, int) Shadow Required PK FK AfterSave:Throw
Timestamp (DateTime) Required
UserId (int) Required FK Index
Navigations:
User (User) ToPrincipal User
Keys:
OwnerUserId PK
Foreign keys:
UserTimestamp {'OwnerUserId'} -> User {'Id'} Unique Required Ownership Cascade ToDependent: Created
UserTimestamp {'UserId'} -> User {'Id'} Required Cascade ToPrincipal: User
Indexes:
UserId
Everything also works if I make a copy of the class UserTimestamp and use that copy for the Updated property (so that Created and Updated have nominally different, but otherwise equivalent, types).
It seems to me that since it works to have navigations back to owning entity type for the case when the owning type owns a type once, and also when it owns multiple (nominally different) types, the expected behaviour is that is should also work for the case when it owns the same type multiple times.
Include provider and version information
EF Core version: 9.0.0
Database provider: occurs with both SQLServer and Postgres providers
Target framework: .NET 9.0
Operating system: N/A
IDE: N/A
The text was updated successfully, but these errors were encountered:
UserTimestamp {'OwnerUserId'} -> User {'Id'} Unique Required Ownership Cascade ToDependent: Created
UserTimestamp {'UserId'} -> User {'Id'} Required Cascade ToPrincipal: User
you have two UserId columns in your Owned entity.
As your User "Owns" a "UserTimestamp" there is an implicit "UserId"-Column in that entity.
You now added a second UserId and a Navigation-Property to that Second userid
i imagine just removing the UserId from UserTimestamp would be enough to fix it
@kirides Yes, that is by design. The UserTimestamp is owned by a User, but it also points to a User (the user that made the change to the owning User). This works well when UserTimestamp is placed on any other entity than User, or when User owns only one UserTimestamp, but not when it owns multiple.
@alexanderchr what happens if modify it to this?
I could imagine the model builder trying to use "User" as the navigation property for the "OwnedUserId"-Shadow property and not correctly using it for "UserId" in that case
[ForeignKey(nameof(User))]publicintUserId{get;set;}
public User User {get;set;}
I get the same error with that change. But as you can see from the model with Updated commented out, the "has one" relation to User is picked up correctly and is using the right foreign key. I don't understand what changes when I add the second UserTImestamp, but I think it must be EF confusing the two relations internally.
I have lots of entities for which I need to track when and by whom they were created and last updated. I have solved this by putting an owned type
UserTimestamp
on tracked entities asCreated
andUpdated
. Something like this:This worked well until I needed to add tracking to the
User
model, probably because of the self referencing that this introduces. If I putCreated
andUpdated
as above on theUser
model, EF throws during model construction.Below is a minimal reproduction:
which throws
However, if I remove the
Updated
property from the type and the configuration, the following model is created, which looks as expected:Everything also works if I make a copy of the class
UserTimestamp
and use that copy for theUpdated
property (so thatCreated
andUpdated
have nominally different, but otherwise equivalent, types).It seems to me that since it works to have navigations back to owning entity type for the case when the owning type owns a type once, and also when it owns multiple (nominally different) types, the expected behaviour is that is should also work for the case when it owns the same type multiple times.
Include provider and version information
EF Core version: 9.0.0
Database provider: occurs with both SQLServer and Postgres providers
Target framework: .NET 9.0
Operating system: N/A
IDE: N/A
The text was updated successfully, but these errors were encountered: