I'm having a hard time figuring out what I am missing when trying to parameterize a "Select" with "Distinct" query.
This is a sample of the code I have repeated multiple times with different items to select.
private void getCustomerCodeList(ObservableCollection<Model> FilteredData)
{
var distCustomerCode = FilteredData.Select(i => new { i.CustomerCode, i.FamilyName }).Distinct().OrderBy(x => x.FamilyName).ToList();
DistinctCustomerCodeList.Clear();
foreach (var item in distCustomerCode)
{
DistinctCustomerCodeList.Add(new Model() { CustomerCode = item.CustomerCode, FamilyName = item.FamilyName });
}
}
I am trying to convert this into one method where I can pass in the "Select" and the "Orderby" as parameters. The code below is as far as I was able to get, and it works fine if I pass in a lambda with one property, but errors as soon as I try to add a second.
public void getDistinct<TKey>(ObservableCollection<Model> FilteredData, Func<Model, TKey> myDistinctProperty, Func<TKey, TKey> mySortingProperty)
{
var distinct = FilteredData.Select(myDistinctProperty).Distinct().OrderBy(mySortingProperty).ToList();
DistinctCustomerCodeList.Clear();
foreach (var item in distinct )
{
DistinctCustomerCodeList.Add(new Model() { CustomerCode = item.ToString() });
}
}
If I call this method:
getDistinct<string>(FilteredData, x => x.CustomerCode, x => x);
it works fine
but if I try:
getDistinct<string>(FilteredData, i => new { i.CustomerCode, i.FamilyName }, x => x.FamilyName)
it errors.
Can anyone shed some light on this for me.
Thanks.
Update-
Here is the error message I am receiving.
'IEnumerable' does not contain a definition for 'OrderBy' and the best extension method overload 'ParallelEnumerable.OrderBy<Model, string>(ParallelQuery, Func<Model, string>)' requires a receiver of type 'ParallelQuery'
I made the changes based on the clarification that nalpnir provided and here is what my updated code looks like.
public void getDistinct<TKey>(ObservableCollection<Model> FilteredData, Func<Model, TKey> myDistinctProperty, Func<Model, string> mySortingProperty)
{
var distinct= FilteredData.Select(myDistinctProperty).Distinct().OrderBy(mySortingProperty).ToList();
DistinctCustomerCodeList.Clear();
foreach (var item in distinct)
{
DistinctCustomerCodeList.Add(new Model() { CustomerCode = item.ToString() });
}
}
This portion has the error provided above (FilteredData.Select(myDistinctProperty).Distinct())
and here is the updated method call:
getDistinct<Model>(FilteredData, i => new Model { FamilyName = i.FamilyName, CustomerCode = i.CustomerCode }, x => x.CustomerCode);
Update 2 -
Thank you all for your feedback.
I ended up using the answer from JeremyLakeman, as it fit best in the project I am working on.
The answer from nalpnir is awesome and I will try to implement it in the future.
Your first example turned each instance of Model into an anonymous type, then later back into a Model again. Instead, the generic distinct method should create a new Model with only the requested fields.
public void getDistinct<TOrder>(ObservableCollection<Model> FilteredData, Func<Model, Model> distinct, Func<Model, TOrder> sort)
{
var distinct = FilteredData.Select(distinct).Distinct().OrderBy(sort).ToList();
DistinctCustomerCodeList.Clear();
DistinctCustomerCodeList.AddRange(distinct);
}
Then this should work, with the compiler able to infer the generic types;
getDistinct(FilteredData, i => new Model{ i.CustomerCode, i.FamilyName }, x => x.FamilyName);
The reason is simple, the LINQ you are doing is wrong, for 2 reasons but the same reason itself:
Your TKey is set as a string, so when you say new { i.CustomerCode, i.FamilyName } it doesnt know how to go from that expression to a string.
The same occurs in the case of the sorting, x is of type String, and x doesnt know what x.CustomerName is.
What you should use is the Model as TKey, like this:
getDistinct<Model>(list, i => new Model { FamilyName = i.FamilyName, CustomerCode = i.CustomerCode }, x => x.CustomerCode);
UPDATE:
Since i noticed it didnt do what you wanted to do, which is to be able to get it without repetitions, I changed a bit the idea so you can get around it, and even made it more generic so you can implement it with any class you want, as long as you make a few changes which i ll explain
public static List<T> getDistinct<T, TKey>(ObservableCollection<T> FilteredData, Func<T, TKey> myDistinctProperty, Func<T, string> mySortingProperty)
where T:Model, new()
{
var DistinctCustomerCodeList = new List<T>();
var distinct = FilteredData.GroupBy(myDistinctProperty).Select(x => x.First()).OrderBy(mySortingProperty).ToList();
foreach (var item in distinct)
{
DistinctCustomerCodeList.Add(new T() { CustomerCode = item.CustomerCode, FamilyName = item.FamilyName });
}
return DistinctCustomerCodeList;
}
As you see now you have 2 Generics, One is T which i restricted to be of type Model, and the other one is another type whichever you want in this particular case it will accept new { x.CustomerCode, x.FamilyName } . I ll give you a link to a .Net fiddle so you know can see a demo
Demo: demo
You can toy with it. Basically whichever class you replace model for will work as long as you change the output of the type T, and change the Add inside the foreach. Inherited members of Model will also work so lets say for example you implement the following:
public class ModelExtended:Model
{
public int NewProp { get; set; }
}
And also changed the ObservableCollection To ObservableCollection will also work because they share some properties
The OrderBy() is expecting a generic type TKey but the parameter mySortingProperty is explicitly referencing Model. Update the parameter in your method definition as follows:
public void getDistinct<TKey>(ObservableCollection<Model> FilteredData, Func<Model, TKey> myDistinctProperty, Func<TKey, string> mySortingProperty)
Related
I would like to make a sorting extension method which will take a Generic Collection and sort it using one or more keys. The keys will be properties of the collection's containing objects.
A sample LINQ query with 3 keys looks like this.
studentResults.OrderBy(x => x.CG).ThenBy(x => x.Student.Roll)
.ThenBy(x => x.Student.Name).ToList();
I have already found something which can do this with one key.
public static List<TSource> OrderByAsListOrNull<TSource, TKey>(
this ICollection<TSource> collection, Func<TSource,TKey> keySelector)
{
if (collection != null && collection.Count > 0) {
return collection
.OrderBy(x => keySelector(x))
.ToList();
}
return null;
}
I thought of using IEnumerable<Func<TSource, TKey> keySelector>, but I cannot call the function like that.
So, how may I implement a method of this kind?
In theory, you could build a multi-levelled sort extension, which diffentiates between the initial OrderBy and the subsequent ThenBys for secondary, tertiary sorting tiebreakers. Since by taking multiple order functions, each of which could reference a different type, you'll need to soften the projected type (I've used object, below).
public static class Extensions
{
public static IEnumerable<T> MyOrderBy<T>(
this IEnumerable<T> source,
params Func<T, object>[] orders)
{
Debug.Assert(orders.Length > 0);
var sortQuery = source.OrderBy(orders[0]);
foreach(var order in orders.Skip(1))
{
sortQuery = sortQuery.ThenBy(order);
}
return sortQuery;
}
}
public class Poco
{
public string Name {get; set;}
public int Number {get; set;}
}
void Main()
{
var items = new []{
new Poco{Name = "Zebra", Number = 99},
new Poco{Name = "Apple", Number = 123}};
foreach(var poco in items.MyOrderBy(i => i.Number, i => i.Name))
{
Console.WriteLine(poco.Name);
}
}
The problem with this (as with your original function) is that you'll probably want to order by descending at some point. Although for numeric sort functions this could be hacked by passing a *-1, it's going to be really difficult to do this for an arbitrary type
// Hack : Order a numeric descending
item => item.Number * -1
For me, I would just stay with Linq's sorting extensions, and not try to abstract them in any way!
I am retrieving some tuples from a database that are mapped to entity classes by means of Entity Framework.
For these entities, I have a key selector function (supplied at runtime by other developers) that I would like to pass to Queryable.OrderBy. The key selector function is provided upon "registration" of the entity type in my system - which happens by means of a method that looks roughly like this:
public void RegisterEntity<TEntity, TKey>(string entityName, TKey defaultKey, Func<TEntity, TKey> keySelectorFunc)
I would like to execute this OrderBy call before materializing the results to entity objects (i.e. in such a way that the OrderBy call still gets translated to SQL under the hood).
The problem is that the entities have composite keys, and thus, the key selector function will return a custom object instantiated in the function. You can imagine it like this:
var keySelectorFunc = e => new CustomKey(e.Value1, e.Value2);
As usual, Entity Framework does not like this (the usual "Only parameterless constructors and initializers are supported in LINQ to Entities" error).
Is there any way to use such a custom key selector function to return a custom key? Do I have to resort to anonymous classes? Or should I move the OrderBy call to a place after I have left the LINQ-to-Entities world?
In this particular case it would be easy to use Sort method of Generic List.
https://msdn.microsoft.com/en-us/library/3da4abas(v=vs.110).aspx
Sort method requires the type of the list to implement IComparable interface and it uses the implementation of CompareTo method from IComparable interface. Otherwise implementation of IComparer also can be passed to this method.
So if your entity class is already implemeting IComparable interface then this should surely work for you. You will have to to .ToList() on the IQueryable result of course before you can call the Sort method on it.
public class Category : IComparable<Category>
{
public int CategoryId { get; internal set; }
public string CategoryName { get; internal set; }
public int CompareTo(Category x)
{
return String.Compare(x.CategoryName, this.CategoryName, StringComparison.InvariantCulture);
}
}
List<Category> categories = new List<Category>();
categories.Add(new Category {CategoryName = "Cate1"});
categories.Add(new Category {CategoryName = "Cate2"});
categories.Sort();
foreach (var cat in categories)
{
Console.WriteLine(cat.CategoryName);
}
This displays me category names in reverse order based on the comparison logic I have written in the CompareTo method of Category Class.
In this case I think the best way is use a custom ExtensionMethod to avoid any overhead of coding or unnecessary complexity to do that.
See if it implementation can help you.
First we create your customkey class that is responsable to create the statement expressions:
class CustomKey
{
public CustomKey(params string[] value)
{
if(!value.Any())
throw new InvalidOperationException("Select at least one Property for this operation");
Values = new List<string>();
Values.AddRange(value);
}
private List<string> Values { get; set; }
// this method run throughout all property configured to create the expressions
public void ForEachProperty<TSource, TKey>(Action<Expression<Func<TSource, TKey>>, bool> method)
{
bool firstItem = true;
Values.ForEach(f =>
{
var expression = CreateExpression<TSource, TKey>(f);
method(expression, firstItem);
firstItem = false;
});
}
// this method is responsable to create each expression
Expression<Func<TSource, TKey>> CreateExpression<TSource, TKey>(string property)
{
var parameter = Expression.Parameter(typeof(TSource), "x");
var member = typeof(TSource).GetMember(property).FirstOrDefault();
Expression body = Expression.MakeMemberAccess(parameter, member);
return Expression.Lambda<Func<TSource, TKey>>(Expression.Convert(body, typeof(object)), parameter);
}
}
After that we create your custom ExtesionMethod, somethink like that:
public static class OrderByExtensionClass
{
// instead of try passing an expression, we pass our CustomKey object with the columns to sort.
// than this method create the apropriate OrderBy Expression statement
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, CustomKey customKey)
{
// the parameter isFirst is just to control where we are to build the expression
customKey.ForEachProperty<TSource, object>((expression, isFirst) =>
{
if (isFirst)
source = source.OrderBy(expression);
else
source = ((IOrderedQueryable<TSource>)source).ThenBy(expression);
});
return ((IOrderedQueryable<TSource>)source);
}
}
After that we just do:
CustomKey custom = new CustomKey("Name", "Age");
myEntityContext.People.OrderBy(custom).ToList()
I hope it can help you.
Part of the problem, I think, is that OrderBy wouldn't know what to do with a complex type. SQL Server knows how to order by primitive types, but that's about it. You would have to do something like ...OrderBy(x=>x.Field1).ThenBy(x=>x.Field2). You could write an extension method that takes the key, extracts the property names from the key, and builds the .OrderBy().ThenBy() expression, as long as you know what the key will be before executing the query. Otherwise yeah, you may have to materialize the results before ordering.
I need to create a general routine in visual studio to get some parameters as input and return a list resulted from a repository. I am using Linq. But I am not sure how to develop this function and neither what key words I can use and find some resources.
This is a sample code that already is used in my program:
var lstReceiptDetails = Repository<TransactionDetail>()
.Where(current => current.HeaderId == headerId)
.OrderBy(current => current.DocumentRow)
.ToList();
I need to change the above linq statement to something like the following pseudocode:
private List<> GetQuery(repositoryName, conditionFieldName, orderFieldName )
{
var lstResult = Repository<repositiryName>()
.Where(current => current.ConditionFieldName == conditionFieldName)
.OrderBy(current => current.orderFieldName)
.ToList();
Return(lstResult);
}
Any help is appreciate.
Maryam
I think the closest way you can get is by using the following example below. I've tried a several ways to do this, but it would harm the usability and the readability. This is a compromise between code duplication and readability.
A sample POCO object:
class TransactionDetail
{
public DateTime DateProcessed { get; set; }
public string AccountName { get; set; }
}
The repositories:
abstract class GenericRepository<T>
{
public List<T> GetQuery<TKey>(
Func<T, bool> conditionFieldName,
Func<T, TKey> orderFieldName)
{
var lstResult = Repository()
.Where(conditionFieldName)
.OrderBy(orderFieldName)
.ToList();
return lstResult;
}
private IEnumerable<T> Repository()
{
throw new NotImplementedException();
}
}
class TransactionDetailRepository : GenericRepository<TransactionDetail>
{
}
And caller-side:
var repository = new TransactionDetailRepository();
var transactions = repository.GetQuery(
x => x.AccountName == "Foo Bar",
x => x.DateProcessed);
Argument checks should still be implemented properly though.
If this piece of code should be used in EntityFramework or Linq-to-SQL, parameters should be wrapped in Expression<T> such that, for example: Func<T, bool> becomes Expression<Func<T, bool>>
You can try to use the LINQ Dynamic Query Library that take string arguments instead of type-safe language operators.
Short example:
var result = Repository<repositoryName>().
Where("Id = 1").
Select("new(Id, Name)");
More information here: http://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library
I was wondering how I might be able to create a re-usable method that creates select lists based on method arguments? I was thinking something like below:
public IEnumerable<SelectListItem> CreateSelectList(IList<T> entities, T value, T text)
{
return entities
.Select(x => new SelectListItem
{
Value = x.value.ToString(),
Text = x.text.ToString()
});
}
I think I have it a bit backwards though. I'm unsure how to call a method like this, when I call it with an IList of Category for the first argument the compiler complains that it cannot assign type Category to type T? Also, how would I insert the method arguments into the lambda? Any help appreciated!
Code I'm trying to use to call it (which is wrong, but you get the idea)
viewModel.Categories = _formServices.CreateSelectList(categories, Id, Name);
Code I'm trying to make more generic and reusable:
viewModel.Categories = categories
.Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
Edit For Answer
Credit goes to #Pavel Backshy for working answer. I wanted to edit in an extension I made to his answer in case it helps anybody! The extension just adds a .Where clause into the mix:
public IEnumerable<SelectListItem> CreateSelectListWhere<T>(IList<T> entities, Func<T, bool> whereClause, Func<T, object> funcToGetValue, Func<T, object> funcToGetText)
{
return entities
.Where(whereClause)
.Select(x => new SelectListItem
{
Value = funcToGetValue(x).ToString(),
Text = funcToGetText(x).ToString()
});
}
You can define this using Reflection to take property value by name, but I think more elegant and flexible to use Func.
Change your method to:
public IEnumerable<SelectListItem> CreateSelectList<T>(IList<T> entities, Func<T, object> funcToGetValue, Func<T, object> funcToGetText)
{
return entities
.Select(x => new SelectListItem
{
Value = funcToGetValue(x).ToString(),
Text = funcToGetText(x).ToString()
});
}
And then you can use it by this way:
viewModel.Categories = _formServices.CreateSelectList(categories, x => x.Id, x => x.Name);
I found Pavel Bakshy's answer, as well as your edit to include the 'whereClause' very helpful, and exactly what I was trying to accomplish.
Along the same lines I also added a 'selectedValue' object as this was part of what I was trying to do. The 'null' check is for when a list does not have currently selected value (i.e. when it's first loaded).
Edit: Also I used an IEnumerable instead of IList as my first parameter
IEnumerable<SelectListItem> ISelectUtils.CreateSelectList<T>(IEnumerable<T> entities, Func<T, bool> whereClause, Func<T, object> funcToGetValue, Func<T, object> funcToGetText, object selectedValue)
{
return entities
.Where(whereClause)
.Select(x => new SelectListItem
{
Value = funcToGetValue(x).ToString(),
Text = funcToGetText(x).ToString(),
Selected = selectedValue != null ? ((funcToGetValue(x).ToString() == selectedValue.ToString()) ? true : false) : false
});
}
I have a class MyDummyClass to which I'd like to pass some properties in form of a Lambda expression for a later evaluation. So what I can do something like
public class MyDummyClass<T>
{
public MyDummyClass(Expression<Func<T, object>> property)
{
...
}
...
}
..and then use that class like new MyDummyClass<Person>(x=>x.Name), right?
But then I'd like to pass not only a single property but a list of properties. So I'd write my class like
public class MyDummyClass<T>
{
public MyDummyClass(IEnumerable<Expression<Func<T, object>>> properties)
{
...
}
...
}
and I'd like to use it like new MyDummyClass<Person>(new[] { x=>x.Name, x=>x.Surname }) but unfortunately that doesn't work! Instead I have to write
new MyDummyClass<Person>
(new Expression<Func<Person, object>>[] { x=>x.Name, x=>x.Surname});
But this is a bit awkward to write, isn't it? Of course, using params would work, but this is just a sample out of a more complicated piece of code where using params is not an option.
Does anyone have a better option to come out of this??
Try using params instead:
public MyDummyClass(params Expression<Func<T, object>>[] properties)
Then you should be able to do:
var dummy = new DummyClass<Person>(x => x.Name, x => x.Surname);
You could try:
public class MyDummyClass<T>
{
public MyDummyClass(Expression<Func<T, object>> expression)
{
NewArrayExpression array = expression.Body as NewArrayExpression;
foreach( object obj in ( IEnumerable<object> )( array.Expressions ) )
{
Debug.Write( obj.ToString() );
}
}
}
And then you would call it like this:
MyDummyClass<Person> cls = new MyDummyClass<Person>( item => new[] { item.Name, item.Surname } );
The problem is this won't give you the value of the property because no actual Person instance it specified Doing a ToString on "obj" will give you the name of the property. I don't know if this is what you're after, but it maybe a starting point.