About LINQ and LAMBDA Chain usage - c#

I'm trying to understand how Linq chain or lambda chain is working but I couldn't get it. I wrote a sample code below. I can actually run the code with long way like that but I want to learn the other way. I used AWS SDK for the code. I'm trying to do single line code that is managed exactly the same thing while using lambda chain. There is a "_dict" variable for what I want to get from lambda Chain. I must use multiple "GroupBy"s and Select commands for "reservedList" variable but how can I do that?
Dictionary<string, Dictionary<string, int>> _dict = new Dictionary<string, Dictionary<string, int>>();
//Dict<AvailabilityZone, Dict<InstanceType, Count>>
var reservedList = _ec2Client.DescribeReservedInstances(new DescribeReservedInstancesRequest { }).ReservedInstances
.GroupBy(q => q.AvailabilityZone);
foreach (var _availabilityZoneItems in reservedList)
{
if (!_dict.ContainsKey(_availabilityZoneItems.Key))
_dict.Add(_availabilityZoneItems.Key, new Dictionary<string, int>());
var typeGroup = _availabilityZoneItems.GroupBy(q => q.InstanceType);
foreach (var _type in typeGroup)
{
var selectionCount = _type.Where(q => q.State == ReservedInstanceState.Active).Sum(q=>q.InstanceCount);
_dict[_availabilityZoneItems.Key].Add(_type.Key, selectionCount);
}
}
Trying to do something like
var reservedList = var reservedList = _ec2Client.DescribeReservedInstances(new DescribeReservedInstancesRequest { }).ReservedInstances
.GroupBy(q => q.AvailabilityZone)...
.GroupBy(q => q.InstanceType)
....Select...Where..Count...

You are probably looking for the Enumerable.SelectMany() This will flatten lists inside a list..
Something like: (untested/incomplete yet)
var typeGroup = _ec2Client
.DescribeReservedInstances(new DescribeReservedInstancesRequest {})
.ReservedInstances.GroupBy(q => q.AvailabilityZone)
.SelectMany(availabilityZone =>
availabilityZone
.GroupBy(q => q.InstanceType)
.Select(type => new
{
AvailabilityZone = availabilityZone,
Type = type
}));
foreach (var _type in typeGroup)
{
var selectionCount = _type.Type.Where(q => q.State == ReservedInstanceState.Active).Count();
_dict[_type.AvailabilityZoneItems.Key][_type.Type.Key] = selectionCount;
}
Maybe this points you into the right direction. I normally would use Linq for this..

I would try something along these lines
_ec2Client.DescribeReservedInstances(new DescribeReservedInstancesRequest()).ReservedInstances
.GroupBy(q => q.AvailabilityZone)
.ToList()
.ForEach (_availabilityZoneItems => {
_availabilityZoneItems.GroupBy(q => q.InstanceType)
.ToList()
.ForEach(_type => {
_dict[_availabilityZoneItems.Key][_type.Key] = _type.Where(q => q.State == ReservedInstanceState.Active).Count();
});
});
This is untested

Related

Get property from a Func<>

I'm trying to return the property that has been passed to a Func<> so I can then use this property with a DbSet. See below code for this.
Calling code:
_exampleRepository.CountByField(x => x.Status)
Receiving method in a generic repository:
public Dictionary<string, int> CountByField(Expression<Func<TEntity, string>> field)
{
var whichField = ((MemberExpression) field.Body).Member.Name;
var data = _dbSet.Select(x => whichField).GroupBy(y => y);
var returnData = new Dictionary<string, int>();
foreach (var item in data)
{
returnData.Add(item.Key, item.Count());
}
return returnData;
}
At the moment I'm able to return the name of the property passed, but I need to use the actual property instead to complete the _dbSet.Select() line properly.
Any ideas? I feel like I'm not a million miles away! Just need a point in the right direction.
Cheers.
You're overcomplicating things; your field argument is already fit to be passed directly to Select :) You should probably also aggregate the data in the query, rather than issuing a new query for each group you get:
public Dictionary<string, int> CountByField(Expression<Func<TEntity, string>> field)
=>
_dbSet
.Select(field)
.GroupBy(i => i)
.Select(i => new { Key = i.Key, Count = i.Count() })
.ToDictionary(i => i.Key, i => i.Count);

Iterating over properties and use each one in a lambda expression

I am trying to simplify my code a bit by iterating over the columns in a table and use each column to map to a value instead of having multiple lines of code with a single different value.
So going from this:
foreach(var n in groupedResults){
SetCellForData("Column1", n.Sum(x => x.ColumnName1FromTable));
SetCellForData("Column2", n.Sum(x => x.ColumnName2FromTable));
...
SetCellForData("Column10", n.Sum(x => x.ColumnName10FromTable));
}
To something like this:
var columnProperties = typeof(TableClass).GetProperties().Select(t => t);
foreach(var n in groupedResults){
foreach(var columnProperty in columnProperties ){
SetCellForData(columnProperty.Name, n.Sum(x => x.????);
}
}
Where the ???? part uses the columnProperty to Sum the column in the n grouped result.
Since arekzyla decided to delete his answer before I could confirm that it was what I needed then I have decided to answer my own question with arekzyla's answer.
This method creates the function:
private static Func<T, U> GetPropertyFunc<T, U>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var body = Expression.PropertyOrField(parameter, propertyName);
var lambda = Expression.Lambda<Func<T, U>>(body, parameter);
return lambda.Compile();
}
This code iterates over the properties in the database type and creates a function for each of them returning an integer.
var columnProperties = typeof(DatabaseType).GetProperties()
.Where(x => x.PropertyType == typeof(int))
.Select(t => new { t.Name, GetPropertyFunc = GetPropertyFunc<DatabaseType, int>(t.Name) })
.ToList();
Then we iterate over some grouped list:
foreach (var n in groupedList)
{
foreach (var property in columnProperties)
{
var sumOfElementsInGrouping = n.Sum(x => property.GetPropertyFunc(x));
}
}
Then we have the sum of the specific column based on the property.

Use List<Tuple<int, int>> to return data in Linq

Given:
List<int> myList;
If I wanted to return data where the record ID was contained in this list I would simply do:
var q = db.Table.Where(c=> myList.Contains(c.ID));
However, given:
List<Tuple<int, int>> myList;
How would I write a Linq query to return records where both conditions are met? With one data point I would write:
var q = db.Table.Where(c=>
c.ID == myList.Item1
&& c.AnotherValue == myList.Item2);
How would I convert the above statement to work on a List<Tuple<int, int>>?
A Tuple is a structure that can't not be translated to sql by your Linq Provider. A solution could be making a switch to Linq to Objects
var q = db.Table.AsEnumerable()
.Where(c=> myList.Any(tuple => c.ID == tuple.Item1 &&
c.AnotherValue == tuple.Item2));
But the bad thing about this solution is that you're going to load all the rows from that table to filter in memory.
Another solution could be using Linqkit:
var predicate = PredicateBuilder.False<Table>();
foreach (string t in myList)
{
predicate = predicate.Or(c =>c.ID == t.Item1 && c.AnotherValue == t.Item2));
}
db.Table.AsExpandable().Where(predicate);
You will find more info about this last solution in this link
var q = db.Table.AsEnumerable().Where(c => myList.Any(tuple => c.ID == tuple.Item1 &&
c.AnotherValue == tuple.Item2));
With Any you can check if there is at least one element in myList the matches your condition.
But as #octaviocci pointed out, this is not translatable to SQL, so you would need to call AsEnumerable() before and do the filtering locally, which may not be what you want if there are a lot of irrelevant records.
Here is some sample code that illustrates one approach:
DataTable dt = new DataTable("demo");
// hydrate your table here...
List<Tuple<int, int>> matches = new List<Tuple<int, int>>();
Func<List<Tuple<int,int>>, DataRow, bool> RowMatches = (items, row) => {
var rowValue1 = (int)row["Id"];
var rowValue2 = (int)row["SomeOtherValue"];
return items.Any(item => item.Item1 == rowValue1 && item.Item2 == rowValue2);
};
var results = dt.Rows.Cast<DataRow>().Where(r => RowMatches(matches, r));
Console.WriteLine(results.Any());
See code below:
List<Tuple<int, int>> myList;
var set = new HashSet(myList);
var q = db.Table.AsEnumerable().Where(c=> set.Contains(new Tuple(c.ID, c.AnotherValue)));
Note that hash set is used to performance-optimize the execution of Where clause for large myList.
Since Tuple cannot be used in Linq to Entities, you can try something like this:
List<int> items1 = myList.Select(t => t.Item1).ToList();
List<int> items2 = myList.Select(t => t.Item2).ToList();
var q = db.Table.GroupBy(m => { m.ID, m.AnotherValue })
.Where(g => items1.Contains(g.Key.ID) &&
items2.Contains(g.Key.AnotherValue))
.SelectMany(g => g);

c# Static Member inside ForEach

I have the following:
blocks.Where(x => x.BlockName == NAVIGATION)
.ForEach(block => block.Data = db.Pages.Select(x => x.Name).ToList());
If where finds more than one block, how can I prevent the db.Pages.Select from performing the database hit for every block and simply reuse the List for the second and subsequent blocks.
Can you have static variables inside lambda functions?
UPDATE: I don't want to perform the fetch at all if the where finds zero blocks.
UPDATE: blocks is an in-memory List
var blocksResult = blocks.Where(x => x.BlockName == NAVIGATION);
if (blocksResult.Any())
{
var blockData = db.Pages.Select(x => x.Name).ToList();
blocksResult.ForEach(block => block.Data = blockData);
}
OR
List<string> blockData = null;
blocks.Where(x => x.BlockName == NAVIGATION).ForEach(block => block.Data = (blockData ?? (blockData = db.Pages.Select(x => x.Name).ToList())))

Cannot assign void to an implicitly-typed local variable

var query = rep.GetIp() // in this line i have the error
.Where(x => x.CITY == CITY)
.GroupBy(y => o.Fam)
.Select(z => new IpDTO
{
IId = z.Key.Id,
IP = z.Select(x => x.IP).Distinct()
})
.ToList().ForEach(IpObj => IpObj.IP.ToList().ForEach(ip => PAINTIP(ip)));
When I run this code I have the error:
Cannot assign void to an implicitly-typed local variable
I googled and found that it is a type issue because foreach is not a LINQ function? I cannot understand where the void is!
ForEach() has type void.
Select() returns IEnumerable<T>, ToList() returns List<T>, etc.
so:
List<X> x = ...Select(x => x).ToList(); // List<T>
or
x.ForEach(x => x); // void
because you can't assign void to List<T>.
var query = rep.GetIp() // in this line i have the error
.Where(x => x.CITY == CITY)
.GroupBy(y => o.Fam)
.Select(z => new IpDTO
{
IId = z.Key.Id,
IP = z.Select(x => x.IP).Distinct()
});
foreach (var dto in query)
{
foreach (var ip in dto.IP)
{
PAINTIP(ip);
}
}
or
var query = ....
.SelectMany(z => z.Select(x => x.IP).Distinct());
foreach (var ip in query)
{
PAINTIP(ip);
}
ForEach() does not return anything. Its type is void.
Try replacing your ForEach() calls with Select().
I saw your other questions, wherein you have asked similar question for the same query. The code partially looks like this :
var Q = rep.GetIp()
.Where(x => x.CITY == CITY)
.GroupBy(y => o.Fam)
.Select(z => new IpDTO
{
IId = z.Key.Id,
IP = z.Select(x => x.IP).Distinct()
});
Looks like you have used the answer as such and you are trying to assign the returned value from the query to the variable "Q". Check out your previous post : syntax in LINQ IEnumerable<string>
As others have said, ForEach return type is "void". You should call "ForEach", once the variable "Q" is initialized with the collection.

Categories

Resources