Related
I hope somebody can guide and help me with this. We have an inherited project that uses ExpressionHelper class. Basically, this Expression Helper will return an IQueryable that build a dynamic query base on the search term that the user provided.
For example, I have the below code where I pass 2 search terms.
IQueryable<UserEntity> modifiedQuery = _uow.UserRepository.GetAll();;
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "John" }
};
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var obj = ExpressionHelper.Parameter<TEntity>();
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider
.GetComparison(left, searchTerm.Operator, right);
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper
.GetLambda<TEntity, bool>(obj, comparisonExpression);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
With the code above and using the below ExpressionHelper class, this generate the below SQL query when I check using SQLProfiler. Please notice the AND in the query. What I actually what is OR.
Constructed QUERY in SQL Profiler
SELECT
[Extent1].[FirstName] AS [FirstName],
FROM [dbo].[tblUser] AS [Extent1]
WHERE ([Extent1].[Conatact1] = N'Bob') AND ([Extent1].[Contact2] = N'John')
ExpressionHelper.cs
public static class ExpressionHelper
{
private static readonly MethodInfo LambdaMethod = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);
private static MethodInfo[] QueryableMethods = typeof(Queryable)
.GetMethods()
.ToArray();
private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
{
var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
return LambdaMethod.MakeGenericMethod(predicateType);
}
public static PropertyInfo GetPropertyInfo<T>(string name)
=> typeof(T).GetProperties()
.Single(p => p.Name == name);
public static ParameterExpression Parameter<T>()
=> Expression.Parameter(typeof(T));
public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
=> Expression.Property(obj, property);
public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
=> GetLambda(typeof(TSource), typeof(TDest), obj, arg);
public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
{
var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
}
public static IQueryable<T> CallWhere<T>(IQueryable<T> query, LambdaExpression predicate)
{
var whereMethodBuilder = QueryableMethods
.First(x => x.Name == "Where" && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(T) });
return (IQueryable<T>)whereMethodBuilder
.Invoke(null, new object[] { query, predicate });
}
public static IQueryable<TEntity> CallOrderByOrThenBy<TEntity>(
IQueryable<TEntity> modifiedQuery,
bool useThenBy,
bool descending,
Type propertyType,
LambdaExpression keySelector)
{
var methodName = "OrderBy";
if (useThenBy) methodName = "ThenBy";
if (descending) methodName += "Descending";
var method = QueryableMethods
.First(x => x.Name == methodName && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(TEntity), propertyType });
return (IQueryable<TEntity>)method.Invoke(null, new object[] { modifiedQuery, keySelector });
}
}
I have hard time understanding on how the query was created and how do I change it to become OR in the created query.
Hope someone can guide me and point to the right direction. Thank you!
Add to SearchTerm a new property (C# 6.0 syntax here):
// This is quite wrong. We should have an enum here, but Operator is
// done as a string, so I'm maintaining the "style"
// Supported LogicalConnector: and, or
public string LogicalConnector { get; set; } = "and";
}
Then:
private static IQueryable<TEntity> BuildQuery<TEntity>(IQueryable<TEntity> modifiedQuery, List<SearchTerm> searchTerms)
{
Expression comparisonExpressions = null;
var obj = ExpressionHelper.Parameter<TEntity>();
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider.GetComparison(left, searchTerm.Operator, right);
if (comparisonExpressions == null)
{
comparisonExpressions = comparisonExpression;
}
else if (searchTerm.LogicalConnector == "and")
{
comparisonExpressions = Expression.AndAlso(comparisonExpressions, comparisonExpression);
}
else if (searchTerm.LogicalConnector == "or")
{
comparisonExpressions = Expression.OrElse(comparisonExpressions, comparisonExpression);
}
else
{
throw new NotSupportedException(searchTerm.LogicalConnector);
}
}
if (comparisonExpressions != null)
{
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper.GetLambda<TEntity, bool>(obj, comparisonExpressions);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
return modifiedQuery;
}
Use it like:
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "SecondaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "John", LogicalConnector = "or", }
};
IQueryable<UserEntity> query = BuildQuery<UserEntity>(modifiedQuery, searchTerms);
Note that there is no way in this code to explicitly set brackets, that will be implicitly set as:
(((A opB b) opC C) opD D)
Where A, B, C, D are the SearchTerm[0], SearchTerm[1], SearchTerm[2], SearchTerm[3] and opB, opC, opD are the operators defined in SearchTerm[1].LogicalConnector, SearchTerm[2].LogicalConnector, SearchTerm[3].LogicalConnector.
While putting brackets is easy, choosing how to "describe" them is complex, unless you change significantly your SearchTerm collection (it couldn't be a "linear" array but it would need to be a tree).
P.S. I was wrong, you don't need an ExpressionVisitor. You need an ExpressionVisitor when you are trying to "merge" multiple LambdaExpressions that have distinct ParameterExpression. In this code we are able to have a single var obj = ExpressionHelper.Parameter<TEntity>() for all the query, so no problems merging the conditions. To make it clear: if you want to "merge" x1 => x1.Foo == "Foo1" with x2 => x2.Foo == "Foo2" then you need an ExpressionVisitor that replaces x2 with x1, otherwise you would get a wrong query like x1 => x1.Foo == "Foo1" || x2.Foo == "Foo2". In the code given we have only x1 (that is var obj = ExpressionHelper.Parameter<TEntity>()), so no problem.
I'm trying to build the following lambda expression using the expression tree ->
info => info.event_objects.Select(x => x.object_info.contact_info)
I researched a lot and find some answers on the StackOverflow.
This one helped me to build the
info =>
info.event_objects.Any(x => x.object_info.contact_info.someBool == true)
As you can see, the method 'Any' is easy to get.
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any"
&& m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
The main problem is with the method 'Select'. If you will try to change the Name "Any" to "Select", you will get the following exception:
var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name ==
"Select" && m.GetParameters().Length == 2);
selectMethod = selectMethod.MakeGenericMethod(childType);
Additional information: Sequence contains more than one matching element
Another way I've tried:
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name
== "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p =>
p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
It seems work, but then I get the exception here:
navigationPropertyPredicate = Expression.Call(selectMethod, parameter,
navigationPropertyPredicate);
Additional information: Method
System.Collections.Generic.IEnumerable`1[TResult] Select[TSource,TResult]
(System.Collections.Generic.IEnumerable`1[TSource],
System.Func`2[TSource,TResult]) is a generic method definition>
After that, I've tried to use:
selectMethod = selectMethod.MakeGenericMethod(typeof(event_objects),
typeof(contact_info));
In fact, it doesn't help.
Here is my full code
public static Expression GetNavigationPropertyExpression(Expression parameter, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, "x");
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, innerProperties);
if (isCollection)
{
//var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Select" && m.GetParameters().Length == 2);
//selectMethod = selectMethod.MakeGenericMethod(childType);
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
navigationPropertyPredicate = Expression.Call(selectMethod, parameter, navigationPropertyPredicate);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(true, typeof(bool));
navigationPropertyPredicate = Expression.Lambda(left);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
[TestMethod]
public void TestDynamicExpression()
{
var parameter = Expression.Parameter(typeof(event_info), "x");
var expression = GetNavigationPropertyExpression(parameter, "event_objects", "object_info", "contact_info");
}
Edit: unfortunately, I've tried answers from this question, but it doesn't seem work
You can avoid finding the correct generic method overload via reflection (which is complicated and error prone as you already noticed) by using one of the two Expression.Call method overloads (one for static and one for instance methods) accepting string methodName and Type[] typeArguments.
Also the current implementation is overcomplicated and contains other problems, due to the lack of clear separation of expression and lambda expression building.
Here is a correct working implementation:
public static LambdaExpression GetNavigationPropertySelector(Type type, params string[] properties)
{
return GetNavigationPropertySelector(type, properties, 0);
}
private static LambdaExpression GetNavigationPropertySelector(Type type, string[] properties, int depth)
{
var parameter = Expression.Parameter(type, depth == 0 ? "x" : "x" + depth);
var body = GetNavigationPropertyExpression(parameter, properties, depth);
return Expression.Lambda(body, parameter);
}
private static Expression GetNavigationPropertyExpression(Expression source, string[] properties, int depth)
{
if (depth >= properties.Length)
return source;
var property = Expression.Property(source, properties[depth]);
if (typeof(IEnumerable).IsAssignableFrom(property.Type))
{
var elementType = property.Type.GetGenericArguments()[0];
var elementSelector = GetNavigationPropertySelector(elementType, properties, depth + 1);
return Expression.Call(
typeof(Enumerable), "Select", new Type[] { elementType, elementSelector.Body.Type },
property, elementSelector);
}
else
{
return GetNavigationPropertyExpression(property, properties, depth + 1);
}
}
The first is the public method. It internally uses the next two private methods to recursively build the desired lambda. As you can see, I distinguish between building lambda expression and just expression to be used as lambda body.
Test:
var selector = GetNavigationPropertySelector(typeof(event_info),
"event_objects", "object_info", "contact_info");
Result:
x => x.event_objects.Select(x1 => x1.object_info.contact_info)
"Additional information: Sequence contains more than one matching element"
Unlike "Any()", for "Select()" there are two overloads with two parameters:
Select<TS, TR>(IE<TS> source, Func<TS, TR> selector)
Select<TS, TR>(IE<TS> source, Func<TS, int, TR> selector)
(takes the "(item, index) => " selector lambda)
Since your code already relies on "esoteric knowledge" anyway, just take the first one of them:
var selectMethod = typeof(Enumerable).GetMethods()
.First(m => m.Name == nameof(Enumerable.Select)
&& m.GetParameters().Length == 2);
It's the first time I write here on stackoverflow.
I would like to use PredicateBuilder in a LINQ query with SelectMany.
I enter my code
public async Task<List<Articolo>> OttieniElencoArticoliFiltratoComplessoAsync
(ExpressionStarter<Articolo> predicateArticolo,
ExpressionStarter<ArticoloFornitore> predicateFornitore,
ExpressionStarter<Categoria> predicateCategoria,
ExpressionStarter<Magazzino> predicateMagazzino,
ExpressionStarter<PrezzoVendita> predicatePrezzoVendita)
{
using (var database = DatabaseContext.NuovoDatabase())
{
var query = database.Articoli.AsExpandable();
if (predicateArticolo != null)
{
query = query.Where(predicateArticolo);
}
if (predicateFornitore != null)
{
query = query.AsExpandable().SelectMany(a => a.ElencoArticoliFornitore.Where(predicateFornitore)).Select(fornitore => fornitore.Articolo);
}
if (predicateMagazzino != null)
{
query = query.AsExpandable()
.SelectMany(articolo => articolo.ElencoMagazzino.Where(predicateMagazzino))
.Select(magazzino => magazzino.Articolo);
}
if (predicatePrezzoVendita != null)
{
query = query.AsExpandable().SelectMany(articolo =>
articolo.ElencoPrezzoVendita.Where(predicatePrezzoVendita).Select(vendita => vendita.Articolo));
}
if (predicateCategoria != null)
{
query = query.AsExpandable()
.SelectMany(articolo => articolo.ElencoCategorie.Where(predicateCategoria))
.Select(categoria => categoria.Articolo);
}
return await query.ToListAsync();
}
}
I create the predicate like this
private ExpressionStarter<ArticoloFornitore> PredicateFornitore()
{
var ritornaNull = true;
var predicate = PredicateBuilder.New<ArticoloFornitore>();
if (IsEnabledFiltroFornitore && FornitoreSelezionato != null)
{
ritornaNull = false;
predicate = predicate.And(fornitore => fornitore.IdFornitore == FornitoreSelezionato.IdFornitore);
}
return ritornaNull ? null : predicate;
}
private ExpressionStarter<Categoria> PredicateCategoria()
{
var ritornaNull = true;
var predicate = PredicateBuilder.New<Categoria>();
if (IsEnabledCategoriaLivello1 && CategoriaLivello1Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv1 == CategoriaLivello1Selezionata.IdCategoriaLv1);
}
if (IsEnabledCategoriaLivello2 && CategoriaLivello2Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv2 == CategoriaLivello2Selezionata.IdCategoriaLv2);
}
if (IsEnabledCategoriaLivello3 && CategoriaLivello3Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv3 == CategoriaLivello3Selezionata.IdCategoriaLv3);
}
if (IsEnabledCategoriaLivello4 && CategoriaLivello4Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv4 == CategoriaLivello4Selezionata.IdCategoriaLv4);
}
if (IsEnabledCategoriaLivello5 && CategoriaLivello5Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv5 == CategoriaLivello5Selezionata.IdCategoriaLv5);
}
return ritornaNull ? null : predicate;
}
I tried this method on LINQPAD with fixed data instead of the PredicateBuilder and the query works.
But with the PredicateBuilder I get the .NET Framework Data Provider error 1025.
How can I solve? I would like to be able to create a dynamic query that takes the parameters from the interface and returns the results.
I hope you can help me.
EntityFramework requires your predicates to be Expression<Func<T, bool>> rather than Func<T, bool>. This is what IQueryable.ToExpandable() does. However, your Entity objects such as articolo.ElencoPrezzoVendita are virtual ICollection objects, so the predicate is reduced to Func<T, bool> which is incompatible with EF.
It doesn't exactly roll off the tongue but you could try something like this (I didn't know the structure of your objects) with database to ensure the Expression is used.
if (predicatePrezzoVendita != null)
{
query = query.AsExpandable()
.SelectMany(articolo =>
database.ElencoPrezzoVendita
.Where(x => articolo.ForeignKeyID == x.ID)
.AsExpandable()
.Where(predicatePrezzoVendita)
.Select(vendita => vendita.Articolo));
}
For example I have some class with some property:
public class SomeClass
{
public Version Version { get; set; }
}
And I have a list of this type with sample data:
var list = new List<SomeClass>();
for (var i = 0; i < 1000; i++)
{
list.Add(new SomeClass
{
Version = new Version(i, i / 2, i / 3, i / 4),
});
}
I want to write method that filters by version using Version.Equals method:
var filterValue = new Version(12, 6, 4, 3);
var modelType = typeof(SomeClass);
var propertyType = typeof(Version);
var arg = Expression.Parameter(modelType, "x");
var property = Expression.Property(arg, "Version");
var value = Expression.Convert(Expression.Constant(filterValue), propertyType);
var versionEqualsMethod = typeof(Version).GetMethod("Equals", new[] { typeof(Version) });
/////////
Expression inst = null; // <-- ???
/////////
var expr = Expression.Call(inst, versionEqualsMethod, property, value);
var delegateType = typeof(Func<,>).MakeGenericType(modelType, typeof(bool));
var delegateValue = Expression.Lambda(delegateType, expr, arg).Compile();
var genericMethod =
typeof(Enumerable).GetMethods()
.First(
method =>
method.Name == "Where" && method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 1 && method.GetParameters().Length == 2)
.MakeGenericMethod(modelType);
var result = genericMethod.Invoke(null, new object[] { list, delegateValue });
What do I use as instance in Expression.Call?
UPDATE
Solution is:
var expr = Expression.Call(property, versionEqualsMethod, value);
You normally would do:
var filterValue = new Version(12, 6, 4, 3);
var modelType = typeof(SomeClass);
var propertyType = typeof(Version);
var arg = Expression.Parameter(modelType, "x");
var property = Expression.Property(arg, "Version");
// Changes from here onward
var value = Expression.Constant(filterValue);
var versionEqualsMethod = typeof(Version).GetMethod("Equals", new[] { typeof(Version) });
var expr = Expression.Call(property, versionEqualsMethod, value);
Because the Equals would be used like:
model.Version.Equals(filterValue);
I'm not handling the model.Version == null case!
Note that you don't need the Expression.Convert.
And what you are doing is ok if the "containing method" (the method where you'll put this code) is non-generic, but normally it would be a generic method, that has as the generic parameter the modelType, so the last part of the code would be different (starting from var delegateType =), because you could use the TModelType generic type directly.
Maybe I'm missing out on something, but wouldn't this work:
var results = list.Where(sc => sc.Version == filterVersion);
What you are trying to accomplish is much easier to do with reflection. Check this running online example. (If I understand you correctly that is... It would be helpful, if you could provide the signature of the function you are trying to write.)
Implementation
public static class Extensions
{
public static IEnumerable<T> Filter<T>(
this IEnumerable<T> enumerable, string propertyName, object filterValue)
{
var elementType = typeof (T);
var property = elementType.GetProperty(propertyName);
return enumerable.Where(element =>
{
var propertyValue = property.GetMethod.Invoke(element, new object[] {});
return propertyValue.Equals(filterValue);
});
}
}
Usage
var list = new List<SomeClass>();
for (var i = 0; i < 1000; i++)
{
list.Add(new SomeClass {Version = new Version(i, i/2, i/3, i/4)});
}
var filteredList = list.Filter("Version", new Version(12, 6, 4, 3));
Console.WriteLine(filteredList.Single().Version);
I wish to sort the collection List<UserClass>
on the basis of one of the property among various of that UserClass which is User Defined.
Is it valid
List<Function> objFunctionsList = new List<Function>();
// populating data in objFunctionsList
objFunctionsList = objFunctionsList.OrderBy(x => x.Name).ToList();
Just use the linq extension method orderby
var sorted=list.OrderBy(x=>x.MyProperty);
If you need a list as a result then add ToList() eg
var sortedList=list.OrderBy(x=>x.MyProperty).ToList();
Alternatively you could use this Extension class
public static class SortExtension {
public static Comparison<T> GetComparer<T, TP>(Expression<Func<T, IComparable<TP>>> propertyExpression) {
if (propertyExpression == null) throw new ArgumentNullException();
var member = ((propertyExpression.Body is UnaryExpression) ? ((UnaryExpression)propertyExpression.Body).Operand : propertyExpression.Body) as MemberExpression;
if (member == null) throw new ArgumentException();
var parameterA = Expression.Parameter(typeof(T), "a");
var parameterB = Expression.Parameter(typeof(T), "b");
var nullExpr = Expression.Constant(null);
var valueA = Expression.Property(parameterA, member.Member.Name);
var valueB = Expression.Property(parameterB, member.Member.Name);
var compare = Expression.Call(valueA, typeof(TP).GetMethod("CompareTo", new[] { typeof(TP) }), valueB);
var checkBPropNull = Expression.Condition(Expression.Equal(valueB, nullExpr), Expression.Constant(0), Expression.Constant(-1));
var checkAPropertyNull = Expression.Condition(Expression.Equal(valueA, nullExpr), checkBPropNull, compare);
var checkBNullANot = Expression.Condition(Expression.Equal(parameterB, nullExpr), Expression.Constant(1), checkAPropertyNull);
var checkBNullANull = Expression.Condition(Expression.Equal(parameterB, nullExpr), Expression.Constant(0), Expression.Constant(-1));
var checkANull = Expression.Condition(Expression.Equal(parameterA, nullExpr), checkBNullANull, checkBNullANot);
return (a, b) => Expression.Lambda<Func<T, T, int>>(checkANull, parameterA, parameterB).Compile()(a, b);
}
public static void SortBy<T, TP>(this List<T> source, Expression<Func<T, IComparable<TP>>> propertyExpression) {
if (source == null) throw new ArgumentNullException();
source.Sort(GetComparer(propertyExpression));
}
}
Then you can just do
list.SortBy(x=>x.MyProperty);
The Expression building produces a comparitor that is functionally equivalent to
list.Sort((a,b) => {
if (a == null) return (b==null) ? 0 :-1;
if (b==null) return 1;
if (a.MyProperty == null) return (b.MyProperty==null) ? 0 : -1;
return a.T1.CompareTo(b.T1);
});
users.Sort((u1, u2) => {
return u1.Age.CompareTo(u2.Age);
});
This will sort by Age for example.
If your list is list, then:
list.Sort((a, b) => {
if (a==null) return (b==null) ? 0 : -1;
if (b==null) return 1;
if (a.Property==null) return (b.Property==null) ? 0 : -1;
return a.Property.CompareTo(b.Property);
});