Home > Software engineering >  Identity framework tries to insert new object when I want to reference an existing object
Identity framework tries to insert new object when I want to reference an existing object

Time:11-23

I am making a web app using blazer and identity framework. I would like the object BoardYear to have a one-to-one relation with Member as chairman which is an IdentityUser class and selected by a dropdown.

When I try to insert a new BoardYear with an exciting Member as chairman I get the following SQL exception.

SqlException: Violation of PRIMARY KEY constraint 'PK_AspNetUsers'. Cannot insert duplicate key in object 'dbo.AspNetUsers'. The duplicate key value is (de20c079-ed99-4bf9-9474-d8eb1a05b5b6).

It seems like Entity Framework wants to add the referred Member to the database, which is not possible because it already exists.

The member class:

public class Member : IdentityUser
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Guid? ChairmanBoardYearId { get; set; }
    public BoardYear ChairmanBoardYear { get; set; }
    ...
}

Boardyear class:

public class BoardYear
{
    [Key]
    public Guid BoardYearId { get; set; }
    [DataType(DataType.Date)]
    public DateTime StartDate { get; set; }
    [DataType(DataType.Date)]
    public DateTime EndDate { get; set; }

    public Member Chairman { get; set; }
}

The relation is configured in the dbcontext like this:

public class ApplicationDbContext : IdentityDbContext<Member>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<BoardYear>()
            .HasOne(m => m.Chairman)
            .WithOne(b => b.ChairmanBoardYear)
            .HasForeignKey<Member>(m => m.ChairmanBoardYearId);

            base.OnModelCreating(builder);
    }

    public DbSet<BoardYear> BoardYears { get; set; }
    public DbSet<Commission> Commissions { get; set; }
    public DbSet<CommissionFine> CommissionFines { get; set; }
    public DbSet<MemberFine> MemberFines { get; set; }
    public DbSet<Meeting> Meetings { get; set; }
    public DbSet<MeetingFile> MeetingFiles { get; set; }
    public DbSet<MemberSanction> MemberSanctions { get; set; }
    public DbSet<CommissionSanction> CommissionSanctions { get; set; }
    public DbSet<YearGroup> YearGroups { get; set; }
    public DbSet<CommissionList> CommissionLists { get; set; }
}

When the dropdown is edited the chairman is assigned like this in the razor page (I can see it gets the correct member):

async void OnBoardUserChange()
{
    Member selectedMember = await MemberService.FindMember(selectedChairmanId);
    curBoardYear.Chairman = selectedMember;
}

And then put in the database in the following service:

public class BoardYearService
    {
        private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
        private readonly DidaDbContext _db;

        public BoardYearService(IDbContextFactory<ApplicationDbContext> contextFactory)
        {
            _contextFactory = contextFactory;
        }

        public async Task<int> Create(BoardYear boardYear)
        {
            using (var ctx = _contextFactory.CreateDbContext())
            {
                CommissionList boardYearCommissionList = new CommissionList();
                boardYear.CommissionList = boardYearCommissionList;
                ctx.BoardYears.Add(boardYear);
                return await ctx.SaveChangesAsync();
            }
        }
    }

CodePudding user response:

The problem is that you have retrieved the user from whatever context instance MemberService.FindMember uses, but are referencing it in a BoardYear added to a new context instance.

The second context instance, where the BoardYear is added is not tracking the Member, so assumes it is an addition.

Either perform the whole action using data retrieved and persisted back through the same context instance, or just use an Id field on the BoardYear instead of a navigation property.

  • Related