I have a class with large amount of properties that I need to group by almost all columns.
class Sample {
public string S1 { get; set; }
public string S2 { get; set; }
public string S3 { get; set; }
public string S4 { get; set; }
// ... all the way to this:
public string S99 { get; set; }
public decimal? N1 { get; set; }
public decimal? N2 { get; set; }
public decimal? N3 { get; set; }
public decimal? N4 { get; set; }
// ... all the way to this:
public decimal? N99 { get; set; }
}
From time to time I need to group by all columns except one or two decimal columns and return some result based on this (namely object with all the fields, but with some decimal value as a sum or max).
Is there are any extension method that would allow me to do something like this:
sampleCollection.GroupByExcept(x => x.N2, x => x.N5).Select(....);
instead of specifying all columns in object?
You won't find anything builtin that handles such a case. You'd have to create one yourself. Depending on how robust you need this to be, you could take a number of approaches.
The main hurdle you'll come across is how you'll generate the key type. In an ideal situation, the new keys that are generated would have their own distinct type. But it would have to be dynamically generated.
Alternatively, you could use another type that could hold multiple distinct values and still could be suitably used as the key. Problem here is that it will still have to be dynamically generated, but you will be using existing types.
A different approach you could take that doesn't involve generating new types, would be to use the existing source type, but reset the excluded properties to their default values (or not set them at all). Then they would have no effect on the grouping. This assumes you can create instances of this type and modify its values.
public static class Extensions
{
public static IQueryable<IGrouping<TSource, TSource>> GroupByExcept<TSource, TXKey>(this IQueryable<TSource> source, Expression<Func<TSource, TXKey>> exceptKeySelector) =>
GroupByExcept(source, exceptKeySelector, s => s);
public static IQueryable<IGrouping<TSource, TElement>> GroupByExcept<TSource, TXKey, TElement>(this IQueryable<TSource> source, Expression<Func<TSource, TXKey>> exceptKeySelector, Expression<Func<TSource, TElement>> elementSelector)
{
return source.GroupBy(BuildKeySelector(), elementSelector);
Expression<Func<TSource, TSource>> BuildKeySelector()
{
var exclude = typeof(TXKey).GetProperties()
.Select(p => (p.PropertyType, p.Name))
.ToHashSet();
var itemExpr = Expression.Parameter(typeof(TSource));
var keyExpr = Expression.MemberInit(
Expression.New(typeof(TSource).GetConstructor(Type.EmptyTypes)),
from p in typeof(TSource).GetProperties()
where !exclude.Contains((p.PropertyType, p.Name))
select Expression.Bind(p, Expression.Property(itemExpr, p))
);
return Expression.Lambda<Func<TSource, TSource>>(keyExpr, itemExpr);
}
}
}
Then to use it you would do this:
sampleCollection.GroupByExcept(x => new { x.N2, x.N5 })...
But alas, this approach won't work under normal circumstances. You won't be able to create new instances of the type within a query (unless you're using Linq to Objects).
If you're using Roslyn, you could generate that type as needed, then use that object as your key. Though that'll mean you'll need to generate the type asynchronously. So you probably will want to separate this from your query all together and just generate the key selector.
public static async Task<Expression<Func<TSource, object>>> BuildExceptKeySelectorAsync<TSource, TXKey>(Expression<Func<TSource, TXKey>> exceptKeySelector)
{
var exclude = typeof(TXKey).GetProperties()
.Select(p => (p.PropertyType, p.Name))
.ToHashSet();
var properties =
(from p in typeof(TSource).GetProperties()
where !exclude.Contains((p.PropertyType, p.Name))
select p).ToList();
var targetType = await CreateTypeWithPropertiesAsync(
properties.Select(p => (p.PropertyType, p.Name))
);
var itemExpr = Expression.Parameter(typeof(TSource));
var keyExpr = Expression.New(
targetType.GetConstructors().Single(),
properties.Select(p => Expression.Property(itemExpr, p)),
targetType.GetProperties()
);
return Expression.Lambda<Func<TSource, object>>(keyExpr, itemExpr);
async Task<Type> CreateTypeWithPropertiesAsync(IEnumerable<(Type type, string name)> properties) =>
(await CSharpScript.EvaluateAsync<object>(
AnonymousObjectCreationExpression(
SeparatedList(
properties.Select(p =>
AnonymousObjectMemberDeclarator(
NameEquals(p.name),
DefaultExpression(ParseTypeName(p.type.FullName))
)
)
)
).ToFullString()
)).GetType();
}
To use this:
sampleCollection.GroupBy(
await BuildExceptKeySelector((CollectionType x) => new { x.N2, x.N5 })
).Select(....);
Borrowing from this answer here:
Create a class EqualityComparer
public class EqualityComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
IDictionary<string, object> xP = x as IDictionary<string, object>;
IDictionary<string, object> yP = y as IDictionary<string, object>;
if (xP.Count != yP.Count)
return false;
if (xP.Keys.Except(yP.Keys).Any())
return false;
if (yP.Keys.Except(xP.Keys).Any())
return false;
foreach (var pair in xP)
if (pair.Value.Equals( yP[pair.Key])==false)
return false;
return true;
}
public int GetHashCode(T obj)
{
return obj.ToString().GetHashCode();
}
}
Then create your GroupContent method:
private void GroupContent<T>(List<T> dataList, string[] columns, string[] columnsToExclude)
{
string[] columnsToGroup = columns.Except(columnsToExclude).ToArray();
EqualityComparer<IDictionary<string, object>> equalityComparer = new EqualityComparer<IDictionary<string, object>>();
var groupedList = dataList.GroupBy(x =>
{
var groupByColumns = new System.Dynamic.ExpandoObject();
((IDictionary<string, object>)groupByColumns).Clear();
foreach (string column in columnsToGroup)
((IDictionary<string, object>)groupByColumns).Add(column, GetPropertyValue(x, column));
return groupByColumns;
}, equalityComparer);
foreach (var item in groupedList)
{
Console.WriteLine("Group : " + string.Join(",", item.Key));
foreach (object obj in item)
Console.WriteLine("Item : " + obj);
Console.WriteLine();
}
}
private static object GetPropertyValue(object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName).GetValue(obj, null);
}
I extended the code above borrowing another answer.
public static class IEnumerableExt {
public static IEnumerable<T> GroupBye<T, C>(this IEnumerable<T> query, Func<IGrouping<IDictionary<string, object>, T>, C> grouping) where T : class
{
var cProps = typeof(C).GetProperties().Select(prop => prop.Name).ToArray();
var columnsToGroup = typeof(T).GetProperties().Select(prop => prop.Name).Except(cProps).ToArray();
var equalityComparer = new EqualityComparer<IDictionary<string, object>>();
return query
.GroupBy(x => ExpandoGroupBy(x, columnsToGroup), equalityComparer)
.Select(x => MergeIntoNew(x, grouping, cProps));
}
private static IDictionary<string, object> ExpandoGroupBy<T>(T x, string[] columnsToGroup) where T : class
{
var groupByColumns = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
groupByColumns.Clear();
foreach (string column in columnsToGroup)
groupByColumns.Add(column, typeof(T).GetProperty(column).GetValue(x, null));
return groupByColumns;
}
private static T MergeIntoNew<T, C>(IGrouping<IDictionary<string, object>, T> x, Func<IGrouping<IDictionary<string, object>, T>, C> grouping, string[] cProps) where T : class
{
var tCtor = typeof(T).GetConstructors().Single();
var tCtorParams = tCtor.GetParameters().Select(param => param.Name).ToArray();
//Calling grouping lambda function
var grouped = grouping(x);
var paramsValues = tCtorParams.Select(p => cProps.Contains(p) ? typeof(C).GetProperty(p).GetValue(grouped, null) : x.Key[p]).ToArray();
return (T)tCtor.Invoke(paramsValues);
}
private class EqualityComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
var xDict = x as IDictionary<string, object>;
var yDict = y as IDictionary<string, object>;
if (xDict.Count != yDict.Count)
return false;
if (xDict.Keys.Except(yDict.Keys).Any())
return false;
if (yDict.Keys.Except(xDict.Keys).Any())
return false;
foreach (var pair in xDict)
if (pair.Value == null && yDict[pair.Key] == null)
continue;
else if (pair.Value == null || !pair.Value.Equals(yDict[pair.Key]))
return false;
return true;
}
public int GetHashCode(T obj)
{
return obj.ToString().GetHashCode();
}
}
}
Which can be used in the following way:
var list = enumerable.GroupBye(grp => new
{
Value = grp.Sum(val => val.Value)
});
The result will like grouping all other columns but Value, which will be valued to the sum of grouped elements' value
Related
I am getting a System.StackOverflowException: 'Exception of type 'System.StackOverflowException' was thrown.' message.
My code as follows, Here I want to assign value to a variable recursively based on the condition and return the list.
public class FancyTree
{
public string title { get; set; }
public string key { get; set; }
public List<FancyTree> children { get; set; }
}
For example the FancyTree Class produces the output like parent->child or parent->parent->child or parent->parent->parent->child just like the Treeview structure.
public JsonResult EmployeesTree()
{
var output = converttoFancyTree(db.Database.GetEmployees(true));
return Json(output, JsonRequestBehavior.AllowGet);
}
public List<FancyTree> converttoFancyTree(List<EmpTable> emps)
{
var output = new List<FancyTree>();
foreach (var emp in emps)
{
var fancyTreeItem = new FancyTree();
fancyTreeItem.key = emp.EMP_ID.ToString();
fancyTreeItem.title = emp.EMP_NAME;
if (!string.IsNullOrEmpty(emp.TEAM))
{
//var empIDs = emp.TEAM?.Split(',')?.Select(Int32.Parse)?.ToList();
var tms = emp.TEAM.Split(',');
if (tms.Length > 0) {
var empIDs = new List<int>();
foreach (var t in tms)
{
empIDs.Add(int.Parse(t));
}
var TeamMembers = emps.Where(x => empIDs.Contains(x.EMP_ID)).ToList();
if (TeamMembers.Count > 0)
{
var childrens = converttoFancyTree(TeamMembers);
fancyTreeItem.children = childrens;
}
}
}
output.Add(fancyTreeItem);
}
return output;
}
I would assume your input is in the form of a plain list of objects, where each object contains the IDs of all the children, and you want to convert this to an object representation, i.e. something like:
public class Employee{
public int Id {get;}
public List<int> SubordinateIds {get;}
}
public class EmployeeTreeNode{
public IReadOnlyList<EmployeeTreeNode> Subordinates {get;} ;
public int Id {get;}
public EmployeeTreeNode(int id, IEnumerable<EmployeeTreeNode> subordinates){
Id = id;
Subordinates = subordinates;
}
To convert this to a tree representation we can start by finding the roots of the tree, i.e. employees that are not subordinate to anyone.
var allSubordinates = allEmployees.SelectMany(e => e.SubordinateIds).ToList();
var allRoots = allEmployees.Select(e => e.Id).Except(allSubordinates);
We then need an efficient way to find a specific employee by the Id, i.e. a dictionary:
var employeeById = allEmployees.ToDictionary(e => e.Id, e => e.SubordinateIds);
We can then finally do the actual recursion, and we can create a generic helper method to assist:
public static TResult MapChildren<T, TResult>(
T root,
Func<T, IEnumerable<T>> getChildren,
Func<T, IEnumerable<TResult>, TResult> map)
{
return RecurseBody(root);
TResult RecurseBody(T item) => map(item, getChildren(item).Select(RecurseBody));
}
...
var tree = allRoots.Select(r => MapChildren(
r,
id => employeeById[id],
(id, subordinates) => new EmployeeTreeNode(id, subordinates)));
This will recurse down to any employee without any subordinates, create EmployeeTreeNode for these, and then eventually traverse up the tree, creating node objects as it goes.
This assumes that there are no loops/cycles. If that is the case you do not have a tree, since trees are by definition acyclic, and the code will crash. You will instead need to handle the more general case of a graph, and this is a harder problem, and you will need to decide how the cycles should be handled.
I have the following method which determines which cars I need to delete from the DB.
private List<CarDTO> BuildCarsToDelete(IList<CarDTO> newCars, IList<CarDTO> existingCars)
{
var missingCars = new List<CarDTO>();
var cars = newCars.Select(c => c.CarId);
var newCarIds = new HashSet<int>(cars);
foreach (var car in existingCars)
{
//If there are no new cars then it had some and they have been removed
if (newCars.Count() == 0)
{
missingCars.Add(car);
}
else
{
if (!newCarIds.Contains(car.CarId))
{
missingCars.Add(car);
}
}
}
return missingCars;
}
This works as I want - but if I want to achieve the same functionality for Customers or Apartments of other DTOs I will be copying a pasting the code but only changing the variable names and the Type of DTO around - is there a nicer way possible using generics which would keep the algorithm and logic as it is but allow me to use on any DTO?
If all the ids are of type int then you can do that by passing in a Func to determine the id.
private List<T> BuildToDelete<T>(
IList<T> newItems,
IList<T> existingItems,
Func<T, int> getId)
{
var missingItems = new List<T>();
var items = newItems.Select(getId);
var newItemIds = new HashSet<int>(items);
foreach (var item in existingItems)
{
if (newItems.Count() == 0)
{
missingItems.Add(item);
}
else
{
if (!newItemIds.Contains(getId(item)))
{
missingItems.Add(item);
}
}
}
return missingItems;
}
Then call as shown below:
var results = BuildToDelete(newCars, existingCars, c => c.CarId);
Assuming you use the interface approach mentioned in comments, a generic version could look something like this:
private List<TEntity> BuildEntitiesToDelete(IList<TEntity> newEntities, IList<TEntity> existingEntities) where TEntity : IEntityWithId
{
var missingEntities = new List<TEntity>();
var entities = newEntities.Select(e => e.Id);
var newEntityIds = new HashSet<int>(entities);
foreach (var entity in existingEntities)
{
if (entities.Count() == 0)
{
missingEntities.Add(entity);
}
else
{
if (!newEntityIds.Contains(entity.Id))
{
missingEntities.Add(entity);
}
}
}
return missingEntities;
}
IEntityWithId is probably a poor name for the interface, but I'll leave picking a better name up to you.
Try something cleaner:
1) create flexible equality comparer (need to add some null checking etc.)
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
Func<T, T, bool> comparer;
Func<T, int> hash;
public FuncEqualityComparer (Func<T, T, bool> comparer, Func<T, int> hash)
{
this.comparer = comparer;
this.hash = hash;
}
public bool Equals (T x, T y) => comparer (x, y);
public int GetHashCode (T obj) => hash (obj);
}
2) and now, just simply:
var carComparerByID = new FuncEqualityComparer<CarDTO> ((a, b) => a.CarId == b.CarId, x => x.CarId.GetHashCode ());
var result = existingCars.Except (newCars, carComparerByID).ToList ();
I've bending my mind for a while, but I think I'm missing something, so may be someone will help.
Let's say I have following mapper class:
public class Mapping<TSource, TResult>
{
private readonly Action<TSource, TResult> setter;
public Mapping(Expression<Func<TSource, TResult>> expression)
{
var newValue = Expression.Parameter(expression.Body.Type);
var body = Expression.Assign(expression.Body, newValue);
var assign = Expression.Lambda<Action<TSource, TResult>>(body, expression.Parameters[0], newValue);
setter = assign.Compile();
}
public void Assign(TSource instance, TResult value)
{
setter(instance, value);
}
}
And it is working fine:
[Test]
public void ShouldMapProperty()
{
var testClass = new TestClass();
var nameMapping = new Mapping<TestClass, string>(x => x.Name);
var ageMapping = new Mapping<TestClass, int>(x => x.Age);
nameMapping.Assign(testClass, "name");
ageMapping.Assign(testClass, 10);
Assert.AreEqual("name", testClass.Name);
Assert.AreEqual(10, testClass.Age);
}
Thing is, that I would like to keep mappings for single object type into some collection and TResult is getting in the way, as long as different properties have different types.
How to get rid of TResult nicely?
Update:
looks like I wasn't clear enough, so this would be sample how would I use it:
public class Mapping<TSource, TResult>
{
private readonly Action<TSource, TResult> setter;
private readonly string columnName;
public Mapping(Expression<Func<TSource, TResult>> expression, string columnName)
{
this.columnName = columnName;
var newValue = Expression.Parameter(expression.Body.Type);
var body = Expression.Assign(expression.Body, newValue);
var assign = Expression.Lambda<Action<TSource, TResult>>(body, expression.Parameters[0], newValue);
setter = assign.Compile();
}
public void Assign(TSource instance, DataRow row)
{
setter(instance, row[columnName]);
}
}
And then I would have some MappingConfiguration class, that would let me do this:
MappingConfiguration.For<TestClass>()
.Map(x => x.Name, "FirstName")
.Map(x => x.Age, "Age");
And finaly some MappingEngine class, that would take DataTable and MappingConfiguration as input and produce IEnumerable<TestClass> as output.
Update 2:
I've modified initial version to this:
public class Mapping2<TSource>
{
private readonly Delegate setter;
public Mapping2(Expression<Func<TSource, object>> expression)
{
var newValue = Expression.Parameter(expression.Body.Type);
var body = Expression.Assign(expression.Body, newValue);
var assign = Expression.Lambda(body, expression.Parameters[0], newValue);
setter = assign.Compile();
}
public void Assign(TSource instance, object value)
{
setter.DynamicInvoke(instance, value);
}
}
And it almost works.
By almost I mean it works with reference type properties, and with value type properties I get:
System.ArgumentException : Expression must be writeable
Parameter name: left
I've managed to do it, source code below. It runs somewhat faster than Automapper (not sure if my Automapper configuration is the fastest for this task), benchmark is not bulletproof, but on my machine to map 5 million rows took 20.16 seconds using my written mapper and 39.90 using Automapper, although it seems that Automapper uses less memory for this task (haven't measured it, but with 10 million rows Automapper gives result and my mapper fails with OutOfMemory).
public class MappingParameter<TSource>
{
private readonly Delegate setter;
private MappingParameter(Delegate compiledSetter)
{
setter = compiledSetter;
}
public static MappingParameter<TSource> Create<TResult>(Expression<Func<TSource, TResult>> expression)
{
var newValue = Expression.Parameter(expression.Body.Type);
var body = Expression.Assign(expression.Body, newValue);
var assign = Expression.Lambda(body, expression.Parameters[0], newValue);
var compiledSetter = assign.Compile();
return new MappingParameter<TSource>(compiledSetter);
}
public void Assign(TSource instance, object value)
{
object convertedValue;
if (!setter.Method.ReturnType.IsAssignableFrom(typeof(string)))
{
convertedValue = Convert.ChangeType(value, setter.Method.ReturnType);
}
else
{
convertedValue = value;
}
setter.DynamicInvoke(instance, convertedValue);
}
}
public class DataRowMappingConfiguration<TSource>
{
private readonly Dictionary<string, MappingParameter<TSource>> mappings =
new Dictionary<string, MappingParameter<TSource>>();
public DataRowMappingConfiguration<TSource> Add<TResult>(string columnName,
Expression<Func<TSource, TResult>> expression)
{
mappings.Add(columnName, MappingParameter<TSource>.Create(expression));
return this;
}
public Dictionary<string, MappingParameter<TSource>> Mappings
{
get
{
return mappings;
}
}
}
public class DataRowMapper<TSource>
{
private readonly DataRowMappingConfiguration<TSource> configuration;
public DataRowMapper(DataRowMappingConfiguration<TSource> configuration)
{
this.configuration = configuration;
}
public IEnumerable<TSource> Map(DataTable table)
{
var list = new List<TSource>(table.Rows.Count);
foreach (DataRow dataRow in table.Rows)
{
var obj = (TSource)Activator.CreateInstance(typeof(TSource));
foreach (var mapping in configuration.Mappings)
{
mapping.Value.Assign(obj, dataRow[mapping.Key]);
}
list.Add(obj);
}
return list;
}
}
public class TestClass
{
public string Name { get; set; }
public int Age { get; set; }
}
[TestFixture]
public class DataRowMappingTests
{
[Test]
public void ShouldMapPropertiesUsingOwnMapper()
{
var mappingConfiguration = new DataRowMappingConfiguration<TestClass>()
.Add("firstName", x => x.Name)
.Add("age", x => x.Age);
var mapper = new DataRowMapper<TestClass>(mappingConfiguration);
var dataTable = new DataTable();
dataTable.Columns.Add("firstName");
dataTable.Columns.Add("age");
for (int i = 0; i < 5000000; i++)
{
var row = dataTable.NewRow();
row["firstName"] = "John";
row["age"] = 15;
dataTable.Rows.Add(row);
}
var start = DateTime.Now;
var result = mapper.Map(dataTable).ToList();
Console.WriteLine((DateTime.Now - start).TotalSeconds);
Assert.AreEqual("John", result.First().Name);
Assert.AreEqual(15, result.First().Age);
}
[Test]
public void ShouldMapPropertyUsingAutoMapper()
{
Mapper.CreateMap<DataRow, TestClass>()
.ForMember(x => x.Name, x => x.MapFrom(y => y["firstName"]))
.ForMember(x => x.Age, x => x.MapFrom(y => y["age"]));
var dataTable = new DataTable();
dataTable.Columns.Add("firstName");
dataTable.Columns.Add("age");
for (int i = 0; i < 5000000; i++)
{
var row = dataTable.NewRow();
row["firstName"] = "John";
row["age"] = 15;
dataTable.Rows.Add(row);
}
var start = DateTime.Now;
var result = dataTable.Rows.OfType<DataRow>().Select(Mapper.Map<DataRow, TestClass>).ToList();
Console.WriteLine((DateTime.Now - start).TotalSeconds);
Assert.AreEqual("John", result.First().Name);
Assert.AreEqual(15, result.First().Age);
}
}
Something like thid maybe :
public class Mapping<TSource>
{
public void Assign<TResult>(TSource instance, TResult value)
{
var property = typeof(TSource).GetProperties().FirstOrDefault(p => p.PropertyType == typeof(TResult)));
if (property != null)
{
property.SetValue(instance, value, new object[0]);
}
}
}
But your object need to have ONE property of each type for this to be accurate
We could even make it more generic, but more dangerous :
public void Assign<TResult>(TSource instance, TResult value)
{
var property = typeof(TSource).GetProperties().FirstOrDefault(p => p.PropertyType.IsAssignableFrom(typeof(TResult)));
if (property != null)
{
property.SetValue(instance, value, new object[0]);
}
}
(this won't work if you have 2 properties inheriting from the same base class)...
I am trying to create a quick class so that I can make writing sorting code for a grid much easier to work with and maintain, and to keep code repetition down. To do this I came up with the following class:
public class SortConfig<TSource, TRelatedObject> where TSource : class where TRelatedObject : class
{
public IList<SortOption> Options { get; protected set; }
public SortOption DefaultOption { get; set; }
public SortConfig()
{
Options = new List<SortOption>();
}
public void Add(string name, Expression<Func<TSource, object>> sortExpression, TRelatedObject relatedObject, bool isDefault = false)
{
var option = new SortOption
{
FriendlyName = name,
SortExpression = sortExpression,
RelatedObject = relatedObject
};
Options.Add(option);
if (isDefault)
DefaultOption = option;
}
public SortOption GetSortOption(string sortName)
{
if (sortName.EndsWith("asc", StringComparison.OrdinalIgnoreCase))
sortName = sortName.Substring(0, sortName.LastIndexOf("asc", StringComparison.OrdinalIgnoreCase));
else if (sortName.EndsWith("desc", StringComparison.OrdinalIgnoreCase))
sortName = sortName.Substring(0, sortName.LastIndexOf("desc", StringComparison.OrdinalIgnoreCase));
sortName = sortName.Trim();
var option = Options.Where(x => x.FriendlyName.Trim().Equals(sortName, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
if (option == null)
{
if (DefaultOption == null)
throw new InvalidOperationException(
string.Format("No configuration found for sort type of '{0}', and no default sort configuration exists", sortName));
option = DefaultOption;
}
return option;
}
public class SortOption
{
public string FriendlyName { get; set; }
public Expression<Func<TSource, object>> SortExpression { get; set; }
public TRelatedObject RelatedObject { get; set; }
}
}
The idea is that you create a quick configuration of the different sort options, what OrderBy expression is used for that, and optionally an object that is related to that sort option. This allows my code to look like:
protected void InitSortConfig()
{
_sortConfig = new SortConfig<xosPodOptimizedSearch, HtmlAnchor>();
_sortConfig.Add("name", (x => x.LastName), lnkSortName, true);
_sortConfig.Add("team", (x => x.SchoolName), lnkSortTeam);
_sortConfig.Add("rate", (x => x.XosRating), lnkSortRate);
_sortConfig.Add("pos", (x => x.ProjectedPositions), null);
_sortConfig.Add("height", (x => x.Height), lnkSortHeight);
_sortConfig.Add("weight", (x => x.Weight), lnkSortWeight);
_sortConfig.Add("city", (x => x.SchoolCity), lnkSortCity);
_sortConfig.Add("state", (x => x.SchoolState), lnkSortState);
}
and then I can sort by just doing
// Get desired sorting configuration
InitSortConfig();
var sortOption = _sortConfig.GetSortOption(sort);
bool isDescendingSort = sort.EndsWith("desc", StringComparison.OrdinalIgnoreCase);
// Setup columns
InitSortLinks();
if (sortOption.RelatedObject != null)
{
// Make modifications to html anchor
}
// Form query
var query = PodDataContext.xosPodOptimizedSearches.AsQueryable();
if (isDescendingSort)
query = query.OrderByDescending(sortOption.SortExpression);
else
query = query.OrderBy(sortOption.SortExpression);
This works great when the sorted variable is a string, but when it is not a string I get the following exception: Cannot order by type 'System.Object'.
I'm assuming this is because I am storing the expression as Expression<Func<TSource, object>> and not being more specific about that 2nd generic. I don't understand how I can keep all my different sort options (even for non-string properties) in one class.
I guess one of the issues is that the Linq.OrderBy() clause takes Expression<Func<TSource, TKey>> as it's parameter, but I am not wrapping my head around how Linq.OrderBy() is able to infer what TKey should be, and therefore I cannot understand how to take advantage of that inference to store these expressions with the proper TKey.
Any ideas?
The generic argument is inferred like so:
IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> enumerable, Func<TSource, TKey> expression)
When you have an IEnumerable<T>, the compiler is able to infer that the TSource is T in this situation because of the extension method declaration; so the extension method is already added on knowing what TSource is. For example:
Enumerable.Range(0, 10).OrderBy(x => x)
Since we start with an IEnumerable<int>, the compiler can infer that the expression it expects is Func<int, TKey>, because the extension is affecting the IEnumerable<int>. Next, because your expression returns a value, the compiler can infer the remaining type, in this situation int, so it becomes a Func<int, int> with this example.
Now, germane to your particular problem, you could easily set up your expression to work appropriately if you genericize your SortConfig object appropriately. It looks like your SortConfig takes a Func<TSource, object> delegate right now. If you genericize your SortConfig to use another type, you gain specificity. Example:
void Add<TSource, TKey>(string name, Func<TSource, TKey> expression)
The next problem here is how to store your backing methods in some format. And your class declaration looks like so:
public class SortConfig<TSource>
Then all the data types should line up when you invoke the OrderBy extension.
EDIT: Here's a working example of what I think you want to do:
static void Main(string[] args)
{
var list = Enumerable.Range(0, 10).Reverse().Select(x => new SampleClass { IntProperty = x, StringProperty = x + "String", DateTimeProperty = DateTime.Now.AddDays(x * -1) });
SortContainer<SampleClass> container = new SortContainer<SampleClass>();
container.Add("Int", x => x.IntProperty);
container.Add("String", x => x.StringProperty);
container.Add("DateTime", x => x.DateTimeProperty);
var sorter = container.GetSorterFor("Int");
sorter.Sort(list).ForEach(x => Console.WriteLine(x.IntProperty));
Console.ReadKey();
}
public class SampleClass
{
public int IntProperty { get; set; }
public string StringProperty { get; set; }
public DateTime DateTimeProperty { get; set; }
}
public class SortContainer<TSource>
{
protected Dictionary<string, ISorter<TSource>> _sortTypes = new Dictionary<string, ISorter<TSource>>();
public void Add<TKey>(string name, Func<TSource, TKey> sortExpression)
{
Sorter<TSource, TKey> sorter = new Sorter<TSource, TKey>(sortExpression);
_sortTypes.Add(name, sorter);
}
public ISorter<TSource> GetSorterFor(string name)
{
return _sortTypes[name];
}
}
public class Sorter<TSource, TKey> : ISorter<TSource>
{
protected Func<TSource, TKey> _sortExpression = null;
public Sorter(Func<TSource, TKey> sortExpression)
{
_sortExpression = sortExpression;
}
public IOrderedEnumerable<TSource> Sort(IEnumerable<TSource> sourceEnumerable)
{
return sourceEnumerable.OrderBy(_sortExpression);
}
}
public interface ISorter<TSource>
{
IOrderedEnumerable<TSource> Sort(IEnumerable<TSource> sourceEnumerable);
}
Is this the fastest way to update a property using reflection? Assume the property is always an int:
PropertyInfo counterPropertyInfo = GetProperty();
int value = (int)counterPropertyInfo.GetValue(this, null);
counterPropertyInfo.SetValue(this, value + 1, null);
I did some benchmarking here when you know the type arguments (a non generic approach wont be very different). CreateDelegate would be the fastest approach for a property if you can't directly access it. With CreateDelegate you get a direct handle to GetGetMethod and GetSetMethod of the PropertyInfo, hence reflection is not used every time.
public static Func<S, T> BuildGetAccessor<S, T>(Expression<Func<S, T>> propertySelector)
{
return propertySelector.GetPropertyInfo().GetGetMethod().CreateDelegate<Func<S, T>>();
}
public static Action<S, T> BuildSetAccessor<S, T>(Expression<Func<S, T>> propertySelector)
{
return propertySelector.GetPropertyInfo().GetSetMethod().CreateDelegate<Action<S, T>>();
}
// a generic extension for CreateDelegate
public static T CreateDelegate<T>(this MethodInfo method) where T : class
{
return Delegate.CreateDelegate(typeof(T), method) as T;
}
public static PropertyInfo GetPropertyInfo<S, T>(this Expression<Func<S, T>> propertySelector)
{
var body = propertySelector.Body as MemberExpression;
if (body == null)
throw new MissingMemberException("something went wrong");
return body.Member as PropertyInfo;
}
So now you call:
TestClass cwp = new TestClass();
var access = BuildGetAccessor((TestClass t) => t.AnyValue);
var result = access(cwp);
Or even better you can encapsulate the logic in a dedicated class to have a get and set methods on it.
Something like:
public class Accessor<S>
{
public static Accessor<S, T> Create<T>(Expression<Func<S, T>> memberSelector)
{
return new GetterSetter<T>(memberSelector);
}
public Accessor<S, T> Get<T>(Expression<Func<S, T>> memberSelector)
{
return Create(memberSelector);
}
public Accessor()
{
}
class GetterSetter<T> : Accessor<S, T>
{
public GetterSetter(Expression<Func<S, T>> memberSelector) : base(memberSelector)
{
}
}
}
public class Accessor<S, T> : Accessor<S>
{
Func<S, T> Getter;
Action<S, T> Setter;
public bool IsReadable { get; private set; }
public bool IsWritable { get; private set; }
public T this[S instance]
{
get
{
if (!IsReadable)
throw new ArgumentException("Property get method not found.");
return Getter(instance);
}
set
{
if (!IsWritable)
throw new ArgumentException("Property set method not found.");
Setter(instance, value);
}
}
protected Accessor(Expression<Func<S, T>> memberSelector) //access not given to outside world
{
var prop = memberSelector.GetPropertyInfo();
IsReadable = prop.CanRead;
IsWritable = prop.CanWrite;
AssignDelegate(IsReadable, ref Getter, prop.GetGetMethod());
AssignDelegate(IsWritable, ref Setter, prop.GetSetMethod());
}
void AssignDelegate<K>(bool assignable, ref K assignee, MethodInfo assignor) where K : class
{
if (assignable)
assignee = assignor.CreateDelegate<K>();
}
}
Short and simple. You can carry around an instance of this class for every "class-property" pair you wish to get/set.
Usage:
Person p = new Person { Age = 23 };
var ageAccessor = Accessor<Person>(x => x.Age);
int age = ageAccessor[p]; //gets 23
ageAccessor[p] = 45; //sets 45
Bit bad use of indexers here, you may replace it with dedicated "Get" and "Set" methods, but very intuitive to me :)
To avoid having to specify type each time like,
var ageAccessor = Accessor<Person>(x => x.Age);
var nameAccessor = Accessor<Person>(x => x.Name);
var placeAccessor = Accessor<Person>(x => x.Place);
I made the base Accessor<> class instantiable, which means you can do
var personAccessor = new Accessor<Person>();
var ageAccessor = personAccessor.Get(x => x.Age);
var nameAccessor = personAccessor.Get(x => x.Name);
var placeAccessor = personAccessor.Get(x => x.Place);
Having a base Accessor<> class means you can treat them as one type, for eg,
var personAccessor = new Accessor<Person>();
var personAccessorArray = new Accessor<Person>[]
{
personAccessor.Get(x => x.Age),
personAccessor.Get(x => x.Name),
personAccessor.Get(x => x.Place);
};
You should look at FastMember (nuget, source code], it's really fast comparing to reflection.
I've tested these 3 implementations:
PropertyInfo.SetValue
PropertyInfo.SetMethod
FastMember
The benchmark needs a benchmark function:
static long Benchmark(Action action, int iterationCount, bool print = true)
{
GC.Collect();
var sw = new Stopwatch();
action(); // Execute once before
sw.Start();
for (var i = 0; i <= iterationCount; i++)
{
action();
}
sw.Stop();
if (print) System.Console.WriteLine("Elapsed: {0}ms", sw.ElapsedMilliseconds);
return sw.ElapsedMilliseconds;
}
A fake class:
public class ClassA
{
public string PropertyA { get; set; }
}
Some test methods:
private static void Set(string propertyName, string value)
{
var obj = new ClassA();
obj.PropertyA = value;
}
private static void FastMember(string propertyName, string value)
{
var obj = new ClassA();
var type = obj.GetType();
var accessors = TypeAccessor.Create(type);
accessors[obj, "PropertyA"] = "PropertyValue";
}
private static void SetValue(string propertyName, string value)
{
var obj = new ClassA();
var propertyInfo = obj.GetType().GetProperty(propertyName);
propertyInfo.SetValue(obj, value);
}
private static void SetMethodInvoke(string propertyName, string value)
{
var obj = new ClassA();
var propertyInfo = obj.GetType().GetProperty(propertyName);
propertyInfo.SetMethod.Invoke(obj, new object[] { value });
}
The script itself:
var iterationCount = 100000;
var propertyName = "PropertyA";
var value = "PropertyValue";
Benchmark(() => Set(propertyName, value), iterationCount);
Benchmark(() => FastMember(propertyName, value), iterationCount);
Benchmark(() => SetValue(propertyName, value), iterationCount);
Benchmark(() => SetMethodInvoke(propertyName, value), iterationCount);
Results for 100 000 iterations:
Default setter : 3ms
FastMember: 36ms
PropertyInfo.SetValue: 109ms
PropertyInfo.SetMethod: 91ms
Now you can choose yours !!!
Just be sure that you are caching the PropertyInfo somehow, so that you aren't repeatably calling type.GetProperty. Other than that it would probably be faster if you created a delegate to a method on the type that performed the increment, or like Teoman suggested make the type implement an interface and use that.