AutoMapper / AutoMapper.Collection.EFCore

EFCore support for AutoMapper.Collections

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

InsertOrUpdate tries to modify the key value

thomasrea0113 opened this issue · comments

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)

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.