I have two list.
I need remove items from the first list are not in the second list and add the other elements of the first.
foreach (var product in item.Products)
{
item.Products.Remove(product);
}
var newProducts = _catalogService.GetProductBaseItems(x => model.Products.Contains(x.Id))
.ToList();
foreach (var product in newProducts)
{
item.Products.Add(product);
}
You can use Enumerable.Except to find all which are in the first but not in the second. But since your Product class might not override Equals and GetHashCode by comparing the ID's you either have to do it, create a custom IEqualityComparer<Product> or use following approach:
IEnumerable<int> idsInFirstNotSecond = item.Products
.Select(x => x.Id)
.Except(newProducts.Select(x => x.Id));
var productsInFirstNotSecond = from p in item.Products
join id in idsInFirstNotSecond
on p.Id equals id
select p;
List<Product> completeListOfOldAndNew = productsInFirstNotSecond
.Concat(newProducts)
.ToList()
Related
I have a list of incomplete product models. Everyone is missing an owner and a price.
Can these deficiencies be filled with a single query to context? Without this foreach loop?
foreach(var item in products)
{
item.Owner = context.Products.Where(x => x.Id == item.Id).Select(x => x.ProductOwner).FirstOrDefault();
item.Price = context.Products.Where(x => x.Id == item.Id).Select(x => x.ProductPrice).FirstOrDefault();
}
I would like one query to fill in the missing fields in IEnumerable products
// build list of Id for which we need data
var idsToUpdate = products.Select(o => o.Id).ToList();
var dataById = Context.Products
// get matching entries (server side)
.Where(x => idsToUpdate.Contains(x.Id))
// get only relevant data
.Select(x => new { x.Id, x.ProductOwner, x.Price })
// ensure uniqueness (server side, free if Id is a PK)
.DistinctBy(x => x.Id)
// we will not update db
.AsNoTracking()
// now client side
.AsEnumerable()
// arrange the data
.ToDictionary(x => x.Id, x => new { x.ProductOwner, x.Price });
foreach (var item in products)
{
if (!dataById.TryGetValue(item.Id, out var data))
continue;
item.ProductOwner = data.ProductOwner;
item.Price = data.Price;
}
If data is not many then try query once, maybe?
Select all the target id
Get all products from DB
Do as you please with data(two lists) you have
ref : Using LINQ, is it possible to output a dynamic object from a Select statement? If so, how?
Since "products" is coming from external service and "context.Products" is from your DB. Why don't you join "context.Products" with "products" and return properties of "products" by applying value for "owner" and "price".
Example
var result = (from p in products
join dbP in context.Products on dbP.Id == p.Id into gj
from subDbP in gj.DefaultIfEmpty()
select new {
Owner = subDbP?.ProductOwner ?? string.Empty,
Price = subDbP?.ProductPrice ?? string.Empty,
Id = p.Id
}).ToList();
This is highly depends on the DataType of products. If this is a List, there is an method available, called ForEach.
If you are using something different, you have to write an extension method somewhere within your code. This can be something like this:
public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> values, Action<T> predicate)
{
foreach(var value in values)
{
predicate(value);
}
}
}
Afterwards, you can use this extension method like LINQ:
products.ForEach(item =>
{
var product = context.Products.Where(x => x.Id == item.Id);
item.Owner = product.Select(x => x.ProductOwner).FirstOrDefault();
item.Price = product.Select(x => x.ProductPrice).FirstOrDefault();
});
Hope this helps :-)
How to use OrderBy for shaping output in the same order as per the requested distinct list
public DataCollectionList GetLatestDataCollection(List<string> requestedDataPointList)
{
var dataPoints = _context.DataPoints.Where(c => requestedDataPointList.Contains(c.dataPointName))
.OrderBy(----------) //TODO: RE-ORDER IN THE SAME ORDER AS REQUESTED requestedDataPointList
.ToList();
dataPoints.ForEach(dp =>
{
....
});
}
Do the sorting on the client side:
public DataCollectionList GetLatestDataCollection(List<string> requestedDataPointList)
{
var dataPoints = _context.DataPoints.Where(c => requestedDataPointList.Contains(c.dataPointName))
.AsEnumerable()
.OrderBy(requestedDataPointList.IndexOf(c.dataPointName));
foreach (var dp in dataPoints)
{
....
});
}
NOTE: Also, I don't think ToList().ForEach() is ever better than foreach ().
It think the fastest method is to join the result back with the request list. This makes use of the fact that LINQ's join preserves the sort order of the first list:
var dataPoints = _context.DataPoints
.Where(c => requestedDataPointList.Contains(c.dataPointName))
.ToList();
var ordered = from n in requestedDataPointList
join dp in dataPoints on n equals dp.dataPointName
select dp;
foreach (var dataPoint in ordered)
{
...
}
This doesn't involve any ordering, joining does it all, which will be close to O(n).
Another fast method consists of creating a dictionary of sequence numbers:
var indexes = requestedDataPointList
.Select((n, i) => new { n, i }).ToDictionary(x => x.n, x => x.i);
var ordered = dataPoints.OrderBy(dp => indexes[dp.dataPointName]);
I am trying to learn advanced LINQ techniques, so how we could achieve, if possible with LINQ only, to select distinct items of a collection and merge their sub-items in a dictionary/struct or dynamic ExpandoObject?
Lets say i have these two class:
public class TestItem
{
public int Id;
public string Name;
public List<TestSubItem> SubItems;
}
public class TestSubItem
{
public string Name;
}
How can i create a single LINQ query(again, if possible) to select all distinct TestItem based on the Name property and if the same TestItem is found two time with the same name to merge the two List in the final result?
I know i could select distinct TestItem by doing the below code, but i'm stuck there:
var result = items.GroupBy(item => item.Name)
.ToList();
Thanks in advance!
A combination of a Select and an Aggregate on the groupings should get the job done. Finally, a ToDictionary call cleans everything up and gets rid of the potentially invalid ID field:
var result = items.GroupBy(item => item.Name)
.Select(g => g.Aggregate((i, j) =>
{
i.SubItems.AddRange(j.SubItems);
return i;
}))
.ToDictionary(k => k.Name, v => v.SubItems);
Alternatively, the query syntax is a bit more verbose, but I find it easier to read:
var result = (from item in items
group item by item.Name
into g
let n =
from ti in g
select ti.Name
let i =
from ti in g
from si in ti.SubItems
select si
select new { n, i }).ToDictionary(k => k.n, v => v.i);
I have this structure
Customer
- has many Orders
- has many OrderItems
I want to generate a list of CustomerItems via LINQ given a subset of OrderItems:
List of new { Customer, List<OrderItem> Items }
which is a grouping of all the items a Customer has ordered from the subset of items
How can i use LINQ to back track through the order and group by Customer to generate this object?
so far I'm on something like
items
.GroupBy(i => i, i => i.Order.Customer, (i, customer) => new {customer, i})
But thats obviously not a List. I'm guessing I need a SelectMany in there somewhere, but could do with some pointers.
I think you want:
items.GroupBy(item => item.Order.Customer)
.Select(group => new { Customer = group.Key, Items = group.ToList() })
.ToList()
If you want to continue use the overload of GroupBy you are currently using, you can do:
items.GroupBy(item => item.Order.Customer,
(key, group) => new { Customer = key, Items = group.ToList() })
.ToList()
...but I personally find that less clear.
you may also like this
var Grp = Model.GroupBy(item => item.Order.Customer)
.Select(group => new
{
Customer = Model.First().Customer,
CustomerId= group.Key,
Orders= group.ToList()
})
.ToList();
you can achive it with group join
var result = (from c in Customers
join oi in OrderItems on c.Id equals oi.Order.Customer.Id into g
Select new { customer = c, orderItems = g});
c is Customer and g is the customers order items.
I am attempting to collect an IEnumerable<Item> from a list of projects.
Here's the code I'm working with:
IEnumerable<Project> projects;
List<Item> itemList = db.Items.ToList();
IEnumerable<Items> item = itemList.Where(i => i.ProjectID == /*the ID of each project from projects*/)
I also tried going this route: IEnumerable<Items> item = from i in itemList where i.ProjectID == /*the ID of each project from projects*/ select i;
How do I iterate through each project to collect all associated items?
IEnumerable<Items> item = from i in itemList
where projects.Any(p => p.ID == i.ProjectId)
select i;
or using your method syntax
IEnumerable<Items> item = itemList.Where(i => projects.Any(p => p.ID == i.ProjectID));
There are so many ways to do that. Like those:
IEnumerable<Items> items = projects.SelectMany(p => itemsList.Where(i => i.ProjectID == p.ID))
or
IEnumerable<Items> items = itemsList.Where(i => projects.Any(p => p.ID == i.ProjectID))
Instead of Any(), you can also use the join:
var item = from i in itemList
join p in projects on i.ProjectId equals p.ProjectId
select i;