I have a structure which looks like following:
class Items
{
//item properties
List<Transactions> _ItemTransactions {get;set;}
}
And the transactions class contains following elements:
class Transactions
{
public int QuantitySoldTotal {get;set;}
public double TransactionPrice {get;set;}
}
I'm trying to sum all the revenu of all items transactions alltogether. I have tried to do something like this:
var totalRevenue = Context.Items.AsParallel().Select(x => x._ItemTransactions.Sum(y => y.TransactionPrice * y.QuantitySoldTransaction)).FirstOrDefault();
But I always get 0 value in return... Can someone help me out with this ?
Linq has Sum method
double totalRevenue = items._ItemTransactions.Sum(transaction => transaction.TransactionPrice * transaction.QuantitySoldTotal);
To sum list of lists you can do something like
double totalRevenue = Context.Items.Sum(items => items._ItemTransactions.Sum(transaction => transaction.TransactionPrice * transaction.QuantitySoldTotal));
Or
double totalRevenue = Context.Items.SelectMany(items => items._ItemTransactions).Sum(transaction => transaction.QuantitySoldTotal * transaction.TransactionPrice);
var totalRevenue = Context.Items.AsParallel().Select(x => x.Transactions.Sum(y => y.TransactionPrice * y.QuantitySoldTransaction)).FirstOrDefault();
lets break down what your doing here
Context.Items.AsParallel() Run in parallel (probably not needed) returning IEnumerable<Item>
.Select(x => x.Transactions.Sum(y => y.TransactionPrice * y.QuantitySoldTransaction)) for each Item in the list sum up the transactions thus leaving you with an IEnumerable<double>
.FirstOrDefault(); get the first item from the IEnumerable<double> result which in your case is probably, by chance, always zero
my guess is what you wanted is
var totalRevenue = Context.Items
.Sum(x => x._ItemTransactions
.Sum(y => y.TransactionPrice * y.QuantitySoldTransaction)
);
basically not returning FirstOrDefault but suming the sum
or alternativly
var totalRevenue = Context.Items
.SelectMany(x => x._ItemTransactions)
.Sum(x => x.QuantitySoldTotal * x.TransactionPrice);
This should give you the expected result:-
var totalRevenue = Context.Items.SelectMany(x => x._ItemTransactions)
.Sum(x => x.QuantitySoldTotal * x.TransactionPrice);
First use SelectMany to flatten your inner list i.e. _ItemTransactions, after this you can simply call the LINQ Sum method to perform a sum like you do on a normal list.
Related
I'm trying to use a custom method for ordering but I also want to use that same custom method to only return results that match a certain value. I realize that the code below works but I was hoping there was a way to combine both methods to hopefully speed up the process.
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var bestList = inputList.Where(x => x != null && CalculateAverage(x) > 0).
OrderByDescending(x => CalculateAverage(x)));
return bestList;
}
public decimal CalculateAverage(List<decimal> inputList)
{
return inputList.Average();
}
As far as I understand you want to prevent recalculation of average, so you can use Select to create a temporary tuple containing average and original list, for example like that:
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var bestList = inputList
.Where(x => x != null )
.Select(x => (x, Avg: CalculateAverage(x)))
.Where(x => x.Avg > 0)
.OrderByDescending(x => x.Avg)
.Select(x => x.x);
return bestList;
}
The way to avoid performing the potentially expensive computation multiple times is to project the sequence into a new value that includes the list and the computation. This is simpler and easier with query syntax than method syntax:
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var query = from list in inputList
where list != null
let average = CalculateAverage(list)
where average > 0
orderby average
select list;
}
I have a list where I'm applying the following condition with linQ:
I want to select all items where Name contains a certain string.
var nameFilter = result
.Where(e => e.Name.Contains(requestedValue))
.ToList();
At the end, sometimes it happens that I am having a list with repeated names:
For example:
requestedValue = 'form';
I end up with:
Name Price
transformer 100
transformer 20
formation 340
former 201
I got transformer twice. In that case, I want to only leave transformer with the least price : 20
How could I do this with linQ without looping?
You can take advantage of GroupBy method
var nameFilter = result.Where(e => e.Name.Contains(requestedValue))
.GroupBy(k=>k.Name, g=>g.Price, (k,g)=>new Model {Name = k, Price = g.Min()})
.ToList();
where new Model should be changed to your class name.
If you have more properties to return probably it will be more convenient to do
var nameFilter = result.Where(e => e.Name.Contains(requestedValue))
.GroupBy(k => k.Name, g => g, (k, g) =>
{
var minPrice = g.Min(x => x.Price);
return g.First(x => x.Price == minPrice);
}).ToList();
Finding minPrice and finding the item with minPrice can be done is a single for loop or, for example, by using following discussion here
I am beginner at using lambda expressions.
I have a list of dealers, foreach dealer I have to calculate grade.
The request is that the grade calculation to be separated into a separate method.
So I am writing the below two methods, however I am unable to pass parameters to CalculateGrade() method,
public IEnumerable<Dealers> GetDealerGrades(IEnumerable<Dealers> gradeData)
{
return gradeData
.GroupBy(row => new { row.Name })
.Select(g => new Dealers
{
Name = g.Key.Name,
TotalPoints = CalculateGrade(x => Convert.ToDouble(x.RecievedPoints),
y => y.MaxPoints,
z => Convert.ToDouble(z.Weightage))
})
.ToList();
}
private double CalculateGrade(double d1, int d2, double d3)
{
return ( d1 / d2 )
* d3 == 0.00 ? 1
: d3;
}
Can somebody advise how to pass parameters in this , or how to pass lamda expressions and calculate grade?
Many thanks in advance...
Looks like you need that:
return gradeData
.GroupBy(row => row.Name)
.Select(g => new Dealers
{
Name = g.Key.Name,
TotalPoints = g.Sum(x => CalculateGrade(Convert.ToDouble(x.RecievedPoints),
x.MaxPoints,
Convert.ToDouble(x.Weightage)))
})
.ToList();
It will call CalculateGrade method on every element from group and sum returned values into TotalPoints property.
Or you can change your CalculateGrade to take IEnumerabale<Dealers>:
private double CalculateGrade(IEnumerable<Dealers> dealers)
{
// calculations here
return dealers.Sum(x => CalculateGrade(Convert.ToDouble(x.RecievedPoints),
x.MaxPoints,
Convert.ToDouble(x.Weightage)))
}
And use it in your query:
return gradeData
.GroupBy(row => row.Name)
.Select(g => new Dealers
{
Name = g.Key.Name,
TotalPoints = CalculateGrade(g)
})
.ToList();
This doesn't solve your problem, but it gives you an overview how to send lambdas into methods
You would use Func & Action to pass lambdas into a method
Func
Can have 1 - 15 input parameters and must have an output parameter
Action
Can have 1 - 16 input parameters with no output parameter
ie, how I imagine they do it in EntityFramework for a where predicate
public static List<People> Filter<TEntity>(this List<People> myList, Func<TEntity, TResult> predicate)
{
return myList.Where(predicate).ToList();
}
the usage would then be something like
myList.Filter(ml => ml.Age > 18);
I have this function below that takes a list of id's and searches the DB for the matching persons.
public IQueryable<Person> GetPersons(List<int> list)
{
return db.Persons.Where(a => list.Contains(a.person_id));
}
The reason I need to split this into four queries is because the query can't take more than 2100 comma-separated values:
The incoming tabular data stream (TDS) remote procedure call (RPC) protocol stream is incorrect. Too many parameters were provided in this RPC request. The maximum is 2100.
How can I split the list into 4 pieces and make a query for each list. Then join the results into one list of persons?
Solved
I don't want to post it as an own answer and take cred away from #George Duckett's answer, just show the solution:
public IQueryable<Person> GetPersons(List<int> list)
{
var persons = Enumerable.Empty<Person>().AsQueryable<Person>();
var limit = 2000;
var result = list.Select((value, index) => new { Index = index, Value = value })
.GroupBy(x => x.Index / limit)
.Select(g => g.Select(x => x.Value).ToList())
.ToList();
foreach (var r in result)
{
var row = r;
persons = persons.Union(db.Persons.Where(a => row.Contains(a.person_id)));
}
return persons;
}
See this answer for splitting up your list: Divide a large IEnumerable into smaller IEnumerable of a fix amount of item
var result = list.Select((value, index) => new { Index = index, Value = value})
.GroupBy(x => x.Index / 5)
.Select(g => g.Select(x => x.Value).ToList())
.ToList();
Then do a foreach over the result (a list of lists), using the below to combine them.
See this answer for combining the results: How to combine Linq query results
I am not sure why you have a method like this. What exactly are you trying to do. Anyway you can do it with Skip and Take methods that are used for paging.
List<Person> peopleToReturn = new List<Person>();
int pageSize = 100;
var idPage = list.Skip(0).Take(pageSize).ToList();
int index = 1;
while (idPage.Count > 0)
{
peopleToReturn.AddRange(db.Persons.Where(a => idPage.Contains(a.person_id)).ToList());
idPage = list.Skip(index++ * pageSize).Take(pageSize).ToList();
}
Assuming this object:
DXMessage
{
public byte[] msg;
public int time;
public int millisecond;
}
and assuming that I have 2 sorted lists:
public static SortedList<long, DXMessage> brimstoneMessages =
new SortedList<long, DXMessage>();
public static SortedList<long, DXMessage> gpsMessages =
new SortedList<long, DXMessage>();
I have executed 2 queries on 2 different lists of messages:
var bsQuery = GlobalObjects.bsMessages.Where(t =>
((t.Value.Time >= eventStart))).ToList();
var gpsQuery = GlobalObjects.gpsMessages.Where(t =>
((t.Value.Time >= eventStart))).ToList();
I would like to take the results of these 2 queries, and join them in ascending order by Time and millisecond.
By "join" do you mean "concatenate" rather than a sort of SQL join? I suspect you just want:
var combined = bsQuery.Concat(gpsQuery)
.OrderBy(x => x.Value.time)
.ThenBy(x => x.Value.millisecond);
It's not clear why you've got so many brackets in your queries by the way - and in this case it looks like you could actually perform the combination earlier:
var combined = GlobalObjects.bsMessages
.Concat(GlobalObjects.gpsMessages)
.Where(t => t.Value.Time >= eventStart)
.OrderBy(t => t.Value.Time)
.ThenBy(t => t.Value.Millisecond);