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);
}
Related
This question already has an answer here:
Generic Type in constructor
(1 answer)
Closed 6 years ago.
I've created a validator that checks an IList<T> for duplicates. I came up with the following implementation:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var bar = new Foo() {Bar = "buzz"};
var list = new List<Foo>() { }; //Foo has a property named Bar of type string
var validator = new Validator<Foo, string>(x => x.Bar); //I have to specify the type of Bar
validator.IsValid(list);
}
}
}
public class Foo
{
public string Bar { get; set; }
}
public class Validator<T, Tkey>
{
private readonly Func<T, Tkey> _keySelector;
public Validator(Func<T, Tkey> keySelector)
{
_keySelector = keySelector;
}
public bool IsValid(IList<T> list)
{
if (list != null && list.Count > 0)
{
if (list.GroupBy(_keySelector).Any(g => g.Count() > 1))
{
return false;
}
}
return true;
}
}
But I don't like how It has to be used: I have to specify the type of Bar during construction.
The question is Can I somehow skip TKey when initializing Validator? Can it somehow be inferred?
You can use a generic extension method, which requires a separate static class. Generic methods can derive the T-types from their parameters.
Here is how:
public static class Extensions
{
public static Validator<T, TKey> GetValidatorFor<T, TKey>(this List<T> list, Func<T, TKey> getter) where T : class
{
return new Validator<T, TKey>(getter);
}
}
and then you can use it like this:
var list = new List<Foo>();
var validator = list.GetValidatorFor(x => x.Bar);
You can use an extension (factory) method to create the validator. The compiler will know how to resolve the types:
public static class SomeStaticClass
{
public static Validator<T,TKey> CreateValidator<T,TKey>(this IEnumerable<T> list, Func<T,TKey> keyselector)
{
return new Validator<T,TKey>(keyselector);
}
}
Now a validator can be created with:
var list = new List<Foo>(); //Foo has a property named Bar of type string
var validator = list.CreateValidator(x => x.Bar); //the compiler can infer T (Foo) through the list and TKey from the returned property inside the lambda
You can use a temporary object to apply currying to the generic parameters:
static class Validator
{
public static ValidatorConstructor<T> Construct<T>() => new ValidatorConstructor<T>();
}
class ValidatorConstructor<T>
{
public Validator<T, TKey> WithSelector<TKey>(Func<T, TKey> selector) => new Validator<T, TKey>(selector);
}
class Validator<T, TKey>
{
public Validator(Func<T, TKey> selector)
{
}
}
Usage:
Validator<Foo, Bar> e = Validator.Construct<Foo>().WithSelector(x => x.Bar);
I have an ObservableCollection which I want to sort, not in place but I want to create a new sorted copy.
There's lots of examples on how to sort lists using nifty lambda expressions or using LINQ, but I can't hardcode the fields I want to sort by into code.
I have an array of NSSortDescription which work kinda like SortDescription. There's a string with the name of the property but the direction is specified by a bool (true = ascending). The first value in the array should be the primary sorting field, when the values in that field match, the second sort descriptor should be used, etc.
Example:
Artist: Bob Marley, Title: No Woman No Cry
Artist: Bob Marley, Title: Could You Be Loved
Artist: Infected Mushroom, Title: Converting Vegetarians
Artist: Bob Marley, Title: One Love
Artist: Chemical Brothers, Title: Do It Again
Sort descriptor: Artist descending, Title ascending.
Result:
Artist: Infected Mushroom, Title: Converting Vegetarians
Artist: Chemical Brothers, Title: Do It Again
Artist: Bob Marley, Title: Could You Be Loved
Artist: Bob Marley, Title: No Woman No Cry
Artist: Bob Marley, Title: One Love
Any suggestions on how to accomplish this?
UPDATE: Change Sort to OrderBy as Sort is unstable sort algorithm
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
namespace PNS
{
public class SortableList<T> : List<T>
{
private string _propertyName;
private bool _ascending;
public void Sort(string propertyName, bool ascending)
{
//Flip the properties if the parameters are the same
if (_propertyName == propertyName && _ascending == ascending)
{
_ascending = !ascending;
}
//Else, new properties are set with the new values
else
{
_propertyName = propertyName;
_ascending = ascending;
}
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
PropertyDescriptor propertyDesc = properties.Find(propertyName, true);
// Apply and set the sort, if items to sort
PropertyComparer<T> pc = new PropertyComparer<T>(propertyDesc, (_ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending);
//this.Sort(pc); UNSTABLE SORT ALGORITHM
this.OrderBy(t=>t, pc);
}
}
public class PropertyComparer<T> : System.Collections.Generic.IComparer<T>
{
// The following code contains code implemented by Rockford Lhotka:
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadvnet/html/vbnet01272004.asp
private PropertyDescriptor _property;
private ListSortDirection _direction;
public PropertyComparer(PropertyDescriptor property, ListSortDirection direction)
{
_property = property;
_direction = direction;
}
public int Compare(T xWord, T yWord)
{
// Get property values
object xValue = GetPropertyValue(xWord, _property.Name);
object yValue = GetPropertyValue(yWord, _property.Name);
// Determine sort order
if (_direction == ListSortDirection.Ascending)
{
return CompareAscending(xValue, yValue);
}
else
{
return CompareDescending(xValue, yValue);
}
}
public bool Equals(T xWord, T yWord)
{
return xWord.Equals(yWord);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
// Compare two property values of any type
private int CompareAscending(object xValue, object yValue)
{
int result;
if (xValue == null && yValue != null) return -1;
if (yValue == null && xValue != null) return 1;
if (xValue == null && yValue == null) return 0;
// If values implement IComparer
if (xValue is IComparable)
{
result = ((IComparable)xValue).CompareTo(yValue);
}
// If values don't implement IComparer but are equivalent
else if (xValue.Equals(yValue))
{
result = 0;
}
// Values don't implement IComparer and are not equivalent, so compare as string values
else result = xValue.ToString().CompareTo(yValue.ToString());
// Return result
return result;
}
private int CompareDescending(object xValue, object yValue)
{
// Return result adjusted for ascending or descending sort order ie
// multiplied by 1 for ascending or -1 for descending
return CompareAscending(xValue, yValue) * -1;
}
private object GetPropertyValue(T value, string property)
{
// Get property
PropertyInfo propertyInfo = value.GetType().GetProperty(property);
// Return value
return propertyInfo.GetValue(value, null);
}
}
}
You could dynamically create the OrderBy predicate based on string properties.
Func<MyType, object> firstSortFunc = null;
Func<MyType, object> secondSortFunc = null;
//these strings would be obtained from your NSSortDescription array
string firstProp = "firstPropertyToSortBy";
string secondProp = "secondPropertyToSortBy";
bool isAscending = true;
//create the predicate once you have the details
//GetProperty gets an object's property based on the string
firstSortFunc = x => x.GetType().GetProperty(firstProp).GetValue(x);
secondSortFunc = x => x.GetType().GetProperty(secondProp).GetValue(x);
List<MyType> ordered = new List<MyType>();
if(isAscending)
ordered = unordered.OrderBy(firstSortFunc).ThenBy(secondSortFunc).ToList();
else
ordered = unordered.OrderByDescending(firstSortFunc).ThenBy(secondSortFunc).ToList();
You could ceate a class named e.g. DynamicProperty which does retrieve the requested value. I do assume that the returned values do implement IComparable which should not be a too harsh limitation since you do want to compare the values anyway.
using System;
using System.Linq;
using System.Reflection;
namespace DynamicSort
{
class DynamicProperty<T>
{
PropertyInfo SortableProperty;
public DynamicProperty(string propName)
{
SortableProperty = typeof(T).GetProperty(propName);
}
public IComparable GetPropertyValue(T obj)
{
return (IComparable)SortableProperty.GetValue(obj);
}
}
class Program
{
class SomeData
{
public int X { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
SomeData[] data = new SomeData[]
{
new SomeData { Name = "ZZZZ", X = -1 },
new SomeData { Name = "AAAA", X = 5 },
new SomeData { Name = "BBBB", X = 5 },
new SomeData { Name = "CCCC", X = 5 }
};
var prop1 = new DynamicProperty<SomeData>("X");
var prop2 = new DynamicProperty<SomeData>("Name");
var sorted = data.OrderBy(x=> prop1.GetPropertyValue(x))
.ThenByDescending( x => prop2.GetPropertyValue(x));
foreach(var res in sorted)
{
Console.WriteLine("{0} X: {1}", res.Name, res.X);
}
}
}
}
I once wrote the following extension methods, which basically have the effect of either OrderBy or ThenBy, depending on whether the source is already ordered:
public static class Extensions {
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending) {
var orderedSource = source as IOrderedEnumerable<TSource>;
if (orderedSource != null) {
return orderedSource.CreateOrderedEnumerable(keySelector, comparer, descending);
}
if (descending) {
return source.OrderByDescending(keySelector, comparer);
}
return source.OrderBy(keySelector, comparer);
}
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) {
return source.OrderByPreserve(keySelector, null, false);
}
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) {
return source.OrderByPreserve(keySelector, comparer, false);
}
public static IOrderedEnumerable<TSource> OrderByDescendingPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) {
return source.OrderByPreserve(keySelector, null, true);
}
public static IOrderedEnumerable<TSource> OrderByDescendingPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) {
return source.OrderByPreserve(keySelector, comparer, true);
}
}
The interface is the same as OrderBy / OrderByDescending (alternatively you can pass descending as a boolean). You can write:
list.OrderByPreserve(x => x.A).OrderByPreserve(x => x.B)
which has the same effect as:
list.OrderBy(x => x.A).ThenBy(x => x.B)
Thus you could easily use keyboardP's solution with an arbitrary list of property names:
public static IEnumerable<TSource> OrderByProperties<TSource>(IEnumerable<TSource> source, IEnumerable<string> propertyNames) {
IEnumerable<TSource> result = source;
foreach (var propertyName in propertyNames) {
var localPropertyName = propertyName;
result = result.OrderByPreserve(x => x.GetType().GetProperty(localPropertyName).GetValue(x, null));
}
return result;
}
(the localPropertyName variable is used here because the iteration variable will have changed by the time the query is executed -- see this question for details)
A possible issue with this is that the reflection operations will be executed for each item. It may be better to build a LINQ expression for each property beforehand so they can be called efficiently (this code requires the System.Linq.Expressions namespace):
public static IEnumerable<TSource> OrderByProperties<TSource>(IEnumerable<TSource> source, IEnumerable<string> propertyNames) {
IEnumerable<TSource> result = source;
var sourceType = typeof(TSource);
foreach (var propertyName in propertyNames) {
var parameterExpression = Expression.Parameter(sourceType, "x");
var propertyExpression = Expression.Property(parameterExpression, propertyName);
var castExpression = Expression.Convert(propertyExpression, typeof(object));
var lambdaExpression = Expression.Lambda<Func<TSource, object>>(castExpression, new[] { parameterExpression });
var keySelector = lambdaExpression.Compile();
result = result.OrderByPreserve(keySelector);
}
return result;
}
Essentially what those Expression lines are doing is building the expression x => (object)x.A (where "A" is the current property name), which is then used as the ordering key selector.
Example usage would be:
var propertyNames = new List<string>() { "Title", "Artist" };
var sortedList = OrderByProperties(list, propertyNames).ToList();
You just need to add the ascending / descending logic.
Given two lists of different types, is it possible to make those types convertible between or comparable to each other (eg with a TypeConverter or similar) so that a LINQ query can compare them? I've seen other similar questions on SO but nothing that points to making the types convertible between each other to solve the problem.
Collection Types:
public class Data
{
public int ID { get; set; }
}
public class ViewModel
{
private Data _data;
public ViewModel(Data data)
{
_data = data;
}
}
Desired usage:
public void DoMerge(ObservableCollection<ViewModel> destination, IEnumerable<Data> data)
{
// 1. Find items in data that don't already exist in destination
var newData = destination.Except(data);
// ...
}
It would seem logical that since I know how to compare an instance of ViewModel to an instance of Data I should be able to provide some comparison logic that LINQ would then use for queries like .Except(). Is this possible?
I assume that providing a projection from Data to ViewModel is problematic, so I'm offering another solution in addition to Jason's.
Except uses a hash set (if I recall correctly), so you can get similar performance by creating your own hashset. I'm also assuming that you are identifying Data objects as equal when their IDs are equal.
var oldIDs = new HashSet<int>(data.Select(d => d.ID));
var newData = destination.Where(vm => !oldIDs.Contains(vm.Data.ID));
You might have another use for a collection of "oldData" elsewhere in the method, in which case, you would want to do this instead. Either implement IEquatable<Data> on your data class, or create a custom IEqualityComparer<Data> for the hash set:
var oldData = new HashSet<Data>(data);
//or: var oldData = new HashSet<Data>(data, new DataEqualityComparer());
var newData = destination.Where(vm => !oldData.Contains(vm.Data));
I know this is late but there is a simpler syntax using Func that eliminates the need for a comparer.
public static class LinqExtensions
{
public static IEnumerable<TSource> Except<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
{
return first.Where(x => second.Count(y => comparer(x, y)) == 0);
}
public static IEnumerable<TSource> Contains<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
{
return first.Where(x => second.FirstOrDefault(y => comparer(x, y)) != null);
}
public static IEnumerable<TSource> Intersect<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
{
return first.Where(x => second.Count(y => comparer(x, y)) == 1);
}
}
so with lists of class Foo and Bar
public class Bar
{
public int Id { get; set; }
public string OtherBar { get; set; }
}
public class Foo
{
public int Id { get; set; }
public string OtherFoo { get; set; }
}
one can run Linq statements like
var fooExceptBar = fooList.Except(barList, (f, b) => f.Id == b.Id);
var barExceptFoo = barList.Except(fooList, (b, f) => b.OtherBar == f.OtherFoo);
it's basically a slight variation on above but seems cleaner to me.
If you use this :
var newData = destination.Except(data.Select(x => f(x)));
You have to project 'data' to same type contained in 'destination', but using the code below you could get rid of this limitation :
//Here is how you can compare two different sets.
class A { public string Bar { get; set; } }
class B { public string Foo { get; set; } }
IEnumerable<A> setOfA = new A[] { /*...*/ };
IEnumerable<B> setOfB = new B[] { /*...*/ };
var subSetOfA1 = setOfA.Except(setOfB, a => a.Bar, b => b.Foo);
//alternatively you can do it with a custom EqualityComparer, if your not case sensitive for instance.
var subSetOfA2 = setOfA.Except(setOfB, a => a.Bar, b => b.Foo, StringComparer.OrdinalIgnoreCase);
//Here is the extension class definition allowing you to use the code above
public static class IEnumerableExtension
{
public static IEnumerable<TFirst> Except<TFirst, TSecond, TCompared>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TCompared> firstSelect,
Func<TSecond, TCompared> secondSelect)
{
return Except(first, second, firstSelect, secondSelect, EqualityComparer<TCompared>.Default);
}
public static IEnumerable<TFirst> Except<TFirst, TSecond, TCompared>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TCompared> firstSelect,
Func<TSecond, TCompared> secondSelect,
IEqualityComparer<TCompared> comparer)
{
if (first == null)
throw new ArgumentNullException("first");
if (second == null)
throw new ArgumentNullException("second");
return ExceptIterator<TFirst, TSecond, TCompared>(first, second, firstSelect, secondSelect, comparer);
}
private static IEnumerable<TFirst> ExceptIterator<TFirst, TSecond, TCompared>(
IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TCompared> firstSelect,
Func<TSecond, TCompared> secondSelect,
IEqualityComparer<TCompared> comparer)
{
HashSet<TCompared> set = new HashSet<TCompared>(second.Select(secondSelect), comparer);
foreach (TFirst tSource1 in first)
if (set.Add(firstSelect(tSource1)))
yield return tSource1;
}
}
Some may argue that's memory inefficient due to the use of an HashSet. But actually the Enumerable.Except method of the framework is doing the same with a similar internal class called 'Set' (I took a look by decompiling).
Your best bet is to provide a projection from Data to ViewModel so that you can say
var newData = destination.Except(data.Select(x => f(x)));
where f maps Data to ViewModel. You will need a IEqualityComparer<Data> too.
I'm trying to FIRST filter a set of products, then order them
(repository.GetAllProducts()
.Where(x=>x.Naam.Contains("filter")) as ObjectQuery<Product>)
.FunkyOrder<Product>(sortfield,sortdirection)
Using the Extension method:
public static IEnumerable<T> FunkyOrder<T>(this ObjectQuery<T> input, string fieldname = "Id", string sortdirection = "asc")
{
switch (sortdirection)
{
case "dsc":
case "desc":
return input.OrderBy("it." + fieldname + " DESC");
default:
return input.OrderBy("it." + fieldname);
}
}
(the GetAllProducts() of my Repository returns an IEnumerable
This FunkyOrder method should be working but that - as ObjectQuery - cast returns NULL
I can only use the FunkyOrder with ObjectQuery because that seems to be the only thing that supports those "it.FieldName" queries.
If I change the - as - notation to A regular cast I get:
Unable to cast object of type 'WhereEnumerableIterator`1[MySecretNameSpace.Product]' to type 'System.Data.Objects.ObjectQuery`1[MySecretNameSpace.Product]'.
Is there any way to make this work or will I be forced to either live with sorting before filtering, or write a giant switch with lambda expressions for my sorting?
This actually turned out to be a GIANT headache to try to solve; but in the end, it actually turned out being very easy to accomplish with a bit of hackishness and reflection:
public class Program
{
public class SomeClass
{
public int Id { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
List<SomeClass> sample = new List<SomeClass>
{
new SomeClass { Id = 4, Name = "ABC" },
new SomeClass { Id = 1, Name = "XYZ" },
new SomeClass { Id = 2, Name = "JKL" }
};
var result = sample.OrderByDynamic("Name", OrderDirection.Ascending).ToList();
result.ForEach(x => Console.WriteLine("Id: " + x.Id + " | Name: " + x.Name));
Console.ReadKey();
}
}
public enum OrderDirection
{
Ascending,
Descending
}
public static class LinqExtensions
{
public static IEnumerable<T> OrderByDynamic<T>(this IEnumerable<T> source, string propertyName, OrderDirection direction = OrderDirection.Ascending)
{
if(direction == OrderDirection.Ascending)
return source.OrderBy(x => x.GetType().GetProperty(propertyName).GetValue(x, null));
else
return source.OrderByDescending(x => x.GetType().GetProperty(propertyName).GetValue(x, null));
}
}
This example worked for me in test situations; I could supply the property name, and specify a sort direction, and it worked great. Simply call like so:
yourEnumerable.OrderByDynamic("YourPropertyName");
yourEnumerable.OrderByDynamic("YourPropertyName", OrderDirection.Descending);
Enjoy!
I would change your FunkyOrder method to IEnumerable and implement your dynamic sorting by creating an Expression dynamically like so
public static IEnumerable<T> FunkyOrder<T, TResult>(this IEnumerable<T> input, string fieldname = "Id", string sortdirection = "asc")
{
ParameterExpresssion parameter = Expression.Parameter(typeof(T), "p");
Expression property = Expression.PropertyOrField(parameter, fieldname);
var lambda = Expression.Lambda<Func<T, TResult>>(property, parameter)
if(sortdirection == "asc")
{
return input.OrderBy(lambda.Compile());
}
else
{
return input.OrderByDescending(lambda.Complile());
}
}
You will no longer need to cast to ObjectQuery
You can use Dynamic Linq for dynamic filtering and sorting. This small library (only one single source file) contains extension methods of IQueryable<T> like:
public static IQueryable<T> Where<T>(this IQueryable<T> source,
string predicate, params object[] values)
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source,
string ordering, params object[] values)
//...and more like GroupBy, Select
Your query on IEnumerable could then be written as:
repository.GetAllProducts().AsQueryable()
.Where(x => x.Naam.Contains("filter"))
.OrderBy("FieldName desc")
The library implements all the necessary reflection code to perform those dynamic queries.
My Code looks like this :
Collection<NameValueCollection> optionInfoCollection = ....
List<NameValueCollection> optionInfoList = new List<NameValueCollection>();
optionInfoList = optionInfoCollection.ToList();
if(_isAlphabeticalSoting)
Sort optionInfoList
I tried optionInfoList.Sort() but it is not working.
Using the sort method and lambda expressions, it is really easy.
myList.Sort((a, b) => String.Compare(a.Name, b.Name))
The above example shows how to sort by the Name property of your object type, assuming Name is of type string.
If you just want Sort() to work, then you'll need to implement IComparable or IComparable<T> in the class.
If you don't mind creating a new list, you can use the OrderBy/ToList LINQ extension methods. If you want to sort the existing list with simpler syntax, you can add a few extension methods, enabling:
list.Sort(item => item.Name);
For example:
public static void Sort<TSource, TValue>(
this List<TSource> source,
Func<TSource, TValue> selector)
{
var comparer = Comparer<TValue>.Default;
source.Sort((x, y) => comparer.Compare(selector(x), selector(y)));
}
public static void SortDescending<TSource, TValue>(
this List<TSource> source,
Func<TSource, TValue> selector)
{
var comparer = Comparer<TValue>.Default;
source.Sort((x, y) => comparer.Compare(selector(y), selector(x)));
}
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
}
List<Person> people = new List<Person>();
people.Sort(
delegate(Person x, Person y) {
if (x == null) {
if (y == null) { return 0; }
return -1;
}
if (y == null) { return 0; }
return x.FirstName.CompareTo(y.FirstName);
}
);
You need to set up a comparer that tells Sort() how to arrange the items.
Check out List.Sort Method (IComparer) for an example of how to do this...