GroupBy dynamic list on multiple properties by using reflection - c#

I have a class that defines some settings, one of this settings are the properties to group the list that you want to group by:
object of class MySetting
MySetting setting = new()
{
Groupby = $"{nameof(MyCss.Color)}, {nameof(MyCss.Width)}",
//.....
}
Now I have a dynamic list and I want to send this list as parameter with object setting to a method like ApplySetting, this method has to check if Groupby not a null and group my list:
public ApplySetting(List<TItem> myList, MySetting setting)
{
if(setting.Groupby != null)
{
var arr = setting.Groupby.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
//do some this like, this wrong !
var groubs = myList.GroupBy(x => arr.ForEach(y => GetPropertyValue(y, x, x.GetType())))
}
}
Note: GetPropertyValue is a method that get value from object by using reflection.
Thanks for any help.

This is not solution with reflection you asked for but hack, but maybe it can serve you.
It uses lib System.Linq.Dynamic.Core and converts list to Queriable.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
public class MySetting {
public string Groupby {get; set;}
}
public class ToGroupType{
public string Color {get; set;}
public string Width {get; set;}
}
public class Program
{
public static void Main()
{
MySetting setting = new()
{
Groupby = $"Color, Width",
//.....
};
static void ApplySetting<TItem>(List<TItem> myList, MySetting setting)
{
if(setting.Groupby != null)
{
//do some this like, this wrong !
var groubs = myList.AsQueryable().GroupBy($"new ({setting.Groupby})", "it").Select($"new (it.Key as Key , Count() as Count)").ToDynamicList();
Console.WriteLine(string.Join(",", groubs));
//result: Key = { Color = Red, Width = 10 }, Count = 2 },{ Key = { Color = Blue, Width = 10 }, Count = 2 },{ Key = { Color = Blue, Width = 15 }, Count = 1 }
}
}
ApplySetting(new List<ToGroupType>(){
new ToGroupType{Color = "Red", Width="10"},
new ToGroupType{Color = "Red", Width="10"},
new ToGroupType{Color = "Blue", Width="10"},
new ToGroupType{Color = "Blue", Width="10"},
new ToGroupType{Color = "Blue", Width="15"},
}, setting);
}}

Related

How to sort List by custom Order

Have some List of Application object
Application has Property Status and it holds values {"Red",Yellow,Blue ,Green and Orange")
My Requirement is to sort List in custom sort order
"Red" Should come first
"Blue" Second
"Yellow" Third
"Green" last
How to implement Sorting in this scenario .
Please help .
Thanks in advance
Well, you could create a list of sorted values and then sort by index in it:
var sortedValues = new List<string> {"Red", "Blue", "Yellow", "Green", "Orange"};
var result = myList.OrderBy(a => sortedValues.IndexOf(a.Status));
Define a new class with Id and Name of color property.
Create an array of the class and order the array by Id.
class CutomSort
{
class Color
{
public int Id;
public string Name;
}
static void Main(string[] args)
{
Color[] input = {
new Color{Id=4, Name="Green"},
new Color{Id=3, Name="Yellow"},
new Color{ Id=1, Name="Red"},
new Color{ Id = 2, Name = "Blue" }
};
IEnumerable<Color> result = input.OrderBy(x => x.Id);
foreach (Color color in result)
{
Console.WriteLine($"{color.Id}-{color.Name}");
}
Console.ReadKey();
}
}

To check a list and populate the value accordingly

I have a string list with values. I need to assign a value to a list based on the particular index of the string. Below is my code for the same.
var fruits = new string[] { "Color", "Price", "Shape ", "Nutrients" };
var fruitDetails = db.Fruits.Where(f => f.FruitId == 5).Select(f => new FruitModel{Id = f.FruitId,Category=f.Category, Color = f.FruitColor, Price=f.FruitPrice, Shape = f.FruitShape, Nutrients = f.FruitNutrients}).FirstOrDefault();
Now I need to populate a list using the results obtained from the Linq query based on the list of fruits.
foreach (var item in fruits )
{
var fruitData = new fruitData ();
fruitData.Category= fruitDetails .Category;
fruitData.Description= ; //This has to be the value of Color if item is color,value of price if item is price and so on...
fruitList.Add(fruitData);
}
So based on what the loop value is corresponding value needs to be populated. I do not want to be using Reflection. Is there an alternate method?
What if you use a switch statement like
switch (item)
{
case "Color":
fruitData.Description = fruitDetails.Color;
break;
case "Price":
fruitData.Description = fruitDetails.Price;
break;
case "Nutrient":
fruitData.Description = fruitDetails.Nutrient;
break;
default:
break;
}
I would suggest adding a property to FruitModel that returns the description based on the instance's Category, and that can use a static Dictionary to map categories to accessor functions:
public class FruitModel {
public int Id;
public string Category;
public string Color;
public double Price;
public string Shape;
public string Nutrients;
static Dictionary<string, Func<FruitModel, string>> catmap = new Dictionary<string, Func<FruitModel, string>> {
{ "Color", fm => fm.Color },
{ "Price", fm => fm.Price.ToString() },
{ "Shape", fm => fm.Shape },
{ "Nutrients", fm => fm.Nutrients },
};
public string Description {
get => catmap[Category](this);
}
public static List<string> FruitDetailCategories {
get => catmap.Keys.ToList();
}
}
You can also create a static property to return the detail categories rather than put the list somewhere else.
(Obviously you could use the switch instead of the Dictionary if preferred in the property body, but it doesn't lend itself to providing the detail categories.)
Now you can build your list easily:
var fruitList = new List<FruitData>();
foreach (var fruit in fruitDetails) {
var fd = new FruitData();
fd.Category = fruit.Category;
fd.Description = fruit.Description;
fruitList.Add(fd);
}

Element Metrics with Custom collection in C#

I am trying to figure out the best way to organise a bunch of my data classes, given I need to be able to access some metrics on them all at some point.
Here's a snippet of my OR class:
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
}
Not every OR I initialise will have values for all properties. I want to be able to 'collect' thousands of these together in such a way that I can easily obtain a count of how many OR objects had a value set. For example:
OR a = new OR() { reference = "a" }
OR b = new OR() { reference = "b", title = "test" }
OR c = new OR() { reference = "c", title = "test", status = status.CLOSED }
Now these are somehow collected in such a way I can do (pseudo):
int titleCount = ORCollection.titleCount;
titleCount = 2
I would also want to be able gather metrics for the enum type properties, for example retrieve a Dictionary from the collection that looks like:
Dictionary<string, int> statusCounts = { "CLOSED", 1 }
The reason for wanting access to these metrics is that I am building two collections of ORs and comparing them side-by-side for any differences (they should be identical). I want to be able to compare their metrics at this higher level first, then break-down where precisely they differ.
Thanks for any light that can be shed on how to accomplish this. :-)
... to 'collect' thousands of these
Thousands is not a huge number. Just use a List<OR> and you can get all your metrics with Linq queries.
For example:
List<OR> orList = ...;
int titleCount = orList
.Where(o => ! string.IsNullOrEmpty(o.title))
.Count();
Dictionary<status, int> statusCounts = orList
.GroupBy(o => o.status)
.ToDictionary(g => g.Key, g => g.Count());
The existing answers using Linq are absolutely great and really elegant, so the idea presented below is just for posterity.
Here is a (very rough) reflection-based program that will alow you to count the "valid" properties in any collection of objects.
The validators are defined by you in the Validators dictionary so that you can easily change what is a valid/invalid value for each property. You may find it useful as a concept if you end up with objects having tons of properties and don't want to have to write inline linq metrics on the actual collection itself for every single property.
You could weaponise this as a function and then run it against both collections, giving you a basis to report on the exact differences between both since it records the references to the individual objects in the final dictionary.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace reftest1
{
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
public int foo { get; set; }
}
//creates a dictionary by property of objects whereby that property is a valid value
class Program
{
//create dictionary containing what constitues an invalid value here
static Dictionary<string,Func<object,bool>> Validators = new Dictionary<string, Func<object,bool>>
{
{"reference",
(r)=> { if (r ==null) return false;
return !String.IsNullOrEmpty(r.ToString());}
},
{"title",
(t)=> { if (t ==null) return false;
return !String.IsNullOrEmpty(t.ToString());}
},
{"status", (s) =>
{
if (s == null) return false;
return !String.IsNullOrEmpty(s.ToString());
}},
{"foo",
(f) =>{if (f == null) return false;
return !(Convert.ToInt32(f.ToString()) == 0);}
}
};
static void Main(string[] args)
{
var collection = new List<OR>();
collection.Add(new OR() {reference = "a",foo=1,});
collection.Add(new OR(){reference = "b", title = "test"});
collection.Add(new OR(){reference = "c", title = "test", status = status.CLOSED});
Type T = typeof (OR);
var PropertyMetrics = new Dictionary<string, List<OR>>();
foreach (var pi in GetProperties(T))
{
PropertyMetrics.Add(pi.Name,new List<OR>());
foreach (var item in collection)
{
//execute validator if defined
if (Validators.ContainsKey(pi.Name))
{
//get actual property value and compare to valid value
var value = pi.GetValue(item, null);
//if the value is valid, record the object into the dictionary
if (Validators[pi.Name](value))
{
var lookup = PropertyMetrics[pi.Name];
lookup.Add(item);
}
}//end trygetvalue
}
}//end foreach pi
foreach (var metric in PropertyMetrics)
{
Console.WriteLine("Property '{0}' is set in {1} objects in collection",metric.Key,metric.Value.Count);
}
Console.ReadLine();
}
private static List<PropertyInfo> GetProperties(Type T)
{
return T.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
}
}
}
You can get the title count using this linq query:
int titleCount = ORCollection
.Where(x => !string.IsNullOrWhiteSpace(x.title))
.Count();
You could get the count of closed like this:
int closedCount = ORCollection
.Where(x => x.status == status.CLOSED)
.Count();
If you were going to have larger collections or you access the values a lot it might be worth creating a custom collection implementation that stores the field counts, it could then increment/decrement these values as you add and remove items. You could also store a dictionary of status counts in this custom collection that gets updated as you add and remove items.

How to select Dynamic column from List

I want to select columns dynamically from List as following. So what could be the best way?
//a objects list
List<DashBoard> dashboardlist = (List<DashBoard>)objList;
string strColumns = "RecDate,ModifiedDate";
objList = (from obj in dashboardlist select new { strColumns }).ToList();
/////////////
Ok,Just forget Object List say I have database table which have number of column ID,Name,Age,sex,etc ..Then I have columnList to display and the columnList is change according to condition . SO I have List people; and List columnTemplate; so now I want to select the column based on the template .
Thanks for providing ideas to my question.Spending couple of hours in
Google I found solution .
public void Test() {
var data = new[] {
new TestData { X = 1, Y = 2, Z = 3 }
, new TestData { X = 2, Y = 4, Z = 6 }
};
var strColumns = "X,Z".Split(',');
foreach (var item in data.Select(a => Projection(a, strColumns))) {
Console.WriteLine("{0} {1}", item.X, item.Z);
}
}
private static dynamic Projection(object a, IEnumerable<string> props) {
if (a == null) {
return null;
}
IDictionary<string,object> res = new ExpandoObject();
var type = a.GetType();
foreach (var pair in props.Select(n => new {
Name = n
, Property = type.GetProperty(n)})) {
res[pair.Name] = pair.Property.GetValue(a, new object[0]);
}
return res;
}
class TestData {
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
}
I assume that the list of the columns may come from an external resource and change, I propose:
With reflection you could produce a list of FieldInfo that correspond to each Column, then loop over each item on the list and each FieldInfo and call GetValue on the data object.
Here is the solution:
Select a Column Dynamically using LINQ?
and look Dynamic Linq: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
Let's supposed that you have only 2 templates. You can create a method for each template, that returns only the columns you need. Something like this:
// method for template 1 - returns only 3 columns/properties
private DashBoard CreateDashBoardXxxxxxx(DashBoard item)
{
return new DashBoard {
Property1 = item.Property1,
Property4 = item.Property2,
Property3 = item.Property3
};
}
// method for template 2 - returns N columns/properties
private DashBoard CreateDashBoardYyyyyyyy(DashBoard item)
{
return new DashBoard {
Property1 = item.Property1,
Property4 = item.Property2,
// Other properties
// .....
PropertyN = item.PropertyN
};
}
You can then use those methods like this:
List<DashBoard> dashboardlist = (List<DashBoard>)objList;
// using template 1
var list = dashboardlist.Select(CreateDashBoardXxxxxxx);
// using template 2
var list2 = dashboardlist.Select(CreateDashBoardYyyyyyyy);
You just need to do some code to decide which template should be used.
I hope this helps!!

Dynamic Expression using LINQ. How To Find the Kitchens?

I try do implement a user dynamic filter, where used selects some properties, selects some operators and selects also the values.
As I didn't find yet an answer to this question, I tried to use LINQ expressions.
Mainly I need to identify all houses which main rooms are kitchens(any sens, I know).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//using System.Linq.Dynamic;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Room aRoom = new Room() { Name = "a Room" };
Room bRoom = new Room() { Name = "b Room" };
Room cRoom = new Room() { Name = "c Room" };
House myHouse = new House
{
Rooms = new List<Room>(new Room[] { aRoom }),
MainRoom = aRoom
};
House yourHouse = new House()
{
Rooms = new List<Room>(new Room[] { bRoom, cRoom }),
MainRoom = bRoom
};
House donaldsHouse = new House()
{
Rooms = new List<Room>(new Room[] { aRoom, bRoom, cRoom }),
MainRoom = aRoom
};
var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse });
//var kitchens = houses.AsQueryable<House>().Where("MainRoom.Type = RoomType.Kitchen");
//Console.WriteLine("kitchens count = {0}", kitchens.Count());
var houseParam = Expression.Parameter(typeof(House), "house");
var houseMainRoomParam = Expression.Property(houseParam, "MainRoom");
var houseMainRoomTypeParam = Expression.Property(houseMainRoomParam, "Type");
var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType");
var comparison = Expression.Lambda(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant("Kitchen", typeof(RoomType)))
);
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
Console.WriteLine("kitchens count = {0}", kitchens.Count());
Console.ReadKey();
}
}
public class House
{
public string Address { get; set; }
public double Area { get; set; }
public Room MainRoom { get; set; }
public List<Room> Rooms { get; set; }
}
public class Room
{
public double Area { get; set; }
public string Name { get; set; }
public RoomType Type { get; set; }
}
public enum RoomType
{
Kitchen,
Bedroom,
Library,
Office
}
}
var kitchens = from h in houses
where h.MainRoom.Type == RoomType.Kitchen
select h;
But you must set the RoomType property on the rooms before.
Ok, edit:
so you must redefine:
var comparison = Expression.Lambda<Func<House, bool>>(...
Then, when you use it:
var kitchens = houses.AsQueryable().Where(comparison.Compile());
Edit #2:
Ok, here you go:
var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType");
// ???????????????????????? DOES NOT WORK
var comparison = Expression.Lambda<Func<House, bool>>(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant(Enum.Parse(typeof(RoomType), "Kitchen"), typeof(RoomType))), houseParam);
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
Edit #3: Of, for your needs, I am out of ideas for now. I give you one last one:
Declare an extension method on the String type:
internal static object Prepare(this string value, Type type)
{
if (type.IsEnum)
return Enum.Parse(type, value);
return value;
}
Then use it in that expression like:
Expression.Constant("Kitchen".Prepare(typeof(RoomType)), typeof(RoomType))
That's because apparently enums are treated differently. That extension will leave the string unaltered for other types. Drawback: you have to add another typeof() there.
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
The Where method takes a Func<House, bool> or a Expression<Func<House, bool>> as the parameter, but the variable comparison is of type LambdaExpression, which doesn't match. You need to use another overload of the method:
var comparison = Expression.Lambda<Func<House, bool>>(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant("Kitchen", typeof(RoomType))));
//now the type of comparison is Expression<Func<House, bool>>
//the overload in Expression.cs
public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters);
I wouldn't build the where clause in that way - I think it's more complex than it needs to be for your needs. Instead, you can combine where clauses like this:
var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse });
// A basic predicate which always returns true:
Func<House, bool> housePredicate = h => 1 == 1;
// A room name which you got from user input:
string userEnteredName = "a Room";
// Add the room name predicate if appropriate:
if (!string.IsNullOrWhiteSpace(userEnteredName))
{
housePredicate += h => h.MainRoom.Name == userEnteredName;
}
// A room type which you got from user input:
RoomType? userSelectedRoomType = RoomType.Kitchen;
// Add the room type predicate if appropriate:
if (userSelectedRoomType.HasValue)
{
housePredicate += h => h.MainRoom.Type == userSelectedRoomType.Value;
}
// MainRoom.Name = \"a Room\" and Rooms.Count = 3 or
// ?????????????????????????
var aRoomsHouses = houses.AsQueryable<House>().Where(housePredicate);
I tested this one, honest :)
what about this
var kitchens = houses
.SelectMany(h => h.Rooms, (h, r) => new {House = h, Room = r})
.Where(hr => hr.Room.Type == RoomType.Kitchen)
.Select(hr => hr.House);
To add a new Enum type to dynamic Linq, you must add the following code :
typeof(Enum),
typeof(T)
T : Enum type
in predefined types of dynamic. That works for me.

Categories