InsertOrUpdate tries to modify the key value
thomasrea0113 opened this issue · 1 comments
thomasrea0113 commented
I've put together a simple example to hopefully illustrate the problem:
The Model
public abstract class BaseModel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public virtual string Id { get; set; }
public override bool Equals(object obj) => obj is BaseModel model &&
Id == model.Id;
public override int GetHashCode() => HashCode.Combine(Id);
}
public class AssigneeModel : BaseModel
{
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
public virtual ICollection<AssigneeEventPosition> AssigneeEventPositions { get; set; }
public override bool Equals(object obj)
=> obj is AssigneeModel model &&
Name == model.Name &&
Email == model.Email;
public override int GetHashCode()
=> HashCode.Combine(Id, Name, Email, AssigneeEventPositions);
}
public class AssigneeModel : BaseModel
{
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
public virtual ICollection<AssigneeEventPosition> AssigneeEventPositions { get; set; }
public override bool Equals(object obj)
=> obj is AssigneeModel model &&
Name == model.Name &&
Email == model.Email;
public override int GetHashCode()
=> HashCode.Combine(Id, Name, Email, AssigneeEventPositions);
}
The Mapper
services.AddAutoMapper((provider, cfg) =>
{
cfg.AddCollectionMappers();
cfg.UseEntityFrameworkCoreModel<AppDbContext>(provider);
cfg.CreateMap<AssigneeModel, AssigneeViewModel>().ReverseMap();
}, typeof(AppDbContext).Assembly);
The Context
public class AppDbContext : DbContext
{
public AppDbContext([NotNullAttribute] DbContextOptions options) : base(options)
{
}
public DbSet<AssigneeModel> Assignees { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<AssigneeModel>()
.HasIndex(e => e.Name).IsUnique();
builder.Entity<AssigneeModel>()
.HasIndex(e => e.Email).IsUnique();
builder.Entity<AssigneeModel>()
.HasIndex(e => new { e.Name, e.Email }).IsUnique();
}
The Problematic Code
var assigneeView = new AssigneeViewModel
{
Id = "baebf5b1-a768-422c-8883-ec39b16e5c82",
Email = "TESTING_UPDATE@gmail.com"
};
_db.Assignees.Persist(_mapper).InsertOrUpdate(assigneeView);
await _db.SaveChangesAsync(token);
Packages Used
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.7" />
<PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="Automapper.Collection.EntityFrameworkCore" Version="7.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
And finally... the exception.
System.InvalidOperationException: The property 'AssigneeModel.Id' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then associate the dependent with the new principal.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetPropertyModified(IProperty property, Boolean changeState, Boolean isModified, Boolean isConceptualNull, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.g__SetPropertyModified|12_0(<>c__DisplayClass12_0& , <>c__DisplayClass12_1& , <>c__DisplayClass12_2& )
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.LocalDetectChanges(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.DetectChanges()
at Microsoft.EntityFrameworkCore.DbContext.TryDetectChanges()
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at PlantScheduler.Pages.SignUpModel.OnPostEventsAsync(DataSourceRequest request, EventViewModel task, CancellationToken token) in C:\Users\reat\Documents\git-repos\plant-scheduler\PlantScheduler\Pages\SignUp.cshtml.cs:line 81
at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Convert[T](Object taskAsObject)
at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments)
at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Tasteful commented
You ar manually creating the map with the p<AssigneeModel, AssigneeViewModel>().ReverseMap();
that will be used instead if finding the mapping from efcore model.
If you want manual mapping you need to add the equality expression as described in AM.Collection repo.