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();
Related
var fdPositions = dbContext.FdPositions.Where(s => s.LastUpdated > DateTime.UtcNow.AddDays(-1));
foreach (JProperty market in markets)
{
// bunch of logic that is irrelevant here
var fdPosition = fdPositions.Where(s => s.Id == key).FirstOrDefault();
if (fdPosition is not null)
{
fdPosition.OddsDecimal = oddsDecimal;
fdPosition.LastUpdated = DateTime.UtcNow;
}
else
{
// bunch of logic that is irrelevant here
}
}
await dbContext.SaveChangesAsync();
This block of code will make 1 database call on this line
var fdPosition = fdPositions.Where(s => s.Id == key).FirstOrDefault();
for each value in the loop, there will be around 10,000 markets to loop through.
What I thought would happen, and what I would like to happen, is 1 database call is made
var fdPositions = dbContext.FdPositions.Where(s => s.LastUpdated > DateTime.UtcNow.AddDays(-1));
on this line, then in the loop, it is checking against the local table I thought I pulled on the first line, making sure I still properly am updating the DB Object in this section though
if (fdPosition is not null)
{
fdPosition.OddsDecimal = oddsDecimal;
fdPosition.LastUpdated = DateTime.UtcNow;
}
So my data is properly propagated to the DB when I call
await dbContext.SaveChangesAsync();
How can I update my code to accomplish this so I am making 1 DB call to get my data rather than 10,000 DB calls?
Define your fdPositions variable as a Dictionary<int, T>, in your query do a GroupBy() on Id, then call .ToDictionary(). Now you have a materialized dictionary that lets you index by key quickly.
var fdPositions = context.FdPositions.Where(s => s.LastUpdatedAt > DateTime.UtcNow.AddDays(-1))
.GroupBy(x=> x.Id)
.ToDictionary(x=> x.Key, x=> x.First());
//inside foreach loop:
// bunch of logic that is irrelevant here
bool found = fdPositions.TryGetValue(key, out var item);
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();
}
Links is an icollection of identifiers and I want to delete an identifier but first I have to delete the entries in links however I get this error:
System.Data.SqlClient.SqlException: New transaction is not allowed because there are other threads running in the session.
I'm not sure how to get around this?
public ActionResult DeleteConfirmed(int id)
{
var userId = User.Identity.GetUserId();
var UserTableID = db.UserTables.Where(c => c.ApplicationUserId == userId).First().ID;
Identifier identifier = db.Identifiers.Find(id);
if (identifier.UserTableID == UserTableID)
{
foreach (var item in db.Links.Where(c => c.IdentifierID == identifier.ID))
{
db.Links.Remove(item);
db.SaveChanges();
}
db.Identifiers.Remove(identifier);
db.SaveChanges();
return RedirectToAction("Index");
This is likely caused by the foreach loop since you're actively pulling in data from the db while simultaneously trying to save it. Try adding a .ToList() to your foreach loop.
foreach (var item in (db.Links.Where(c => c.IdentifierID == identifier.ID)).ToList())
{
db.Links.Remove(item);
db.SaveChanges();
}
first, your foreach didn't has ToList, the query was not executed.
and , from your code, variable "db" must be singleton,
web is multi thread app,
while two thread use "db" run task, you'll get unpredictable error.
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();
}
I have a method that updates a ReportRecipient object in EF. The primitives work fine; the headache comes in when trying to manage a M2M relationship with the RecipientGroups objects.
Please take a look at this code:
public IReportRecipient ModifyRecipientWithGroupAssignments(IEnumerable<Guid> groupIds, IReportRecipient recipient)
{
var entity = Context.ReportRecipients
.Include("RecipientGroups")
.FirstOrDefault(e => e.ReportRecipientId == recipient.ReportRecipientId)
.FromIReportRecipient(recipient);
var toRemove = entity.RecipientGroups
.Where(e => !groupIds.Contains(e.GroupId))
.ToList();
//remove group assignments that no longer apply
foreach (var group in toRemove)
{
if (group != null)
{
entity.RecipientGroups.Attach(group);
entity.RecipientGroups.Remove(group);
}
}
var toAdd = entity.RecipientGroups
.Where(e => groupIds.Contains(e.GroupId))
.ToList();
//add new groups that weren't there before
foreach (var group in toAdd)
{
if (group != null)
{
entity.RecipientGroups.Attach(group);
}
}
return entity;
}
... my problem is on the var ToAdd... line. Even if I have a collection of Guids in groupIds that match Guids representing RecipientGroup objects in the database, toAdd always evaluates to an empty collection. I would think the Contains() function would work for this scenario; can someone please explain if I am doing something wrong?
You should load the RecipientGroups you want to add from the database (Context.RecipientGroups I guess), not from the collection you want to add them to (entity.RecipientGroups in the code sample).