When I use SfGrid to update I get the following Error from EF Core. This happens onlye when I use SfGrid, it does not happen if I use the same mechanic, if I use my own custom Table.
I don't understand what I have to to differently in sfgrid.
Error: System.InvalidOperationException: The instance of entity type 'Trigger' cannot be tracked because another instance with the key value '{TriggerId: 6727b31f-9c74-4394-9723-ce785b93850f}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Using SFGrid code
@page "/ManageTriggers"
@inherits UserInfoComponent
@using Action = Syncfusion.Blazor.Grids.Action
@inject TriggerRepository TriggerRepository
@inject ToastService ToastService
@inject UserService UserService
@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>
<GridColumns>
<GridColumn Field="@nameof(Trigger.TriggerId)"></GridColumn>
<GridColumn Field="@nameof(Trigger.TriggerTitle)"></GridColumn>
<GridColumn Field="@nameof(Trigger.IsPublic)" Type="ColumnType.Boolean"></GridColumn>
<GridColumn>
<Template>
@{ var trigger = (context as Trigger);}
<p>@trigger.Owner.Email</p>
</Template>
</GridColumn>
</GridColumns>
<GridEditSettings
AllowAdding="true"
AllowDeleting="true"
AllowEditing="true">
</GridEditSettings>
</SfGrid>
@code {
IEnumerable<Trigger> Triggers { get; set; } = new List<Trigger>();
IEnumerable<ApplicationUser> Users { get; set; } = new List<ApplicationUser>();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
Users = await UserService.GetAllUsers();
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.TriggerTitle, (await GetUserAsync()).Email);
}
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();
}
}
}
Using normal html Table, works fine:
@page "/ManageTriggersClassic"
@using MigraineDiary_Common
@inject TriggerRepository TriggerRepository
@attribute [Authorize(Roles = Sd.AdminRole)]
<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>
<SfCheckBox @bind-Checked="@trigger.IsPublic"></SfCheckBox>
</td>
<td>
@trigger.Owner.UserName
</td>
<td>
<SfComboBox Placeholder="Select Category"
@bind-Value="@trigger.TriggerCategory" DataSource="TriggerCategories"
AllowCustom="false"
AllowFiltering="false">
<ComboBoxFieldSettings Value="Id" Text="Title"></ComboBoxFieldSettings>
</SfComboBox>
</td>
<td>
<button class="btn btn-primary" @onclick="@(args => { Update(args, trigger); })">Update</button>
</td>
</tr>
}
</table>
</div>
@code {
IEnumerable<Trigger> Triggers { get; set; } = new List<Trigger>();
IEnumerable<TriggerCategory> TriggerCategories { get; set; } = new List<TriggerCategory>();
protected override async Task OnInitializedAsync()
{
TriggerCategories = TriggerRepository.GetTriggerCategories();
var getResult = await TriggerRepository.GetAll();
Triggers = getResult;
}
async void Update(MouseEventArgs obj, Trigger trigger)
{
await TriggerRepository.Update(trigger);
}
}
Hi Patrick,
Greetings from Syncfusion support.
We have analyzed the shared codes. We could see that you have not defined IsPrimaryKey for any of the columns in grid. When performing CRUD in grid, it is must to define a unique valued column as primary key column in Grid. So we suggest you to ensure to add IsPrimaryKey for a unique valued column in grid.
https://blazor.syncfusion.com/documentation/datagrid/editing
And also, we could see that you have re-fetched the data from your service and assigned to grid’s DataSource property based on Action as Save inside OnActionComplete. We are suspecting that this might have caused the reported problem. Grid will internally handle the refresh when perform CRUD actions in grid. So we suggest you to check this by removing the below highlighted codes(in red) in your application.
|
async Task ActionComplete(Syncfusion.Blazor.Grids.ActionEventArgs<Trigger> arg) { if (arg.RequestType.Equals(Syncfusion.Blazor.Grids.Action.Save)) { await RefreshTriggers(); } }
|
Please check this using the above suggestions and if you are still facing difficulties then kindly get back to us with the entire stacktrace of the exception you are facing.
Regards,
Renjith R
Hi Renijith
Thanks for your reply. I tried your suggestions, but I still get the same error.
Here the adjusted code:
@page "/ManageTriggers"
@inherits UserInfoComponent
@using Action = Syncfusion.Blazor.Grids.Action
@inject TriggerRepository TriggerRepository
@inject ToastService ToastService
@inject UserService UserService
@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>
<GridColumns>
<GridColumn Field="@nameof(Trigger.TriggerId)" IsPrimaryKey="true"></GridColumn>
<GridColumn Field="@nameof(Trigger.TriggerTitle)"></GridColumn>
<GridColumn Field="@nameof(Trigger.IsPublic)" Type="ColumnType.Boolean"></GridColumn>
@*<GridColumn>
<Template>
@{ var trigger = (context as Trigger);}
<p>@trigger.Owner.Email</p>
</Template>
</GridColumn>*@
</GridColumns>
</SfGrid>
@code {
IEnumerable<Trigger> Triggers { get; set; } = new List<Trigger>();
IEnumerable<ApplicationUser> Users { get; set; } = new List<ApplicationUser>();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
Users = await UserService.GetAllUsers();
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.TriggerTitle, (await GetUserAsync()).Email);
}
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();
}
}
}
These are the repo methods:
using Microsoft.EntityFrameworkCore;
using MigraineDiary_Common;
using MigraineDiary_DataAccess.Data;
using MigraineDiary_Models;
namespace MigraineDiary_Business.Repository;
public class TriggerRepository
{
IDbContextFactory<ApplicationDbContext> _dbContextFactory;
ApplicationDbContext Db { get; }
public TriggerRepository(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_dbContextFactory = contextFactory;
Db = contextFactory.CreateDbContext();
}
public async Task<Trigger> Update(Trigger trigger)
{
try
{
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.Include(trigger => trigger.Owner).AsNoTracking();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public IEnumerable<TriggerCategory> GetTriggerCategories()
{
try
{
return Db.TriggerCategories;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
Hi Patrick,
Greetings from Syncfusion support.
We have analyzed your query and fixed this issue by detaching the local entry and attach the updated entry before saving (like below code snippet). We suggest you to use the same way to resolve the issue in your project.
|
//To Update the records of a particluar Order public void UpdateOrder(Order Order) { try { var local = db.Set<Order>().Local.FirstOrDefault(entry => entry.OrderID.Equals(Order.OrderID)); // check if local is not null if (local != null) { // detach db.Entry(local).State = EntityState.Detached; } db.Entry(Order).State = EntityState.Modified; db.SaveChanges(); } catch { throw; } } |
Please get back to us if you are facing any further issues
Regards,
Sarveswaran PK
Dear Sarvesan
Thank you this solution does work so far.
But I still do not understand, why this happens only with SfGrid. Is this the preferred way to use SfGrid with DbContext or is there a cleaner solution you would suggest?
Also what would be the solution in a DbContext, connected scenario?
Br Patrick
Hi Patrick,
From your code example we found that you have tried to add the record to database when RequestType is Add. We suggest you insert the record when RequestType is Save. For both Insert and Update action, RequestType will be Save only. We can differentiate the two action using Action argument.
Refer the below general link to know more details about this issue.
https://stackoverflow.com/a/42475617/9634972
Please get back to us if you have further queries.
Regards,
Sarveswaran PK