Aggregate LINQ function to take place of this "accumalator" function - c#

So I have this function:
var ordersInLast7Days = lineItems
.Where(x => x.OrderLogged > DateTime.Now.AddDays(-7))
.ToList();
ordersInLast7Days.ForEach(lineItem =>
{
var qty = lineItem.Quantity;
var pack = packs.FirstOrDefault(x => x.Id.Equals(lineItem.PackId));
if (pack != null)
{
orderTotalsInLast7Days += qty * pack.Price;
}
});
How would I make that into an Aggregate LINQ function that collects qty * pack.Price?
To elaborate somewhat, I have 3 of these functions that are all the same, so just guna have one method to apply to all Aggregates.

I agree with dasblinkenlight but to provide a solution using aggregate:
var total = lineItems.Where(x => x.OrderLogged > DateTime.Now.AddDays(-7) && packs.Any(y => y.Id.Equals(x.PackId)))
.Aggregate(0, (res, item) => res += item.Quantity * packs.First(y => y.Id.Equals(item.PackId)).Price);

You can use Sum to accumulate the total of qty * pack.Price, like this:
orderTotalsInLast7Days = ordersInLast7Days
.Select(lineItem => new {
qty = lineItem.Quantity
, pack = packs.FirstOrDefault(x => x.Id.Equals(lineItem.PackId))
})
.Where(p => p.pack != null)
.Sum(p => p.qty * p.pack.Price);
This is a straightforward translation of your iterative code, which uses an anonymous type instead of local variables.

You could do it this way:
orderTotalsInLast7Days =
(
from lineItem in lineItems
where lineItem.OrderLogged > DateTime.Now.AddDays(-7)
let qty = lineItem.Quantity
from pack in packs.Where(x => x.Id.Equals(lineItem.PackId)).Take(1)
select qty * pack.Price
).Sum();

Related

SQLite and LINQ: find all objects that have a sub list with all ids present in a supplied list of IDs

I have the following class:
public class Article
{
long Id;
List<Category> Categories;
}
I am using EF Core 5 and What I need is a LINQ query against SQLite that returns all the articles that have all the categories that I specify.
I tried the following code:
List<long> cIds = c.Select (x => x.Id).ToList ();
query.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId)));
but the compiler says
InvalidOperationException: The LINQ expression 'DbSet<Article>()
.Where(a => __cIds_0
.All(cId => DbSet<Dictionary<string, object>>("ArticleCategory")
.Where(a0 => EF.Property<Nullable<long>>(a, "Id") != null && object.Equals(
objA: (object)EF.Property<Nullable<long>>(a, "Id"),
objB: (object)EF.Property<Nullable<long>>(a0, "ArticlesId")))
.Join(
inner: DbSet<Category>(),
outerKeySelector: a0 => EF.Property<Nullable<long>>(a0, "CategoriesId"),
innerKeySelector: c => EF.Property<Nullable<long>>(c, "Id"),
resultSelector: (a0, c) => new TransparentIdentifier<Dictionary<string, object>, Category>(
Outer = a0,
Inner = c
))
.Select(ti => ti.Inner.Id)
.Any(p => p == cId)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
How can I obtain it?
A possible workaround I found is the following:
List<long> cIds = c.Select (x => x.Id).ToList ();
query = query.Where (art => art.Categories.Select (c => c.Id).Any (x => cIds.Contains (x)));
query = query.Include (x => x.Categories);
result = await query.ToListAsync ();
result = result.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId))).ToList ();
But I was wondering if I could obtain the same result with a single LINQ query.
Thanks in advance
UPDATE:
I'll just add the function where this code will be used and make make an example to make things clearer:
This is the function where the code will be used:
public async Task<List<Article>> SearchAsync (string search, Section s, Website w,
List<Category> c)
{
List<Article> result = new List<Article> ();
if (
search == ""
&& s == null
&& w == null
&& c.Count == 0
)
return result;
IQueryable<Article> query = dbSet.AsQueryable ();
if (search != "")
query = query.Where (x => x.Title.Contains (search) || x.Summary.Contains (search));
if (s != null)
query = query.Where (x => x.SectionId == s.Id);
if (w != null)
query = query.Where (x => x.WebsiteId == w.Id);
if (c.Count > 0)
{
List<long> cIds = c.Select (x => x.Id).ToList ();
query = query.Where (art => art.Categories.Select (c => c.Id).Any (x => cIds.Contains (x)));
}
query = query.Include (x => x.Categories);
result = await query.ToListAsync ();
if (c.Count > 0)
{
List<long> cIds = c.Select (x => x.Id).ToList ();
result = result.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId))).ToList ();
}
return result;
}
And here is an example:
Let's say c will contain ids 9,10,11 and the articles collection is the following pseudo code:
List<article> articles = new List<Article> ()
{
new Article () {Id = 1, Categories = "12,44,55"}
new Article () {Id = 2, Categories = "7,8,9,10,11"}
new Article () {Id = 3, Categories = "9,10,11"}
}
The linq query should return Article with Id 2 and 3 because both contains all of the ids present in c.
One of the solutions using Intersect, but we have to prepare data for intersection.
// articles query
var query = ...
var cIds = c.Select(x => x.Id).ToList();
var idsCount = cIds.Count();
// translating list of IDs to IQueryable
var categoryIdsQuery = dbContext.Categories
.Where(c => cIds.Contains(c.Id))
.Select(c => c.Id);
query = query
.Where(art => art.Categories
.Select(c => c.Id)
.Intersect(categoryIdsQuery)
.Count() == idsCount
)
.Include(x => x.Categories);
What I need is a LINQ query against SQLite that returns all the articles that have all the categories that I specify.
So you have a sequence of Category Ids and you want all Articles, each Article with only the Categories that are in your sequence of Category Ids.
I'm not sure what your variable 'c' is, but it seems to me that the following statement returns the Ids of all c:
List<long> cIds = c.Select (x => x.Id).ToList ();
If c is your sequence of Categories, then you will have the Ids of all existing categories. This will mean that you will have all Articles, each with all Categories.
If you have a local sequence of Category Ids, with a limited count (say about 250), then you should use Contains:
IEnumerable<long> categoryIds = ...
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => categoryIds.Contains(category.Id)
.ToList(),
})
So if you have CategoryIds 2, 3, and 12, this query will give you all Articles with only the Categories with ids 2, 3, 12.
If Article 40 has only Categories 20, 21, 21, then Article 40 will be in your result, but it will have an empty Categories list.
If you don't have your Category Ids locally, but you have a predicate to select the Category Ids, then your query will be like:
IQueryable<long> categoryIds = dbContext.Categories
.Where(category => category.Status == StatusCode.Obsolete); // predicate
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => categoryIds.Contains(category.Id)
.ToList(),
});
Because your first query is an IQueryable<...> it is not executed yet. If you want you can make it one big statement:
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => dbContext.Categories
.Where(category => category.Status == StatusCode.Obsolete)
.Contains(category.Id))
.ToList(),
});
Although this will not improve efficiency, it surely deteriorates readability.

How to add leading 0 to int variable

I have a list which contains instanceNumber properties of type int I want to add leading 0 if its value is less than 10. i.e 01,02...09 after that 10,11,12 and goes on etc. I tried following code but it did not work
var sidList = _sidRepository.GetAllList().Where(q=>q.IsDeleted==false).OrderByDescending(q=>q.Id).ToList();
if (sidList.Count > 0)
{
sidList.Where(w => w.InstanceNumber<10).ToList().
ForEach(s => s.InstanceNumber = s.InstanceNumber.Value.ToString("D").Length + 2);
}
Try this:
var sidList = _sidRepository.GetAllList()
.Where(q => !q.IsDeleted)
.OrderByDescending(q => q.Id)
.ToList();
if (sidList.Any())
{
sidList
.Where(w => w.InstanceNumber < 10)
.ToList()
.ForEach(s => s.InstanceNumber = s.InstanceNumber.Value.ToString("00").Length + 2);
}
What I would suggest:
var sidList = _sidRepository.GetAllList()
.Where(q => !q.IsDeleted)
.OrderByDescending(q => q.Id);
if (sidList.Any())
foreach (var item in sidList.Where(i => i.InstanceNumber < 10))
item.InstanceNumber = item.InstanceNumber.Value.ToString("00").Length + 2;
My suggestion will return a IEnumerable<item> instead of a List<item>, but you can handle it afterwards with a .ToList() if you need it, but you'll benefit from performance, because you'll avoid ToList(ing) two times.

How to generate duplicate items in a list using LINQ?

this is LINQ query I have used
var result = (from price in inventoryDb.Pricing.AsNoTracking()
where price.Quantity > 0m
select new
{
TagNo = price.TagNo,
SellingRate = price.SellingRate,
Quantity = price.Quantity
}).ToList();
Based on the Quantity value I need to generate duplicate items in the list.
Output :
result = [0]{TagNo="100", SellingRate=1500.00, Quantity=1}
[1]{TagNo="101", SellingRate=1600.00, Quantity=2}
Expected Result:
result = [0]{TagNo="100", SellingRate=1500.00}
[1]{TagNo="101", SellingRate=1600.00}
[2]{TagNo="101", SellingRate=1600.00}
You can use Enumerable.SelectMany + Enumerable.Range:
var result = inventoryDb.Pricing.AsNoTracking()
.Where(p => p.Quantity > 0m)
.SelectMany(p => Enumerable.Range(0, p.Quantity)
.Select(i => new
{
TagNo = p.TagNo,
SellingRate = p.SellingRate
}))
.ToList();
If that's not supported by your LINQ provider (f.e. Linq-To-Entities), the easiest is to use Linq-To-Objects. To avoid that all is loaded into memory you should use AsEnumerable after the Where:
var result = inventoryDb.Pricing.AsNoTracking()
.Where(p => p.Quantity > 0m)
.AsEnumerable()
.SelectMany(p => Enumerable.Range(0, p.Quantity)
.Select(i => new
{
TagNo = p.TagNo,
SellingRate = p.SellingRate
}))
.ToList();
Keeping with the query syntax just add a Enumerable.Repeat as follows:
var result = (from price in inventoryDb.Pricing.AsNoTracking()
where price.Quantity > 0m
from dup in Enumerable.Repeat(0,price.Quantity)
select new
{
TagNo = price.TagNo,
SellingRate = price.SellingRate,
}).ToList();
If indeed linq to entities does not support then add AsEnumerable like follows:
var result = (from price in inventoryDb.Pricing.AsNoTracking()
.Where(p => p.Quantity > 0m)
.AsEnumerable() //Loads only the filtered items to memory
from dup in Enumerable.Repeat(0,price.Quantity)
select new
{
TagNo = price.TagNo,
SellingRate = price.SellingRate,
}).ToList();
You can also use Enumerable.Range but because you do not use the value of that collection (and in my opinion also just that it describes better what you are doing) I decided just to go with Repeat

returning multiple column and sum using linq expression

I need to return two fields using a lambda expression. The first one is the sum of the amount field and the second one is CurrentFinancial year. Below is the code that I have written, how do I include CurrentFinancialYear?
var amount = dealingContext.vw_GetContribution
.Where(o => o.ContactID == contactId)
.Sum(o => o.Amount);
return new Contribution { Amount = amount ?? 0, CurrentFinancialYear = };
Grouping by Year should do the trick:
from entry in ledger.Entries
where entry.ContactID == contactId
&& entry.Time.Year == currentFinancialYear
group entry by entry.Time.Year
into g
select new Contribution ()
{
Amount = g.ToList ().Sum (e => e.Amount),
CurrentFinancialYear = g.Key
};
UPDATE - just return the first/default result...
(from entry in ledger.Entries
where entry.ContactID == contactId
&& entry.Time.Year == currentFinancialYear
group entry by entry.Time.Year
into g
select new Contribution ()
{
Amount = g.ToList ().Sum (e => e.Amount),
CurrentFinancialYear = g.Key
}).FirstOrDefault();
First of all use a simple select
var contribution = dealingContext.vw_GetContribution
.Where(o => o.ContactID == contactId).ToList();
It will give you a list of type vw_GetContribution
Then use groupby on this list as
var groupedContribution = contribution.GroupBy(b => b.CurrentFinancialYear).ToList();
Now you can iterate through or use this list as
foreach(var obj in groupedContribution.SelectMany(result => result).ToList())
{
var amount = obj.Amount;
var Year = obj.CurrentFinancialYear;
}
OR
In single line, you can do all the above as
var contList = context.vw_GetContribution
.Select(a => new { a.Amount, a.CurrentFinancialYear })
.GroupBy(b => b.CurrentFinancialYear)
.SelectMany(result => result).ToList();
I hope this will solve your problem.
Can you try this:
var amount = dealingContext.vw_GetContribution
.Where(o => o.ContactID == contactId)
.GroupBy(o=> new { o.CurrentFinancialYear, o.Amount})
.Select(group =>
new {
year= group.Key.CurrentFinancialYear,
sum= group.Sum(x=>x.Amount)
});

LINQ-to-SQL - 'Sum' inside a select new

I have a LINQ-to-SQL query that runs through a table, that I want to select 3 sum's - the sums of 'Rate' and 'AdditionalCharges', so I have something like this:
var sums = from d in dc.Deliveries
where d.TripDate == DateTime.Now
select new
{
Rate = d.Rate,
AdditionalCharges = d.AdditionalCharges
};
However, obviously this returns a new row for every delivery, which means I have to sum them up afterwards - which seems fairly inefficient. Is there an easier way?
I know that this is an old question, but hey, I found it, so hopefully this will help someone else...
You can also do this using Fluent syntax:
var sums = dc.Deliveries
.Where(d => d.TripDate == DateTime.Now)
.GroupBy(d => d.TripDate)
.Select(g =>
new
{
Rate = g.Sum(s => s.Rate),
AdditionalCharges = g.Sum(s => s.AdditionalCharges)
});
Hope this helps someone...
If you use query syntax you can do something like the following
var data = dc.Deliveries.Where(d => d.TripDate == DateTime.Now)
var rateSum = data.Sum(d => d.Rate);
var additionalCharges = data.Sum(d => d.AdditionalCharges);
this is off the top of my head and not tested
Not sure but you can try out the group bye function as below
var sums = from d in dc.Deliveries
where d.TripDate == DateTime.Now
group d by new {d.Rate,d.AdditionalCharges,d.TripDate} into g
select new
{
Rate = g.Sum(s => s.Rate ),
AdditionalCharges = g.Sum(s => s.AdditionalCharges)
};
You should be able to do this:
DateTime d = DateTime.Now;
var sums = from d in dc.Deliveries
select new
{
Rate = dc.Deliveries.Where(n => n.TripDate == d).Sum(n => n.Rate),
AdditionalCharges = dc.Deliveries.Where(n => n.TripDate == d).Sum(n => n.AdditionalCharges)
};
var result = sums.FirstOrDefault();
var sums = from d in dc.Deliveries
where d.TripDate == DateTime.Now
Group by d.TripDate // or primary key
Into TotalRate = sum(d.Rate),
TotalAdditionalCharges = sum(d.AdditionalCharges)
Select TotalRate , TotalAdditionalCharges

Categories