Iterating over properties and use each one in a lambda expression - c#

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.

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

About LINQ and LAMBDA Chain usage

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

How do I define a SELECT TOP using LINQ with a dynamic query?

I want to pass dynamic lambda expressions to the function below, but I'm not sure how to define the .Take() or .OrderByDescending() on the expression object.
If I want to call the function below, then I want to be able to do this:
dbprovider.Query = (x => x.ConfigurationReference == "172.16.59.175")
.Take(100)
.OrderByDescending(x.Date)
FindEntities(db, dbprovider.Query)
But I can't (this syntax is invalid). Any ideas?
public static List<T> FindEntities<T>(TrackingDataContext dataContext, System.Linq.Expressions.Expression<Func<T, bool>> find) where T : class
{
try
{
var val = dataContext.GetTable<T>().Where(find).ToList<T>();
return val;
}
catch (Exception ex)
{
throw ex;
}
}
The parameter is of type:
System.Linq.Expressions.Expression<Func<T, bool>> find
That means it can take a predicate (the "where" clause), and only a predicate. Thus the only bit you can pass in there is the filter:
x => x.ConfigurationReference == "172.16.59.175"
To do what you want, you would need to add the rest of the code in FindEntities, so that it becomes:
var val = dataContext.GetTable<T>().Where(find)
.OrderByDescending(x => x.Date).Take(100).ToList<T>();
(note also that the Take should really be after the OrderByDescending)
One way you could do that would be:
public static List<T> FindEntities<T>(TrackingDataContext dataContext,
System.Linq.Expressions.Expression<Func<T, bool>> find,
Func<IQueryable<T>, IQueryable<T>> additonalProcessing = null
) where T : class
{
var query = dataContext.GetTable<T>().Where(find);
if(additonalProcessing != null) query = additonalProcessing(query);
return query.ToList<T>();
}
and call:
var data = FindEntities(db, x => x.ConfigurationReference == "172.16.58.175",
q => q.OrderByDescending(x => x.Date).Take(100));
However, frankly I'm not sure what the point of this would be... the caller could do all of that themselves locally more conveniently, without using FindEntities at all. Just:
var data = db.GetTable<T>()
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or even:
var data = db.SomeTable
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or just:
var data = (from row in db.SomeTable
where row.ConfigurationReference == "172.16.58.175"
orderby row.Date descending
select row).Take(100).ToList();

trim away duplicates using Linq

I am working with an API that is returning duplicate Ids. I need to insert these values into my database using the EF. Before trying to add the objects I want to trim away any duplicates.
I have a small example of the code I am trying to write.
var itemsToImport = new List<Item>(){};
itemsToImport.Add(new Item() { Description = "D-0", Id = 0 });
for (int i = 0; i < 5; i++)
{
itemsToImport.Add(new Item(){Id = i,Description = "D-"+i.ToString()});
}
var currentItems = new List<Item>
{
new Item() {Id = 1,Description = "D-1"},
new Item(){Id = 3,Description = "D-3"}
};
//returns the correct missing Ids
var missing = itemsToImport.Select(s => s.Id).Except(currentItems.Select(s => s.Id));
//toAdd contains the duplicate record.
var toAdd = itemsToImport.Where(x => missing.Contains(x.Id));
foreach (var item in toAdd)
{
Console.WriteLine(item.Description);
}
What do I need to change to fix my variable "toAdd" to only return a single record even if there is a repeat?
You can do this by grouping by the Id and then selecting the first item in each group.
var toAdd = itemsToImport
.Where(x => missing.Contains(x.Id));
becomes
var toAdd = itemsToImport
.Where(x => missing.Contains(x.Id))
.GroupBy(item => item.Id)
.Select(grp => grp.First());
Use DistinctBy from MoreLINQ, as recommended by Jon Skeet in https://stackoverflow.com/a/2298230/385844
The call would look something like this:
var toAdd = itemsToImport.Where(x => missing.Contains(x.Id)).DistinctBy(x => x.Id);
If you'd rather not (or can't) use MoreLINQ for some reason, DistinctBy is fairly easy to implement yourself:
static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> sequence, Func<T, TKey> projection)
{
var set = new HashSet<TKey>();
foreach (var item in sequence)
if (set.Add(projection(item)))
yield return item;
}
You can use the Distinct function. You'll need to override Equals and GetHashCode in Item (Given they contain the same data) though.
Or use FirstOrDefault to get the first Item with the matching Id back.
itemsToImport.Where(x => missing.Contains(x.Id)).FirstOrDefault()

LINQ to objects: Is there a way to pass to LINQ the accessor from which to get values?

I cannot seem to find a way to have LINQ return the value from a specified accessor.
I know the name of the accessors for each object, but am unsure if it is possible to pass the requested accessor as a variable or otherwise achieve the desired refactoring.
Consider the following code snippet:
// "value" is some object with accessors like: format, channels, language
row = new List<String> {
String.Join(innerSeparator, (from item in myObject.Audio
orderby item.Key ascending
select item.Value.format).ToArray()),
String.Join(innerSeparator, (from item in myObject.Audio
orderby item.Key ascending
select item.Value.channels).ToArray()),
String.Join(innerSeparator, (from item in myObject.Audio
orderby item.Key ascending
select item.Value.language).ToArray()),
// ...
}
I'd like to refactor this into a method that uses the specified accessor, or perhaps pass a delegate, though I don't see how that could work.
string niceRefactor(myObj myObject, string /* or whatever type */ ____ACCESSOR) {
return String.Join(innerSeparator, (from item in myObject.Audio
orderby item.Key ascending
select item.Value.____ACCESSOR).ToArray());
}
I have written a decent amount of C#, but am still new to the magic of LINQ. Is this the right approach? How would you refactor this?
I'd extract the most obvious commonality to start with:
var audioItems = myObject.Audio.OrderBy(item => item.Key);
row = new List<String> {
String.Join(innerSeparator, audioItems.Select(x => x.Value).ToArray());
String.Join(innerSeparator, audioItems.Select(x => x.Format).ToArray());
String.Join(innerSeparator, audioItems.Select(x => x.Channels).ToArray());
String.Join(innerSeparator, audioItems.Select(x => x.Language).ToArray());
}
If I were using .NET 4, I'd then remove the ToArray calls as string.Join has more overloads now:
var audioItems = myObject.Audio.OrderBy(item => item.Key);
row = new List<String> {
String.Join(innerSeparator, audioItems.Select(x => x.Value));
String.Join(innerSeparator, audioItems.Select(x => x.Format));
String.Join(innerSeparator, audioItems.Select(x => x.Channels));
String.Join(innerSeparator, audioItems.Select(x => x.Language));
}
I might stop there. But if you wanted, you could always add another extension method:
public static string Separate<T>(this IEnumerable<T> items, string separator)
{
return string.Join(separator, items);
}
Then:
var audioItems = myObject.Audio.OrderBy(item => item.Key);
row = new List<String> {
audioItems.Select(x => x.Value).Separate(innerSeparator));
audioItems.Select(x => x.Format).Separate(innerSeparator));
audioItems.Select(x => x.Channels).Separate(innerSeparator));
audioItems.Select(x => x.Language).Separate(innerSeparator);
}
I'd almost certainly stop there. You could keep going:
public static IEnumerable<string> ProjectAndSeparateMany<T>(
this IEnumerable<T> items, string separator, Func<T, object>... projections)
{
return projections.Select(projection => items.Select(projection)
.Separate(separator);
}
And call it with:
var audioItems = myObject.Audio.OrderBy(item => item.Key);
row = audioItems.ProjectAndSeparateMany(innerSeparator,
x => x.Value, x => x.Format, x => x.Channels, x => x.Language).ToList();
... but at that point it's so specialist, I doubt that I'd ever use it again...
You can pass a Func<AudioType, object> to select the property you want:
string niceRefactor(myObj myObject, Func<AudioType,object> propertySelector)
{
return String.Join(innerSeparator, (from item in myObject.Audio
orderby item.Key ascending
select propertySelector(item.value)).ToArray());
}
This assumes that AudioType is the type of the value items returned by the Audio key value pairs.
You can then call your method e.g. like this:
string result = niceRefactor(myObject, x => x.format);
I'd like to refactor this into a method that uses the specified accessor, or perhaps pass a delegate, though I don't see how that could work.
You could do this using the object syntax and passing a delegate (This is assuming that your .Value is of type MyValueType):
string NiceRefactor(MyObj myObject, Func<MyValueType, string> accessor)
{
return string.Join(innerSeparator, myObject.Audio.OrderBy(m => m.Key).Select(m => accessor(m.Value));
}
Using this, you can write:
// "value" is some object with accessors like: format, channels, language
row = new List<String> {
NiceRefactor(myObject, v => v.format),
NiceRefactor(myObject, v => v.channels),
NiceRefactor(myObject, v => v.language),
// ...
}
You could do something like that:
// "value" is some object with accessors like: format, channels, language
row = new List<String> {
JoinProperties(myObject.Audio, innerSeparator, x => x.format),
JoinProperties(myObject.Audio, innerSeparator, x => x.channels),
JoinProperties(myObject.Audio, innerSeparator, x => x.language),
// ...
}
...
public string JoinProperties<TKey, TValue, TProperty>(IDictionary<TKey, TValue> dictionary, string separator, Func<TValue, TProperty> selector)
{
return string.Join(separator, dictionary.OrderBy(kvp => kvp.Key).Select(kvp => selector(kvp.Value)));
}

Categories

Resources