I need help trying to understand why my Blazor (server-side) component in .net Core 6 is not updating my database when removing an entity, please take a look at my code sample: (beforehand, thanks for all your help!)
First, I'm injecting my db context as follow:
@inject AppDbContext db
then, below on my code, I decided to query for some data, in this case, Customers as follow:
@code {
private List<Customer> clients { get; set; }
protected override async Task OnInitializedAsync()
{
clients = await db.Customers.ToListAsync();
}
}
Now, I created a button with a method to delete the customer; first I search the entity, if found, then I remove the item from the "clients" list from the database, as follow:
private async Task DeleteCustomer(int CustomerId)
{
Customer? customer = clients.FirstOrDefault(f => f.Id == CustomerId);
if (customer is not null)
{
clients.Remove(customer);
await db.SaveChangesAsync();
}
}
THE PROBLEM is that the entity is removed from the list but not removed from the database, so when I refresh the item still there, I have to apply another command inside the if to make it work:
db.Remove(customer);
in other words, I have to remove it from the clients list and in the list also, making the work double, it seems to me it loses completely the connection (or ref) between the list coming from the DB and the database. This is the first time I see something like this, am I missing something? am I using the EF the way I am supposed to do? I can just add that command and make it work but I don't think is a good practice, please help!
CodePudding user response:
Yeah, that is because this is just how EF works.
The client list is contains the queried elements. Even after remove, it still is a queried element. There is no logical way for Ef to update clients - ESPECIALLY as Clients is a List which has no API for this in a sensible way.
So, you must remove it from both, which is logical.
Actually you get it totally wrong. Removing something from clients is not triggering anything from Ef because it makes no sense to start. I could remove them to THEN run code over it - and would trigger an automatic removal from the db just for filtering something out in the UI. Only the db.Remove actually tells Ef it is to remove it FROM THE DATABASE. This is not double, it is Ef not magically reading your mind.
CodePudding user response:
You'll need to use dbcontext factory
, like this:
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
ContactContext.cs
/// <summary>
/// Context for the contacts database.
/// </summary>
public class ContactContext : DbContext
{
/// <summary>
/// Magic string.
/// </summary>
public static readonly string RowVersion = nameof(RowVersion);
/// <summary>
/// Magic strings.
/// </summary>
public static readonly string ContactsDb = nameof(ContactsDb).ToLower();
/// <summary>
/// Inject options.
/// </summary>
/// <param name="options">The <see cref="DbContextOptions{ContactContext}"/>
/// for the context
/// </param>
public ContactContext(DbContextOptions<ContactContext> options)
: base(options)
{
Debug.WriteLine($"{ContextId} context created.");
}
/// <summary>
/// List of <see cref="Contact"/>.
/// </summary>
public DbSet<Contact> Contacts { get; set; }
/// <summary>
/// Define the model.
/// </summary>
/// <param name="modelBuilder">The <see cref="ModelBuilder"/>.</param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// this property isn't on the C# class
// so we set it up as a "shadow" property and use it for concurrency
modelBuilder.Entity<Contact>()
.Property<byte[]>(RowVersion)
.IsRowVersion();
base.OnModelCreating(modelBuilder);
}
/// <summary>
/// Dispose pattern.
/// </summary>
public override void Dispose()
{
Debug.WriteLine($"{ContextId} context disposed.");
base.Dispose();
}
/// <summary>
/// Dispose pattern.
/// </summary>
/// <returns>A <see cref="ValueTask"/></returns>
public override ValueTask DisposeAsync()
{
Debug.WriteLine($"{ContextId} context disposed async.");
return base.DisposeAsync();
}
}
Say you've got an Index page where you display a list of contacts:
@inject IDbContextFactory<ContactContext> DbFactory
@code
{
protected async override Task OnInitializedAsync()
{
// When the Index page is created the ReloadAsync method
// is called to retrieve a list of contact objects
}
// When you delete a contact, it is removed both from the database
// and from the list that hold the contacts
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;
var contact = await context.Contacts.FirstAsync(
c => c.Id == Wrapper.DeleteRequestId);
if (contact != null)
{
context.Contacts.Remove(contact);
await context.SaveChangesAsync();
}
Filters.Loading = false;
await ReloadAsync();
}
private async Task ReloadAsync()
{
if (Filters.Loading || Page < 1)
{
return;
}
Filters.Loading = true;
if (Wrapper != null)
{
Wrapper.DeleteRequestId = 0;
}
Contacts = null;
using var context = DbFactory.CreateDbContext();
// run the query to load the current page
Contacts = await
QueryAdapter.FetchAsync(context.Contacts.AsQueryable());
// now we're done
Filters.Loading = false;
}
}
Note: That is the way we code in Blazor. The most important feature is the Blazor component Model...re-rendering is ubiquitous. Each time you add delete or update a contact object, you'll need to create a new db context, do the action, and then let it die as well as updating your contact store (in memory list)
Please, copy the above as I'm soon going to delete it.
CodePudding user response:
You have to remove item from dbcontext, not from the list. After you created view, all your dbcontext was disposed and list is not connected. When you try to delete you have a different db context already. So you need this
public async Task DeleteCustomer(int CustomerId)
{
var customer = await db.Customers.FirstOrDefaultAsync(f => f.Id == CustomerId);
if (customer!=null)
{
db.Customers.Remove(customer);
var result= await db.SaveChangesAsync();
}
}
after deleting you can call
clients = await db.Customers.ToListAsync();
again and get an updated list of customers.