Update A Table From A List - c#

I have two tables. One of them is Customers which is in the database, the other one is ChangedCustomers, which comes from the user. I write the updated model, I guess there is something missing.
public async Task<int> UpdateCustomers (IENumerable<ChangedCustomers> changedCustomers
{
foreach(var item in changedCustomers)
{
_context.Customers.Updaate (new Customers()
{
CustomerName=item.CustomerName,
CustomerAddress=item.CustomerAddress
});
}
return await _context.SaveChangesAsync();
}
In Method I am not saying something like "update that row when Id values in Customers and ChangedCustomers tables are equal". I need this but I can't. How can I do it?

You are trying update the row by adding a new row? Thats what it looks like. If you want to update a specific row, you will need to fetch that row, then update the values.
foreach (var item in changedCustomers)
{
var customer = _context.Customers
.FirstOrDefault(x => x.CustomerId == item.CustomerId);
if (customer != null)
{
customer.CustomerName = item.CustomerName;
customer.CustomerAddress = item.CustomerAddress;
}
else
{
customer = new Customer
{
CustomerName = item.CustomerName,
CustomerAddress = item.CustomerAddress
}
_context.Add(customer);
}
}
_context.SaveChangesAsync();
FirstOrDefault() will retrieve the first value from _context.Customers that matches the expression, if it doesn't find any it will default to null. If it is not null then you can make the changes, if it is null, you could add a new value.

Here's general logic that you'd follow to update an existing record which could be placed within your loop:
//Find the entity already tracked based on table key
var entity = context.Customers.FirstOrDefault(item => item.YourTableID == id);
// Validate entity is not null
if (entity != null)
{
// Make changes to specific field
entity.Name = "Me";
// Update entity in it's entirety
entity = new Customers() { //Your logic here to build the updated entity }
// Save changes in database
context.SaveChanges();
}

Just mark the entities as modified and call SaveChanges.
Only UPDATE sql queries will be executed.
The code in #jaabh answer is very inefficient, since it pre-executes sql-queries SELECT, reading from the database those entities that we already have. This is unnecessary.
public async Task<int> UpdateCustomers(IEnumerable<ChangedCustomers> changedCustomers)
{
foreach (var item in changedCustomers)
{
_context.Entry(item).State = EntityState.Modified;
}
return await _context.SaveChangesAsync();
}

Related

one2many relation, get parent with child

public void getForm(string scode) {
Form result = DBContext.Forms.Where(f => f.Code == fCode && f.SCode == sCode).FirstOrDefault();
result.Products = result.Products.Where(p => p.Deleted== false).ToList(); // commenting this line fix the problem
return result;
}
How can we merge above two lines together to avoid below error.
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
Update:
When called in another function, it throw above error
public void savetrans(string fcode)
{
Form form = GetForm(fCode);
var transDb = new DbContext.Data.Transaction()
{
FId = form.FId,
field1= "test",
field2= "test",
field3= "test",
};
DbContext.Transactions.Add(transactionDb);
DbContext.SaveChanges();
}
If you want to delete products which are flagged as deleted:
var deletedProducts = result.Products.Where(x => x.Deleted).ToList();
foreach(var deletedProduct in deletedProducts)
result.Products.Remove(deletedProduct);
If Form.Products is a List<Product> then you can use .RemoveAll(x => x.Deleted)
If you just want to exclude deleted products when working with your entity then I'd recommend using an unmapped property for that purpose:
public class Form
{
public ICollection<Product> Products { get; set; } = new List<Product>();
[NotMapped]
public IReadOnlyCollection<Product> ActiveProducts => Products.Where(x => !x.Deleted).ToList().AsReadOnly();
// or
public IReadOnlyCollection<Product> ActiveProducts
{
get { return Products.Where(x => !x.Deleted).ToList().AsReadOnly();
}
}
Then when you want to use only the active products, use .ActiveProducts. The caveat of this approach is that you cannot use this property in EF Linq expressions, and it should only be used in a read-only capacity. For instance, don't try something like:
var products = context.Forms.Where(x => x.FormId == formId).SelectMany(x => x.ActiveProducts);
This will error out because EF wont have ActiveProducts mapped. You'd have to use .Products with the appropriate filter for Deleted.
I'll normally put logic like handling active/inactive when populating view models rather than at the entity level. Entities should reflect the data model, while business logic that applies to how you view/interact with that logic is represented by your view models. The benefit of using a view model is that the inspection of the Active/Deleted state is done once when the view models are populated, and they don't poison the entity with properties that aren't valid in certain uses.
Apparently your database has Forms and Products. There is a one-to-many relation between Forms and Products: Every Form has zero or more Products, every Product belongs to exactly one Form using a foreign key (probably Product.FormId)
Your first statement fetches one of your Forms that meets certain requirements or null if there is no such Form. Without checking the null-return value you try to change the Products of this Form.
The problem is, that there might be several Products that have a non-nullable foreign key to this Form. They are items in the ICollection of Products of the Form. If you assigning a new ICollection to Form.Products, entity framework wants to set the foreign key of the Products that were in Form.Products to zero, indicating that this product does not belong to any form anymore. But in your model description you defined that every Product should belong to exactly one Form. Hence the error.
So what you should do, depends on what you want with your procedure. Do you only want to Fetch the Form with its non-deleted products, then you should perform a query. If you want to remove all deleted products from this forms from the database, then you should perform an update
Query a certain Form with its non-deleted products
public ICollection<Form> GetFormWithNonDeletedProducts(string scode)
{
using (var dbContext = new MyDbContext(...))
{
return dBContext.Forms // from the collection of all Forms
.Where(form => form.Code == fCode
&& form.SCode == sCode) // keep only the ones that I want
.Select(form => new Form() // and create a new Form object
{ // with the properties I plan to use
Id = form.Id,
Name = form.Name,
...
Products = form.Products // Fetch only the non-deleted products
.Where(product => !product.Deleted)
.ToList(),
}
.FirstOrDefault();
}
}
}
The only reason I need to create a new Product object is because I want to put it in a return value. If you don't need it in a return value, you can put your fetched properties into an anonymous object. This is usually more efficient, because you won't fetch data you won't used from the database.
For example the above example will assign Product.FormId. You won't need it, becasue you know that all thousand Products of this Form will have the same value FormId: namely the value of Form.Id.
The same query without fetching properties you don't use (anonymous types)
using (var dbContext = new MyDbContext(...))
{
return dBContext.Forms // from the collection of all Forms
.Where(form => form.Code == fCode
&& form.SCode == sCode) // keep only the ones that I want
.Select(form => new // and create a new Form object
{ // with the properties I plan to use
Id = form.Id,
Name = form.Name,
...
Products = form.Products // Fetch only the non-deleted products
.Where(product => !product.Deleted)
.Select(product => new
{ // Select only properties you plan to use
Name = product.Name,
Price = product.Price,
// not meaningful: you already know the value:
// FormId = product.FormId,
})
.ToList(),
}
.FirstOrDefault();
}
Update the database: remove the deleted products of the form
Although your function is called GetForms it seems you want to use it to remove the deleted products.
The easiest way is to use DbSet<Products>.RemoveRange:
using (var dbContext = new MyDbContext(...))
{
// remove all Deleted products of the form with Code equal to fCode
// and SCode equal to sCode
var productsToDelete = dbContext.Products
.Where(product => product.Deleted
&& product.Form.Code == fCode
&& product.Form.Scode == sCode);
// note: query is not executed yet!
dbContext.Products.RemoveRange(productsToDelete);
dbContext.SaveChanges();
}
It might be that you'll have to do RemoveRange(productsToDelete.ToList()), you'll have to check this.

Clean method in order to update collection in entity framework

i'm sorry if this question has already been asked, but i'm in trouble with my method of updating collection in Entity Framework.
Let me explain the situation :
- I have for example one model CUSTOMER with some properties and a collection of ORDERS (for example).
- Let's imagine we have an admin page on wich we can edit all the ORDERS for a customer, and when we submit the form, it will send us back the object CUSTOMERS with updated ORDERS (some added, some updated and some deleted).
For the moment i use something like this in order to compare old collection and new collection and determine which object i need to delete/update/add
var toRemove = new List<ORDERS>();
var toAdd = new List<ORDERS>();
foreach (
var order in
oldList.Where(
order =>
newList.FirstOrDefault(t => t.link_id == order.link_id) == null))
{
toRemove.Add(order);
}
foreach (
var order in
newList.Where(
order =>
oldList.FirstOrDefault(t => t.link_id == order.link_id) == null))
{
toAdd.Add(order);
}
foreach (var ORDERSe in toRemove)
{
bdd.ORDERS.Remove(ORDERSe);
}
foreach (var ORDERSe in toAdd)
{
ORDERSe.pjt_id = project_id;
bdd.ORDERS.Add(ORDERSe);
}
foreach (
var order in
newList.Where(
order =>
oldList.FirstOrDefault(t => t.link_id == order.link_id) != null))
{
var child = oldList.FirstOrDefault(t => t.link_id == order.link_id);
bdd.Entry(child).CurrentValues.SetValues(order);
}
But i'm unconfortable with this, because in my mind, entity framework should be able to do the work for me !
I was hoping something like :
customer.orders = newOrders;
Did i missed anything about entity framework or ?
Because when i do this, it just duplicate my orders.
Thanks in advance for your answer.
You can certainly make it cleaner using .Except() and .Intersect(), but the concept doesn't really change, AFAIK you still have to individually remove, update & add the entries in loops...
var oldList = new List<ORDERS>();
var newList= new List<ORDERS>();
var IdsToRemove = oldList.Select(t => t.link_id).Except(newList.Select(t => t.link_id));
var IdsToAdd = newList.Select(t => t.link_id).Except(oldList.Select(t => t.link_id));
var IdsToUpdate = newList.Select(t => t.link_id).Intersect(oldList.Select(t => t.link_id));
//remove
bdd.orders.where(x => IdsToRemove.Contains(x.link_id)).ForEach(x => bdd.Remove(x));
//add
foreach(var order in newList.Where(x -> IdsToAdd.Contains(x.link_id))
{
bdd.Orders.Attach(order);
bdd.Entries(order).EntityState = EntityState.Added;
}
//update
foreach(var order in newList.Where(x -> IdsToUpdate .Contains(x.link_id))
{
bdd.Orders.Attach(order);
bdd.Entries(order).EntityState = EntityState.Modified;
}
bdd.SaveChanges();
But i'm unconfortable with this, because in my mind, entity framework
should be able to do the work for me !
In fact, EF does the Work for you. Using the data context SaveChanges method EF should be able to save all your changes at once:
DbContext.SaveChanges()
For your convinience you can still override this method. Internally you should use something like this:
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditable>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
entry.Entity.ModifiedDate = DateProvider.GetCurrentDate();
entry.Entity.ModifiedBy = UserName;
}
}
return base.SaveChanges();
}

Pivot List of entities using LINQ in C#

What I have is a list of entities coming back from a database that I want to pivot so that I end up with a new list of entities with the duplicates removed and the pivoted items attached to the new entity.
Currently I have a simple solution like this:
IQueryable<Entity> results // being passed in from calling method.
List<Entity> pivotedEntities = new List<Entity>();
foreach (Entity entity in results)
{
if (pivotedEntities.Contains(entity))
{
Entity matchedEntity = pivotedEntities.Find(e => e.Id == entity.Id);
matchedEntity.RelatedEntities.Add(entity.RelatedEntity);
}
else
{
pivotedEntities.Add(new Entity());
}
}
return pivotedEntities.AsQueryable();
This works fine however I want to be able to achieve the same thing with a LINQ query on the IQueryable results variable so that it maintains its deffered execution rather than executing as soon as I enter the foreach.
I have other methods that get called around this code that also alter the IQueryable and I then want to execute the call to the database once all filters have been applied.
Hope that all makes sense.
Maybe this will reduce few loops.
List<Entity> pivotedEntities = new List<Entity>();
int index = 0;
foreach (Entity entity in results)
{
index = pivotedEntities.IndexOf(e => e.Id == entity.Id);
if (index>-1)
{
pivotedEntities[index].RelatedEntities.Add(entity.RelatedEntity);
}
else
{
pivotedEntities.Add(new Entity());
}
}
return pivotedEntities.AsQueryable();

Sorting data issue

So I have a little issue in sorting some data I have. In a Telerik Grid, I have a column called Requestor that displays the name of a person or Unit (group of people). The problem is, Requestor has two sources it can get it's data from. Here are the two sources.
1.) RequestorId: This is a foreign key to a table called Customer. Here, I store all the data for the user, including their full name. This field can be null btw.
2.) UnitId: This is another foreign key to a table called Units. Here, I store all the data for the Units, particularlly their names. This field can be null btw.
Here is the logic:
//Entity class that contains all the data for my grid
var purchaseOrders = _purchaseOrders.GetPurchaseOrders();
//Key is Id of PurchaseOrders, Value is name of requestor
var dictionary = new Dictionary<int, string>();
foreach (var purchaseOrder in purchaseOrders)
{
if (purchaseOrder.requestorId != null)
dictionary.add(purchaseOrder.Requestor.Fullname);
else
dictionary.add(purchaseOrder.unit.Fullname);
}
dictionary.orderby(x => x.value).ToDictionary(x => x.Key, x.Value);
var tempPurchaseOrders = new List<PurchaseOrder>();
foreach (var item in dictionary)
{
tempPurchaseOrders.Add(purchaseOrders.Where(x => x.Id == item.Key).FirstOrDefault());
}
purchaseOrders = tempPurchaseOrders.AsQueryable();
return purchaseOrders;
This logic returns an ordered list based on what I want to do, however, the problem is the amount of time it takes to process. It takes 1 minute to process. That's horrible obviously. Is there anyway to optimize this? I cut down the source after I return for the grid because there is no logical way to really cut it down beforehand.
Any help would be appreciated. Thanks.
Edit: I found out I no longer am required to use the RequestName field. That limits the data to two areas now. Still a minute to process though.
Did you try something like this:
return _purchaseOrders.GetPurchaseOrders().Select(i => new
{
OrderColumn = i.requestorId != null ? purchaseOrder.Requestor.Fullname : purchaseOrder.unit.Fullname,
// map other columns
})
.OrderBy(i => i.OrderColumn);
A bit like SÅ‚awomir Rosiek's solution (but entity framework won't accept that statement):
return _purchaseOrders.GetPurchaseOrders()
.OrderBy(o => o.unit.Fullname).ToList();
(since you don't use RequestName anymore).
Especially when GetPurchaseOrders() is an IQueryable from EF you delegate the sorting to the database engine because the sort expression becomes part of the SQL statement.
So I came up with my own solution. I first tried what both SÅ‚awomir Rosiek and Gert Arnold did. Unfortunately, like Gert mentioned, the first answer would not go through. The second one had similar issues.
In the end, I created a class to store the data from both Requestors and Units. It consisted of the following:
internal class RequestorData
{
public int entityId { get; set; }
public string Name { get; set; }
public bool isRequestorId { get; set; }
}
Next, I did the following.
//Entity class that contains all the data for my grid
var purchaseOrders = _purchaseOrders.GetPurchaseOrders();
var tempPurchaseOrders = new List<PurchaseOrder>();
var requestors = new List<RequestorData>();
var customers = purchaseOrders.Select(po => po.Requestor).Distinct().ToList();
var units = purchaseOrders.Select(po => po.Unit).Distinct().ToList();
foreach (var customer in customers)
{
if (customer != null)
requestors.Add(new RequestorData { entityId = customer.Id, Name = customer.FullName, isRequestorId = true });
}
foreach (var unit in units)
{
if (unit != null)
requestors.Add(new RequestorData { entityId = unit.Id, Name = unit.FullName, isRequestorId = false });
}
requestors = requestors.OrderBy(r => r.Name).ToList();
foreach (var requestor in requestors)
{
var id = requestor.entityId;
if (requestor.isRequestorId)
tempPurchaseOrders.AddRange(purchaseOrders.Where(po => po.RequestorId == id).ToList());
else
tempPurchaseOrders.AddRange(purchaseOrders.Where(po => po.UnitId == id).ToList());
}
purchaseOrders = tempPurchaseOrders.AsQueryable();
return purchaseOrders;
I ran this new rendition and have a 5-6 second time of wait. That's not perfect but much better than before. Thanks for all the help.

Entity Framework 5.0 SaveChanges of Complex objects

I have a method that accepts an input parameter for a list of transactions with added or updated records.
I loop through the list to discover which is modified and whish is added using the:
context.Entry(item).State = System.Data.EntityState.Modified;
to set the state of each transaction.
The problem I have is because the transaction object has a relationship with TransactionType while I loop through the input parameter list of transactions, if there are more than one transaction with the same transaction id in the database I get the following error:
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same
key.
By the way I am using EF5 and Code First.
The method in concern is:
public TransactionList SaveTransactions(Transaction[] transactions)
{
try
{
using (EntityContext context = new EntityContext())
{
foreach (var item in transactions)
{
if (item.TransactionId > 0)
context.Entry(item).State = System.Data.EntityState.Modified;
else
context.Entry(item).State = System.Data.EntityState.Added;
}
context.SaveChanges();
}
return GetLatestTransactions();
}
## UPDATE ##
if I set the TransactionType for each item to null, I wont get any error and the rest of the transaction fields will be updated fine. i.e. TransAmount, Date, etc. The problem is that by setting the TransType to null I will never be able to change the type of my transaction.
using (EntityContext context = new EntityContext())
{
foreach (var item in transactions)
{
//set the fk to null
item.TransactionType = null;
if (item.TransactionId > 0)
{
context.Entry(item).State = System.Data.EntityState.Modified;
}
else
context.Entry(item).State = System.Data.EntityState.Added;
}
context.SaveChanges();
}
## UPDATE 2 ##
I just found another way that this would work but still is not the ideal for me. I get the single transaction per item and then set the values. I don't like this solution since .Single will do a roundtrip for each iteration.
foreach (var item in transactions)
{
var or = context.Transaction
.Include(t => t.Category)
.Include(t => t.TransactionReasonType)
.Include(t => t.TransactionType)
.Single(t => t.TransactionId == item.TransactionId);
if (item.TransactionId > 0)
{
context.Entry(or).CurrentValues.SetValues(item);
context.Entry(or).State = System.Data.EntityState.Modified;
}
The solution to my problem was to separate the add from the update. For update, fetch each row from database set the original values with the new ones. For add just add the new values to the context.
foreach (var item in transactions)
{
if (item.TransactionId > 0) //Update
{
var original = context.Transaction.Where(
t => t.TransactionId == item.TransactionId)
.FirstOrDefault();
original.TransactionType = context.TypeTransaction.Single(
p => p.TypeTransactionId == item.TransactionType.TypeTransactionId);
context.Entry(original).CurrentValues.SetValues(item);
}
else //Insert
{
item.TransactionType = context.TypeTransaction.Single(
p => p.TypeTransactionId == item.TransactionType.TypeTransactionId);
context.Transaction.Add(item);
}
}
context.SaveChanges();
Another way to workaround this issue:
foreach (var item in updated)
{
var original = db.MyEntities.Find(item.Id);
db.Entry(original).CurrentValues.SetValues(item);
}
db.SaveChanges();

Categories

Resources