How to pass lambda expressions as parameter in C# - c#

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

Related

How to put contains on DataTable Rows using LINQ

This works:
exists = dt.AsEnumerable().Where(c => c.Field<int>("GENARTNR").Equals(int.Parse(nodeID))).Count() > 0;
Now nodeID is a string which can have more than 1 nodeIdz so 100,178,111,200 or 100 or 200,100
Now I want to do something like:
exists = dt.AsEnumerable().Where(nodeID.Contains(c => c.Field<string>("GENARTNR")));
But I am getting :
Cannot convert lambda expression to type 'char' because it is not a delegate type
Any work around this or some other suggestion?
First, don't use Field<string>("GENARTNR") is it's actually an int column.
You can use this approach that uses Contains with an int[]:
int[] nodeIDs = ParseNodeId(nodeId);
exists = dt.AsEnumerable().Any(r => nodeIDs.Contains(r.Field<int>("GENARTNR")));
private static int[] ParseNodeId(string nodeId)
{
return nodeId?.Trim().Split(',')
.Select(n => int.TryParse(n.Trim(), out int id) ? id : (int?)null)
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToArray() ?? Array.Empty<int>();
}

Keep distinct value in List depending on condition

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

Linq loop through child collection and sum revenue

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.

How could I use Func<> to modify a single condition of a lambda

I have a simple method containing a query to calculate some values.
private decimal MyQueryBuilderMethod(List<ThingsViewModel> myThings)
{
return myThings.Where(x => x.Id == 1)
.Select(x => (x.ThisValue * x.That))
.Sum();
}
My aim is to modify the method to allow me to specify the x.ThisValue field being queried in the object.
If I were to specify the Where clause of my query, I might pass a predicate but in this case, I only want to alter the value of x.ThisValue.
private decimal MyQueryBuilderMethod(List<ThingsViewModel> myThings, Func<ThingsViewModel,bool> predicates)
{
return myThings.Where(predicates)
.Select(x => (x.ThisValue * x.That))
.Sum();
}
Ideally, I'd like to pass in something like:
MyQueryBuilderMethod(things, x.ThisOtherValue)
Following should work:
private decimal MyQueryBuilderMethod(List<ThingsViewModel> myThings,
Func<ThingsViewModel, decimal> thisValue)
{
return myThings.Where(x => x.Id == 1)
.Sum(x => (thisValue(x) * x.That));
}
I also replaced the Select(...).Sum() by the shorter but equivalent Sum(...).
You then can call it like the following:
var result = MyQueryBuilderMethod(myThings, t => t.ThisValue);

How do i sum a list of items by code(or any field)?

I have an object that has a list of another object in it. i.e Object1 contains List<Object2>.
Assuming this is the definition of object 2:
public class Object2
{
string code,
string name,
decimal amount
}
I want to be a able to make a list2 from the list whose value will contain what something similar to what a select name, code, sum(amount) group by code kinda statement could have given me
this is what i did but it didnt contain what i needed on passing through.
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new { Amount = g.Sum(x => x.amount) });
I want code and name in the new list just like the sql statement above.
You're almost there:
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new
{
Code = g.First().code,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
This groups the items by code and creates an anonymous object for each group, taking the code and name of first item of the group. (I assume that all items with the same code also have the same name.)
If you are grouping by code and not by name you'd have to choose something for name from the list, perhaps with First() or Last() or something.
var newlist = obj2List.GroupBy(x => x.code).Select(g => new {
Code = g.Key,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
var query = Object1.Obj2List
.GroupBy(obj2 => obj2.code)
.Select(g => new {
Names = string.Join(",", g.Select(obj2.name)),
Code = g.Key,
Amount = g.Sum(obj2 => obj2.Amount)
});
Since you group by code only you need to aggregate the name also in some way. I have used string.Join to create a string like "Name1,Name2,Name3" for each code-group.
Now you could consume the query for example with a foreach:
foreach(var x in query)
{
Console.WriteLine("Code: {0} Names: {1} Amount: {2}"
, x.Code, x.Names, x.Amount);
}
Instead of using the LINQ Extension Methods .GroupBy() and .Select() you could also use a pure LINQ statement which is way easier to read if you come from a SQL Background.
var ls = new List<Object2>();
var newLs = from obj in ls
group obj by obj.code into codeGroup
select new { code = codeGroup.Key, amount = codeGroup.Sum(s => s.amount) };

Categories

Resources