Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4839

Entity Framework Core tries to create existing object

$
0
0

I'm currently learning EF Core and I came across some strange behaviour which I don't understand.

I have a Blazor component which inherits from a base class EditPageBase:

public abstract class EditPageBase<TModel, TIdentifier> : ActivePageBase{    protected EditForm? _form;    protected bool _showOptionsMenu;    [Parameter, SupplyParameterFromQuery(Name = "Id")]    public TIdentifier? Identifier { get; set; }    public TModel? Input { get; set; }    protected HSTrendDbContext DbContext { get; set; } = default!;    protected SemaphoreSlim DbLock { get; } = new(1, 1);    protected override async Task OnInitializedAsync()    {        DbContext = await DbFactory.CreateDbContextAsync();    }    protected override async Task OnParametersSetAsync()    {        await DbLock.WaitAsync();        Input = await LoadEditModeAsync();        DbLock.Release();           if (Input is not null)        {            await CheckActivePageAsync();        }    }    protected async Task<bool> SaveAsync()    {        if (_form is null || _form.EditContext is null || Input is null)        {            return false;        }        if (!_form.EditContext.Validate())        {            return false;        }        try        {            await DbContext.SaveChangesAsync();        }        catch (DbUpdateConcurrencyException)        {            throw;        }        Log.Information("Saved: {url}; Identifier: {identifier}; User: {user}", NavigationManager.Uri, Identifier?.ToString() ?? "<NEU>", CurrentUser?.DisplayName ?? "<UNBEKANNT>");        ToastService.ShowSuccess("Datensatz wurde erfolgreich gespeichert");        await OnParametersSetAsync();        return true;    }    protected abstract Task<TModel?> LoadEditModeAsync();    public override async ValueTask DisposeAsync()    {        await base.DisposeAsync();        DbLock.Dispose();        DbContext.Dispose();    }}

Now I have a class Recipient which looks like this:

public class Recipient{    public int RecipientId { get; set; }    public string Name { get; set; } = string.Empty;    public RecipientCategory Category { get; set; }    public bool SendEmail { get; set; }    public bool UseEmail { get; set; }    public string Email { get; set; } = string.Empty;    public bool IsActive { get; set; }    public IEnumerable<User> Users { get; set; } = new List<User>(); // DO NOT REFACTOR}

The class User looks like this:

public class User{    public int UserId { get; set; }    public string Username { get; set; } = string.Empty;    public Guid? ActiveDirectoryGuid { get; set; }    public string DisplayName { get; set; } = string.Empty;    public string Email { get; set; } = string.Empty;    public string Password { get; set; } = string.Empty;    public string Salt { get; set; } = string.Empty;    public string Origin { get; set; } = string.Empty;    public string DmsName { get; set; } = string.Empty;    public bool IsFieldSales { get; set; }    public bool IsActive { get; set; }    public bool IsActiveDirectoryAccount => Origin == "ad";    public string? AgentNumber { get; set; }    public decimal ApprovalLimit { get; set; }    public User? Supervisor { get; set; }    public List<UserRole> Roles { get; set; } = [];    public IEnumerable<Recipient> Recipients { get; set; } = new List<Recipient>(); // DO NOT REFACTOR    public List<ResponsibleEntity> ResponsibleEntities { get; set; } = [];}

The EditPageBase inherits from another base class which provides the following two methods:

protected virtual async Task OnSearchCostCentersAsync(OptionsSearchEventArgs<CostCenter> e){    try    {        var token = InitializeCancellationToken();        using var dbContext = await DbFactory.CreateDbContextAsync(token);        CostCenterFilter filter = new CostCenterFilter()        {            SearchPhrase = e.Text,        };        var results = await dbContext.GetCostCentersAsync(filter, token);        e.Items = results.Items;    }    catch (OperationCanceledException)    {    }}protected virtual async Task OnSearchUsersAsync(OptionsSearchEventArgs<User> e){    try    {        var token = InitializeCancellationToken();        using var dbContext = await DbFactory.CreateDbContextAsync(token);        UserFilter filter = new UserFilter        {            SearchPhrase = e.Text        };        var results = await dbContext.GetUsersAsync(filter, token);        e.Items = results.Items;    }    catch (OperationCanceledException)    {    }}

Those are being used to search within the database for users and cost centers. When I use this methods within my Razor component then it tries to create a new row for every user which I assign to my Recipient object. However it doesn't do the same thing for the cost centers which I use in another class.

public class MainArea{    public int MainAreaId { get; set; }    public string Name { get; set; } = string.Empty;    public IEnumerable<CostCenter> CostCenters { get; set; } = new List<CostCenter>(); // DO NOT REFACTOR}

I've created custom configurations for my classes which look like this:

public class MainAreaMapping : IEntityTypeConfiguration<MainArea>{    public void Configure(EntityTypeBuilder<MainArea> builder)    {        builder.ToTable("InvestmentMainAreas");        builder.Property(x => x.Name)            .HasMaxLength(50);        builder.HasMany(x => x.CostCenters)          .WithMany(x => x.MainAreas)          .UsingEntity<MainAreaCostCenter>          (              j => j                  .HasOne(d => d.CostCenter)                  .WithMany()                  .HasForeignKey(d => d.CostCenterId)                  .HasConstraintName("FK_InvestmentMainAreaToCostCenter_CostCenterId"),            j => j                    .HasOne(d => d.MainArea)                    .WithMany()                    .HasForeignKey(d => d.MainAreaId)                    .HasConstraintName("FK_InvestmentMainAreaToCostCenter_MainAreaId"),            j =>            {                j.HasKey(t => new { t.MainAreaId, t.CostCenterId });                j.ToTable("InvestmentMainAreaToCostCenter");            }          );    }}
public class RecipientMapping : IEntityTypeConfiguration<Recipient>{    public void Configure(EntityTypeBuilder<Recipient> builder)    {        builder.ToTable("Recipients");        builder.Property(x => x.Name)            .HasMaxLength(50);        builder.Property(x => x.Email)            .HasMaxLength(255);        builder.HasMany(x => x.Users)            .WithMany(x => x.Recipients)            .UsingEntity<RecipientUser>            (                j => j                    .HasOne(d => d.User)                    .WithMany()                    .HasForeignKey(d => d.UserId)                    .HasConstraintName("FK_RecipientUsers_UserId"),                j => j                    .HasOne(d => d.Recipient)                    .WithMany()                    .HasForeignKey(d => d.RecipientId)                    .HasConstraintName("FK_RecipientUsers_RecipientId"),                j =>                {                    j.HasKey(t => new { t.UserId, t.RecipientId });                    j.ToTable("RecipientUsers");                }            );    }}

My question now is: why does Entity Framework Core try to insert all users again into the database and why doesn't it try the same for the cost centers? I don't want to create the users twice. I just want to assign them just like I do with the cost centers.

I understand that the problem exists because I load the classes form different DbContext instances but I don't get why the classes behave differently although the look identical to me and they both use the same base class.

EDIT: when I run this code

foreach (var user in recipient.Users){    DbContext.Entry(user).State = EntityState.Unchanged;}

then the entries won't be created but the assignment will be properly made in the database. But why do I need to do this? And why do I not need to do this for my CostCenters?


Viewing all articles
Browse latest Browse all 4839

Trending Articles