public class Computer { public int ID { get; set; } public string Name { get; set; } public string InventoryNumber { get; set; } public Guarantee Guarantee { get; set; } public string Comments { get; set; } } |
public class Guarantee { public int Id { get; set; } public string DocumentNumber { get; set; } public DateTime IssueDate { get; set; } public DateTime ExpirationDate { get; set; } public string ImagePath { get; set; } } |
This is the way grid is being displayed
. . . @inject ApplicationDbContext dbContext <h3>Computers</h3> <SfGrid @ref="gridComputersList" DataSource="computersList" AllowFiltering="true" AllowPaging="true" AllowSorting="true" Toolbar="@(new List<string>() { "Add", "Edit", "Delete", "Cancel", "Update", "Search" })"> <GridFilterSettings Type="Syncfusion.Blazor.Grids.FilterType.Menu"></GridFilterSettings> <GridEditSettings AllowAdding="true" AllowDeleting="true" AllowEditing="true" AllowEditOnDblClick="true" Mode="EditMode.Dialog" Dialog="DialogParams"> <Template> @{ var computer = (context as Computer); <div> <div class="form-row"> <div class="form-group col-md-12"> <label>Computer Name</label> <SfTextBox FloatLabelType="FloatLabelType.Always" @bind-Value="@(computer.Name)" /> </div> <div class="form-group col-md-12"> <label>Inventory Number</label> <SfTextBox FloatLabelType="FloatLabelType.Always" @bind-Value="@(computer.InventoryNumber)" /> </div> <div class="form-group col-md-12"> <label>Comments</label> <SfTextBox FloatLabelType="FloatLabelType.Always" @bind-Value="@(computer.Comments)" /> </div> <div class="form-group col-md-12"> <label>Document Number</label> <SfTextBox FloatLabelType="FloatLabelType.Always" @bind-Value="@(computer.Guarantee.DocumentNumber)" /> </div> <div class="form-group col-md-12"> <label>Issue Date</label> <SfDatePicker FloatLabelType="FloatLabelType.Always" @bind-Value="@(computer.Guarantee.IssueDate)"></SfDatePicker> </div> <div class="form-group col-md-12"> <label>Expiration Date</label> <SfDatePicker FloatLabelType="FloatLabelType.Always" @bind-Value="@(computer.Guarantee.ExpirationDate)"></SfDatePicker> </div> </div> </div> } </Template> </GridEditSettings> <GridEvents OnActionComplete="OnGridActionCompliete" TValue="Computer"></GridEvents> <GridColumns> <GridColumn Field="@nameof(Computer.ID)" IsIdentity="true" IsPrimaryKey="true" Visible="false"></GridColumn> <GridColumn Field="@nameof(Computer.Name)"></GridColumn> <GridColumn Field="@nameof(Computer.InventoryNumber)"></GridColumn> <GridColumn Field="@nameof(Computer.Comments)"></GridColumn> <GridColumn Field="Guarantee.DocumentNumber"></GridColumn> <GridColumn Field="Guarantee.IssueDate" Format="d" Type="ColumnType.Date" DefaultValue="DateTime.Now"></GridColumn> <GridColumn Field="Guarantee.ExpirationDate" Format="d" Type="ColumnType.Date" DefaultValue="DateTime.Now.AddYears(1)"></GridColumn> <GridColumn HeaderText="Image" AllowEditing="false"> <Template> @{ var guarantee = (context as Computer).Guarantee; <div class="align-items-center d-flex justify-content-center"> @if (string.IsNullOrEmpty(guarantee.ImagePath) || !File.Exists("wwwroot" + guarantee.ImagePath)) { <div> <button class="btn" @onclick="@(e => OpenPopUp(guarantee.Id))"><span class="iconify" data-icon="grommet-icons:document-upload" data-inline="false"></span></button> </div> } else { <div> <button class="btn" @onclick="@(e=>ShowImageAsync(guarantee.ImagePath))"><span class="iconify" data-icon="bi:image" data-inline="false"></span></button> <button class="btn" @onclick="@(e => DeleteImage(guarantee.Id))"><span class="iconify" data-icon="fluent:delete-20-regular" data-inline="false"></span></button> </div> } </div> } </Template> </GridColumn> </GridColumns> </SfGrid> . . . @code { List<Computer> computersList = new List<Computer>(); private SfGrid<Computer> gridComputersList; private DialogSettings DialogParams = new DialogSettings { MinHeight = "400px", Width = "450px" }; bool UploadIsVisible = false; private int dialogItemId = 0; protected override async Task OnInitializedAsync() { computersList = await dbContext.Computers.Include(x => x.Guarantee).ToListAsync(); } private void OnGridActionCompliete(ActionEventArgs<Computer> args) { var x = args; switch (args.RequestType) { case Syncfusion.Blazor.Grids.Action.Save: if (args.Data.ID == 0) { dbContext.Computers.Add(args.Data); } dbContext.SaveChanges(); //Exception gridComputersList.Refresh(); break; case Syncfusion.Blazor.Grids.Action.Delete: dbContext.Computers.Remove(args.Data); dbContext.SaveChanges(); break; default: break; } } . . . } |
Add and delete functions work without any issues, but when I try to edit any value of an existing item in the Grid I'm getting an exception on dbContext.SaveChanges() line e.g.
System.InvalidOperationException HResult=0x80131509 Message=The instance of entity type 'Guarantee' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values. |
And this seems to happen only when I'm passing a list where entry has some navigation property. For example, if I hide columns where Field has Guranatee and dont do .Include(x => x.Guarantees) then edit is working as expected.
How to resolve this?
Thanks!
I do have the same Issue, but "NoTracking" as described in the articles do not solve the problem and I dbContext is injected scoped.
If run an update from a classic HTML Table everything runs smooth, but when I use SFGrid I get the Error " The instance of entity type 'Guarantee' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked".
<h3>ManageTriggersClassic</h3>
<div class="container">
<table class="table table-bordered">
<tr>
<th>ID</th>
<th>Title</th>
<th>Is Public</th>
<th>Owner</th>
<th>Category</th>
</tr>
@foreach (var trigger in Triggers)
{
<tr>
<td>@trigger.TriggerId.ToString()</td>
<td>
<SfTextBox @bind-Value="@trigger.TriggerTitle"></SfTextBox>
</td>
<td>
<button class="btn btn-primary" @onclick="@(args => { Update(args, trigger); })">Update</button>
</td>
</tr>
}
</table>
</div>
@code {
public IEnumerable<Trigger> Triggers { get; set; } = new List<Trigger>();
protected override async Task OnInitializedAsync()
{
var getResult = await TriggerRepository.GetAll();
if (getResult.IsSuccess)
{
Triggers = getResult.Result;
}
}
async void Update(MouseEventArgs obj, Trigger trigger)
{
await TriggerRepository.Update(trigger);
}
@page "/ManageTriggers"
@using MigraineDiary_Common
@using Action = Syncfusion.Blazor.Grids.Action
@inject TriggerRepository TriggerRepository
@inject ToastService ToastService
@attribute [Authorize(Roles = Sd.AdminRole)]
<h3>ManageTriggers</h3>
<SfGrid TValue="Trigger" DataSource="@Triggers"
Toolbar="@(new List<string>() { "Add", "Edit", "Delete", "Cancel", "Update" })">
<GridEvents OnActionBegin="ActionBegin" OnActionComplete="ActionComplete" TValue="Trigger"></GridEvents>
<GridEditSettings
AllowAdding="true"
AllowDeleting="true"
AllowEditing="true">
</GridEditSettings>
</SfGrid>
@code {
IEnumerable<Trigger> Triggers { get; set; } = new List<Trigger>();
protected override async Task OnInitializedAsync()
{
await RefreshTriggers();
}
async Task RefreshTriggers()
{
var getAllResult = TriggerRepository.GetAllAsNoTracking();
Triggers = getAllResult;
}
async Task ActionBegin(Syncfusion.Blazor.Grids.ActionEventArgs<Trigger> arg)
{
if (arg.RequestType == Action.Save)
{
if (arg.Action == "Add")
{
await TriggerRepository.Add(arg.Data);
}
else // Update
{
await TriggerRepository.Update(arg.Data);
}
}
if (arg.RequestType == Action.Delete)
{
await TriggerRepository.Delete(arg.Data);
}
}
async Task ActionComplete(Syncfusion.Blazor.Grids.ActionEventArgs<Trigger> arg)
{
if (arg.RequestType.Equals(Syncfusion.Blazor.Grids.Action.Save))
{
await RefreshTriggers();
}
}
}
public class TriggerRepository
{
IDbContextFactory<ApplicationDbContext> _dbContextFactory;
ApplicationDbContext Db { get; }
public TriggerRepository(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_dbContextFactory = contextFactory;
Db = contextFactory.CreateDbContext();
}
public async Task<OperationResult<ICollection<Trigger>>> GetAll()
{
try
{
var getResult = Db.Triggers.ToList();
return OperationResult<ICollection<Trigger>>.Success(getResult);
}
catch (Exception e)
{
return OperationResult<ICollection<Trigger>>.Failure(ResultType.BadRequest, e.Message);
}
}
public IQueryable<Trigger> GetAllAsQueryable()
{
try
{
return Db.Triggers.AsQueryable();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public async Task<Trigger> Update(Trigger trigger)
{
try
{
Db.Entry(trigger).State = EntityState.Detached;
Db.Update(trigger);
var saveResult = await Db.SaveChangesAsync();
return trigger;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public IEnumerable<Trigger> GetAllAsNoTracking()
{
try
{
return Db.Triggers.AsNoTracking();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public async Task Update(IEnumerable<Trigger> triggers)
{
try
{
Db.UpdateRange(triggers);
await Db.SaveChangesAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public async Task Add(Trigger trigger)
{
try
{
var addResult = Db.Triggers.Add(trigger);
await Db.SaveChangesAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public async Task Delete(Trigger trigger)
{
try
{
var removeResult = Db.Triggers.Remove(trigger);
await Db.SaveChangesAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
Hi Patrick,
We suggest you to follow up on this thread for future updates on this query.
Regards,
Renjith R