EF many-to-many madness - c#

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

Related

Iterate over an IQueryable without calling ToList()

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

C# using aggregate method on nopcommerce shipment items list not working

Ok so I'm trying to get all items which have been shipped and add them to a list if the shipment item is not already in that list. But if the shipment item is found in the list then I want to combine those two items in the list.
Here is the code I'm working with:
var shippedItems = _orderService.GetOrderById(shipment.OrderId).Shipments.Where(x => x.ShippedDateUtc != null && x.OrderId == shipment.OrderId && x.Id != shipment.Id).ToList();
List<ShipmentItem> shipmentItemsList = new List<ShipmentItem>();
for (int i = 0; i <= shippedItems.Count - 1; i++)
{
var si = shippedItems[i];
var sii = si.ShipmentItems.ToList();
foreach (var item in sii)
{
if (!shipmentItemsList.Contains(item))
{
shipmentItemsList.Add(item);
}
else
{
var foundId = shipmentItemsList.Select(x => x.Id == item.Id);
shipmentItemsList.Aggregate((foundId, item) => foundId + item);
}
}
}
For these two variables (foundId, item) i get errors:
A local variable named the variable name cannot be declared in this
scope because that name is used in an enclosing local scope to define
a local or parameter
UPDATE
I also thought I could try the following, but it's not joining the results.
if (i == 0)
{
shipmentItemsList = si.ShipmentItems.ToList();
}
else
{
shipmentItemsList.Concat(si.ShipmentItems.ToList());
}
Anyone able to point me on the right track.
Cheers
Thanks for the clarification. Essentially, the way that I understand your problem is that you need to take an object map that is grouped by Shipment and look at it from the point of Item instead. Linq can deal with this for you by using SelectMany to flatten the list and the GroupBy to shape the flattened list into your new groupings. I've made some assumptions about property names for the nopCommerce objects, but the following code sample should get you close enough to tweak with the correct property names:
var shipmentItemsList = shippedItems // This is logically grouped by shipment id
.SelectMany(s => s.ShipmentItems) // First flatten the list
.GroupBy(i => i.ItemId) // Now group it by item id
.Select(g => new
{
ItemId = g.Key,
Quantity = g.Sum(item => item.Quantity)
}) // now get the quantity for each group
.ToList();

Is there a faster way to do this LINQ?

I have a loop inside my program, which loops through thousands of object to find the right one with particular id.
is there any better and faster way than this
int id;
SPList list = SPContext.Current.Web.Lists.TryGetList("DataLibrary");
IEnumerable<SPListItem> _dataitems = list.Items.OfType<SPListItem>();
foreach (SPListItem item in _dataextantitems)
{
if (item.ID == id)
{
title= item.Title;
}
}
Use the GetItemById of SPList.
var title = SPContext.Current.Web.Lists["DataLibrary"].GetItemById(id).Title;
If your list has a lot of columns, and you want to avoid pulling them all down, you can pull down just the Title column instead:
var title = SPContext.Current.Web.Lists["DataLibrary"]
.GetItemByIdSelectedFields(id, "Title").Title;
Now if you really want to use LINQ here you could use LINQ to Sharepoint, but it's not actually going to simplify the code a ton. After using SPMetal.exe to generate a file based on your lists, you'd be able to write:
using(var context = new YourContextNameHere(SPContext.Current.Site.Url))
{
var title = context.DataLibrary
.Where(item => item.ID == id)
.Select(item => item.Title)//to avoid pulling down other columns
.First();
}
Make sure your list is sorted. Then you can use the BinarySearch method of the list or write your own implementation. If not you can shorten your code using linq.
var itemToLookup = list.Items.OfType<SPListItem>().FirstOrDefault(x => x.ID == id);
if (itemToLookup != null)
{
//...
}

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

Categories

Resources