.NET Delete items in Icollection Before Deleting Entry - c#

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.

Related

How to properly cache a table in Entity Framework for this use case

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);

Update A Table From A List

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();
}

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();

EF - Can not delete an object

Why does this code not work? It inserts an object but does not delete it
public int Toggle(RequestArchive RequestArchiveObj)
{
var ra = DataContext.RequestArchives.Where(rec => rec.UserId == RequestArchiveObj.UserId && rec.RequestId == RequestArchiveObj.RequestId);
if(ra.Count() > 0)
{
foreach (var item in ra)
{
DataContext.DeleteObject(item);
}
}
else
{
DataContext.AddToRequestArchives(RequestArchiveObj);
}
DataContext.SaveChanges();
return RequestArchiveObj.Id;
}
There's a potentially dangerous issue with your code and your problem might relate to that:
If you loop through your query object (the object returned by DataContext.RequestArchives.Where()) without executing it, you're going to have a roundtrip to the database for every single item in the loop. This is called the N+1 selects problem.
You can mitigate this using the ToList() method:
var ra = DataContext.RequestArchives
.Where(rec =>
rec.UserId == RequestArchiveObj.UserId &&
rec.RequestId == RequestArchiveObj.RequestId)
.ToList(); // this executes the query
// ...
foreach (var item in ra) // without ToList() this will query every item by itself
{
DataContext.DeleteObject(item); // and this might collide with the query
}
I'm not sure about this, but maybe the deletion problem occurs because you try to delete objects while still querying them through the foreach loop. If that is the case, it should work once you're using ToList() as recommended above.

how to update easily an entity directly from a wcf call?

This is what I Have in my WCF service
public long Generic_Save(Product p, ObjectSet os)
{
if (p.Id == 0)
{
os.AddObject(p);
}
else
{
// UPDATE
Product original = os.Single<Project>(o => o.Id == p.Id);
original.Name = p.Name;
original.Items = p.Items; // doesn't work !
}
dataEntities.SaveChanges();
return p.Id;
}
Product p is an object from the WCF Call, with an EntityKey etc.. but it's not attached to the current dataEntities..
What I want to do is to save the object Product p directly, not to get the original from the ObjectSet os before and modify the values -> Product original = os.Single<Project>(o => o.Id == p.Id);
How can I do that?
[EDIT]
I have try this to add new items and it's working
foreach (Item item in p.Items)
{
try
{
dataEntities.Items.ApplyCurrentValues(item);
}
catch (Exception)
{
Items i = new Items();
// Set prop here or make a method CopyTo()
i.Prop = item.Prop;
dataEntities.AddToItems(i);
}
}
dataEntities.SaveChanges();
Badly. It is possible only with Product p itself (update detached entity) but it is really hard with items. The problem is that you must manually say EF exactly which item has changes, which is new and also which were deleted. The longer discussion of the problem is here. Common solutions are:
Do it exactly as you did at the moment but instead of assigning items, manually compare original and received items and modify loaded items accordingly. If you do not add or remove items on the client this should be just about calling ApplyCurrentValues for each item.
Use Self tracking entities (only for .NET clients).

Categories

Resources