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.
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 a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
First I'll explain a tad exactly what is happening, most of my values from my database are fine when I execute a linq to grab them, but specific ones such as ItemNumber and JobReference don't seem to carry across to the code, so I can't compare them.
Here is my linq statement for getting all the items.
public List<PurchaseOrderItem> GetPurchaseOrderItems()
{
return _context.PurchaseOrderItems.Include(p => p.PurchaseOrderHeader).ToList();
}
Here is the statement for getting a specific item:
public PurchaseOrderItem GetPurchaseOrderItem(PurchaseOrderItem selectedPoLine)
{
foreach (var item in GetPurchaseOrderItems())
if (item.JobReference == selectedPoLine.JobReference &&
item.ItemNumber == selectedPoLine.ItemNumber)
return item;
return null;
//return _context.PurchaseOrderItems.FirstOrDefault(p => p.JobReference == selectedPoLine.JobReference && p.ItemNumber == selectedPoLine.ItemNumber);
}
Here is my problem
See how the item.JobReference is empty?
Well this doesn't make sense because if I check in my database, the value for the JobReference is there and is correct ...
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();
I'm having trouble here. I have an observable collection, and I want to delete some of the stuff in it if it meets a certain criteria.
Here's the code:
foreach (Record record in SwearJarController.Records)
{
if (record.Word == "Hi")
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
}
Records is the name of the collections, and record is the class. Apparently, it gets confused at the 'foreach' part.
What should I do?
Thanks!
You cannot modify a collection that you are enumerating. You must do a ToArray() or ToList() on your foreach:
foreach (Record record in SwearJarController.Records.ToArray())
{
if (record.Word == "Hi")
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
}
Cleaned up version:
foreach (Record record in SwearJarController.Records
.Where(x => String.Equals(x.Word, "Hi")
.ToArray())
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
Note: You should really use String.Equals(...) and use a StringComparison enumeration as well. I'm not sure if your comparison was meant to be case sensitive and/or ordinal/currentculture/invariantculture.
You can't modify the collection while you are working on it. You can run a for loop with the count going from Records.Count down to 0 and remove that way. That should work
Example:
for(int x = SwearJarController.Records.Count - 1 ; x >= 0; x--)
{
Record record = SwearJarController.Records[x];
if(record.Word == "Hi")
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
}
As others have already said, you can't modify a collection while you are iterating thru it.
Your best bet for deleting your records cleanly is to do the following:
foreach (var record in SwearJarController.Records
.Where(r => r.Word == "Hi")
.ToArray())
{
SwearJarController.Records.Remove(record);
Datastore.DB.Records.DeleteOnSubmit(record);
}
This filters and snapshots your SwearJarController.Records collection before deleting.
Doing the filter first limits the possibility of a large array being created.