EF Core Update with List - asp.net

To make updates to a record of SQL Server using Entity Framework Core, I query the record I need to update, make changes to the object and then call .SaveChanges(). This works nice and clean.
For example:
var emp = _context.Employee.FirstOrDefault(item => item.IdEmployee == Data.IdEmployee);
emp.IdPosition = Data.IdPosition;
await _context.SaveChangesAsync();
But is there a standard method if I want to update multiple records?
My first approach was using a list passing it to the controller, but then I would need to go through that list and save changes every time, never really finished this option as I regarded it as not optimal.
For now what I do is instead of passing a list to the controller, I pass each object to the controller using a for. (kind of the same...)
for(int i = 0; i < ObjectList.Count; i ++)
{
/* Some code */
var httpResponseObject = await MyRepositories.Post<Object>(url+"/Controller", Object);
}
And then do the same thing on the controller as before, when updating only one record, for each of the records...
I don't feel this is the best possible approach, but I haven't found another way, yet.
What would be the optimal way of doing this?

Your question has nothing to do with Blazor... However, I'm not sure I understand what is the issue. When you call the SaveChangesAsync method, all changes in your context are committed to the database. You don't have to pass one object at a time...You can pass a list of objects
Hope this helps...

Updating records in bulk using Entity Framework or other Object Relational Mapping (ORM) libraries is a common challenge because they will run an UPDATE command for every record. You could try using Entity Framework Plus, which is an extension to do bulk updates.
If updating multiple records with a single call is critical for you, I would recommend just writing a stored procedure and call if from your service. Entity Framework can also run direct queries and stored procedures.
It looks like the user makes some changes and then a save action needs to persist multiple records at the same time. You could trigger multiple AJAX calls—or, if you need, just one.
What I would do is create an endpoint—with an API controller and an action—that's specific to your needs. For example, to update the position of records in a table:
Controller:
/DataOrder
Action:
[HttpPut]
public async void Update([FromBody] DataChanges changes)
{
foreach(var change in changes)
{
var dbRecord = _context.Employees.Find(change.RecordId);
dbRecord.IdPosition = change.Position;
}
_context.SaveChanges();
}
public class DataChanges
{
public List<DataChange> Items {get;set;}
public DataChangesWrapper()
{
Items = new List<DataChange>();
}
}
public class DataChange
{
public int RecordId {get;set;}
public int Position {get;set;}
}
The foreach statement will execute an UPDATE for every record. If you want a single database call, however, you can write a SQL query or have a stored procedure in the database and pass the data as a DataTable parameter instead.

Related

EF Core 3 issues with .Include() when using .AsExpandable()

I've migrated concepts from a couple of CQRS frameworks I've seen and just started facing some issues.
I have a common EntityDbContext subclass which I use in any consuming project without further extension to suit the domain of the application, rather I provide interfaces, IReadEntities and IWriteEntities which have methods like Query() and Get() which behind the scenes call Set() returning the DbSet() then allowing the standard LINQ expressions to be chained on as for any EF query. I'm facing issues around using Include() on my IQueryables as I'm using LinqKit with AsExpandable() at the end of all my calls. This is what my context Query methods look like
public new IQueryable<TEntity> Query<TEntity>() where TEntity : class, IEntity
{
// AsNoTracking returns entities that are not attached to the DbContext
return QueryUnfiltered<TEntity>().Where(_recordAuthority.Clause<TEntity>());
}
public IQueryable<TEntity> QueryUnfiltered<TEntity>() where TEntity : class, IEntity
{
// AsNoTracking returns entities that are not attached to the DbContext
return Set<TEntity>().AsNoTracking().AsExpandable();
}
A typical query handler looks like this:
public async Task<IEnumerable<GetCustomerView>> Handle(CustomersBy query, CancellationToken cancellationToken)
{
var customers = _db.Query<Customer>();
// Apply filters
if (!string.IsNullOrEmpty(query.FirstName))
customers = customers.Where(x => x.FirstName.Contains(query.FirstName));
if (!string.IsNullOrEmpty(query.LastName))
customers = customers.Where(x => x.LastName.Contains(query.LastName));
// Execute the query and return the results
var view = await customers.Select(x => new GetCustomerView
{
Id = x.Id,
FirstName = x.FirstName,
LastName = x.LastName,
EmailAddress = x.EmailAddress
}).ToListAsync(cancellationToken).ConfigureAwait(false) as IEnumerable<GetCustomerView>;
return view;
}
This scenario works fine if I wanted to pull address details from a related table as I use projection on the database serve given I'm using the Select prior to execution. There are scenarios though where it makes sense to pull an object graph back and specify Include(...) statements but as it stands specifying _db.Query<Customer>().Include(c => c.Address) doesn't hydrate the Address navigation property. I've tried leaving the AsExpandable() off and then the results come back.
The question is, does anyone see a way to allow the Include statements to be provided maybe as a parameter to the method and then I loop through them and tack them on before calling AsExpandable()? I can't quite get my head around how to do it, if it's possible.
Maybe there's another approach?
Interestingly this apparently works fine on a version of this pattern a colleague uses where they are using EF 6. He says they specify Include after the AsExpandable without a problem.
This is known issue with EF Core and LinqKit AsExpandable (and in general with any extension library which uses custom IQueryProvider to perform its query expression tree preprocessing like LinqKit), because EF Core ignores all EF Core specific IQueryable extensions (Include / ThenInclude, AsNoTracking etc.) it the query provider is different (or does not inherit) the EF Core one (EF6 has no such requirements).
With that being said, currently there is no other solution than applying all EF Core specific extensions before calling AsExpandable.
Ok this works. I created an overload:
public IQueryable<TEntity> Query<TEntity, TProperty>(IEnumerable<Expression<Func<TEntity, TProperty>>> includes) where TEntity : class, IEntity
{
var query = Set<TEntity>().AsNoTracking();
foreach (var expression in includes)
{
query = query.Include(expression);
}
return query.AsExpandable();
}
From my handler I create a list of include expressions and pass to the Query:
var includes = new List<Expression<Func<Customer, object>>>
{
c => c.Address
};
var customers = _db.Query(includes);
var result = await customers.ToListAsync(cancellationToken).ConfigureAwait(false);
Execution of the query has the navigation property populated:
Means I'm not 'Fluent'ly chaining them from the client code's perspective, but I don't think it's terrible.
Thoughts?

Linq-To-Sql SubmitChanges Not Updating Database

I've read multiple questions similar to this one but none are exactly my situation.
Using linq-to-sql I insert a new record and submit changes. Then, in the same web request, I pull that same record, and update it, then submit changes. The changes are not saved. The DatabaseContext is the same across both these operations.
Insert:
var transaction = _factory.CreateTransaction(siteId, userId, questionId, type, amount, transactionId, processor);
using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
{
transaction.Amount = amount;
_transactionRepository.Add(transaction);
unitOfWork.Commit();
}
Select and Update:
ITransaction transaction = _transactionRepository.FindById(transactionId);
if (transaction == null) throw new Exception(Constants.ErrorCannotFindTransactionWithId.FormatWith(transactionId));
using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
{
transaction.CrmId = crmId;
transaction.UpdatedAt = SystemTime.Now();
unitOfWork.Commit();
}
Here's the unit of work code:
public virtual void Commit()
{
if (_isDisposed)
{
throw new ObjectDisposedException(GetType().Name);
}
_database.SubmitChanges();
}
I even went into the designer.cs file and put a breakpoint on the field that is being set but not updated. I stepped through and it entered and execute the set code, so the Entity should be getting "notified" of the change to this field:
public string CrmId
{
get
{
return this._CrmId;
}
set
{
if ((this._CrmId != value))
{
this.OnCrmIdChanging(value);
this.SendPropertyChanging();
this._CrmId = value;
this.SendPropertyChanged("CrmId");
this.OnCrmIdChanged();
}
}
}
Other useful information:
ObjectTracking is enabled
No errors or exceptions when second SubmitChanges is called (just silently fails update)
SQL profiler shows insert and select but not the subsequent update statement. Linq-To-Sql is not generating the update statement.
There is only one database, one database string, so the update is not going to another database
The table has a primary key.
I don't know what would cause Linq-To-Sql to not issue the update command and not raise some kind of error. Perhaps the problem stems from using the same DataContext instance? I've even refreshed the object from the database using the DataContact.Refresh method before it is pulled for the update, but that didn't help.
I have found what is likely to be the root cause. I am using Unity. The initial insert is being performed in a service class with a PerWebRequest lifetime. The select and update is happening in a class with a Singleton lifetime. So my assumption that the DataContext instances are the same was incorrect.
So, in my class with the Singleton lifetime, I get a fresh instance of the database repository and perform the update and no problem.
Now I still don't know why the original code didn't work and my approach could still be considered more a workaround than a solution, but it did solve my problem and hopefully will be useful to others.

EF 5.0 Trouble updating entity which is already tracked

I'll preface this question with the following: I know there are a million posts on the internet about the old "An object with the same key already exists in the ObjectStateManager" issue. My scenario is a bit more complicated, I think.
I have a UnitOfWork class which creates a DbContext and passes it to any repository which is called. The pattern I'm using closely follows the Unit of Work tutorial on the ASP.NET site. Unlike the tutorial, my repositories take in Business entities, map them to data entities, and perform some CRUD action. My Business logic only works with Business entities. Here is what I'm trying to do in a sample Business Manager class:
_unitOfWork.Repository.Add(entity);
_unitOfWork.Save(); // context.SaveChanges() under the hood
...Perform some operations on the model...
_unitOfWork.Repository.Update(entity);
_unitOfWork.Save();
Here is a sample Update method from the repository:
public virtual void Update(entity)
{
var dataEntity = // map from business entity to data;
_context.Entry(dataEntity).State = EntityState.Modified;
}
It obviously fails on the last line. Here is where my confusion sets in:
The entity's State is Detached
When I attempt to change the State to Modified or Unchanged, it gives me the ObjectStateManager exception above.
When I attempt to detach the entity from the context (((IObjectContextAdapter)_context).ObjectContext.Detach(entity);) I get an exception about how the entity is not attached to the context, therefore, it cannot detach it. Very confusing (something fundamental I'm missing, for sure).
Many other posts suggest I make a database call, update that entity in the repository, then _unitOfWork.Save(). I don't like this approach. I shouldn't need to make an unnecessary network call to update an entity.
The Update method in the repository needs to handle two scenarios: 1) updating an entity which is not currently tracked by the context, and 2) updating an entity which IS currently tracked by the context. The second piece is what I'm struggling with.
Any help or insight is appreciated.
Thanks!
This means that there already is an object attached to the context with the same key as the new dataEntity. The existing object and the new entity both represent the same entry in the database but they are two different objects.
This may indicate that the lifespan of your _context is too long, but that's hard to judge from your code. It is certain though that the context was previously used to fetch an entity from the database that is subsequently duplicated by var dataEntity = ....
You may have to shorten the lifespan of the context, I can't tell. If you think it's OK you may want to use the Local collection to check whether the entity is already there. That will save the database round trip that Find may still make.
I found a hybrid solution which appears to work:
public virtual void Update(TB entity)
{
var dataEntity = Mapper.Map<TB, TD>(entity);
var pkey = _dbSet.Create().GetType().GetProperty("Id").GetValue(dataEntity);
var entry = _context.Entry(dataEntity);
if (entry.State == EntityState.Detached)
{
var attachedEntity = _dbSet.Find(pkey);
if (attachedEntity != null)
{
var attachedEntry = _context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(dataEntity);
}
else
{
entry.State = EntityState.Modified;
}
}
else
{
entry.State = EntityState.Modified;
}
}

How to dispose data context after usage

I have a member class that returned IQueryable from a data context
public static IQueryable<TB_Country> GetCountriesQ()
{
IQueryable<TB_Country> country;
Bn_Master_DataDataContext db = new Bn_Master_DataDataContext();
country = db.TB_Countries
.OrderBy(o => o.CountryName);
return country;
}
As you can see I don't delete the data context after usage. Because if I delete it, the code that call this method cannot use the IQueryable (perhaps because of deferred execution?). How to force immediate execution to this method? So I can dispose the data context..
Thank you :D
The example given by Codeka is correct, and I would advice writing your code with this when the method is called by the presentation layer. However, disposing DataContext classes is a bit tricky, so I like to add something about this.
The domain objects generated by LINQ to SQL (in your case the TB_Countries class) often contain a reference to the DataContext class. This internal reference is needed for lazy loading. When you access for instance list of referenced objects (say for instance: TB_Country.States) LINQ to SQL will query the database for you. This will also happen with lazy loaded columns.
When you dispose the DataContext, you prevent it from being used again. Therefore, when you return a set of objects as you've done in your example, it is impossible to call the States property on a TB_Country instance, because it will throw a ObjectDisposedException.
This does not mean that you shouldn't dispose the DataContext, because I believe you should. How you should solve this depends a bit on the architecture you choose, but IMO you basically got two options:
Option 1. Supply a DataContext to the GetCountriesQ method.
You normally want to do this when your method is an internal method in your business layer and it is part of a bigger (business) transaction. When you supply a DataContext from the outside, it is created outside of the scope of the method and it shouldn't dispose it. You can dispose it at a higher layer. In that situation your method basically looks like this:
public static IQueryable<TB_Country> GetCountriesQ(
Bn_Master_DataDataContext db)
{
return db.TB_Countries.OrderBy(o => o.CountryName);
}
Option 2. Don't return any domain objects from the GetCountriesQ method.
This solution is useful when the method is a public in your business layer and will be called by the presentation layer. You can wrap the data in a specially crafted object (a DTO) that contains only data and no hidden references to the DataContext. This way you have full control over the communication with the database and you can dispose the DataContext as you should. I've written more about his on SO here. In that situation your method basically looks like this:
public static CountryDTO[] GetCountriesQ()
{
using (var db = new Bn_Master_DataDataContext())
{
var countries;
from country in db.TB_Countries
orderby country.CountryName
select new CountryDTO()
{
Name = country.CountryName,
States = (
from state in country.States
order by state.Name
select state.Name).ToList();
};
return countries.ToArray();
}
}
public class CountryDTO
{
public string Name { get; set; }
public List<StateDTO> States { get; set; }
}
As you will read here there are some smart things you can do that make using DTOs less painful.
I hope this helps.
You can convert the queryable to a list, like so:
public static List<TB_Country> GetCountriesQ()
{
using(var db = new Bn_Master_DataDataContext())
{
return db.TB_Countries
.OrderBy(o => o.CountryName).ToList();
}
}

IRepository confusion on objects returned

I have some e-commerce code that I use often that uses Linq To SQL for saving orders to the database. I want to remove the tightly coupled Linq to SQL bit and pass in an IRepository instead but I am still a little confused on things.
Say I have a GetCustomer() method on my ICustomerRepository that returns a Customer object.
Do I need it to really return an ICustomer object that gets passed back from that method so if I switch from Linq To SQL to say SubSonic it's not a problem?
I believe I do, if that is the case is there a way in Linq To SQL to easily convert my Linq To SQL Customer object to my ICustomer object like SubSonics ExecuteSingle(Of ) method?
If you want your Customer class to be a plain object with no attachment to LINQ, then you will most likely need to write a mapper method to convert your LINQ-based Customer object to your plain Customer domain object. LINQ to SQL does not have such functionality built-in.
I have begun to wrap my mapping methods in an extension method for readability, and it really helps to keep the Repository code simple. For instance, an example CustomerRepository method my look like:
public Customer GetById(int id)
{
return dataContext.LINQCustomers.Where(c => c.Id == id)
.Single()
.ToDomainObject();
}
and the ToDomainObject() method is defined in an extension method like:
public static class ObjectMapper
{
public static Customer ToDomainObject(this Customer linqObject)
{
var domainObject = null
if (linqObject != null)
{
domainObject = new Customer
{
Id = linqObject.Id,
FirstName = linqObject.FirstName,
LastName = linqObject.LastName
}
}
return domainObject;
}
}
or something similar. You can do the same to convert your domain object back to a LINQ object to pass back into your repository for persistence.
You can have it return a Customer as long as Customer is a plain old .NET object, and not some db-generated entity. Your Customer domain object should have no knowledge about how (or if) it might be persisted to a database, and this is what should be returned from your repository. In your repository you might have some mapping code - this is quite common - that maps from [however you get the data back from its storage location] to your domain object. If you're using Linq-to-sql then this mapping would be from the Linq-To-Sql generated Customer table (and perhaps other tables - your Customer domain object likely won't map 1:1 to a particular table in the database) to your Customer domain object, which would live in a different namespace (and most likely, assembly).
There is no need to make it an ICustomer at all. A repository acts in a way as to make it look as though your persistent instances are in memory.
public interface ICustomerRepository
{
Customer GetByID(int id);
IEnumerable<Customer> GetByName(string name);
Customer GetByVatCode(string vatCode);
}
Some people would additionally include methods such as
void Add(Customer newCustomer);
void Delete(Customer deleted);
void Update(Customer modified);
The latter method implementations would most likely just update a unit of work.
The concept though is that these are just common ways of asking for Customer instances, the repository acts as a way of asking for them without defining how to ask for them.

Resources