I want to build an expression for IQueryable GroupBy. While at the moment I'm just simplifying the problem to try and get it working, the eventual final implementation will involve the creation of quite complex expression trees so I want to build a complete expression that can then be integrated into other expressions.
I specifically want to build an expression of this overload:
public static System.Linq.IQueryable<TResult> GroupBy<TSource,TKey,TResult> (
this System.Linq.IQueryable<TSource> source,
System.Linq.Expressions.Expression<Func<TSource,TKey>> keySelector,
System.Linq.Expressions.Expression<Func<TKey,System.Collections.Generic.IEnumerable<TSource>,TResult>> resultSelector);
... my problem is in the implementation of the resultSelector and and the IEnumerable<TSource>.
I have a table of Customers (just dummy data for the purposes of working out this problem). This is stored in an SQL DB and I specifically want to use IQueryable to access the data.
public class Customer
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int Age { get; set; }
}
I also have a GroupResult class used to hold the results of the GroupBy (I have different constructors which I've been using in my testing to work out where my problem is occurring)
internal class GroupResult
{
public string? Name { get; set; }
public int NumRecords { get; set; }
public decimal AverageAge { get; set; }
public int TotalAge { get; set; }
public GroupResult() { }
public GroupResult(string name)
{
Name = name;
}
public GroupResult(IEnumerable<Customer> customers)
{
Name = Guid.NewGuid().ToString();
NumRecords = customers.Count();
}
public GroupResult(string name, IEnumerable<Customer> customers)
{
Name = name;
NumRecords = customers.Count();
}
}
The main static class that displays prompts to select column to group on, creates the relevant expression tree and executes it
internal static class SimpleGroupByCustomer
{
internal static DataContext db;
internal static void Execute()
{
using (db = new DataContext())
{
//get input
Console.WriteLine();
Console.WriteLine("Simple Customer GroupBy");
Console.WriteLine("=======================");
Console.WriteLine("Simple GroupBy on the Customer Table");
Console.WriteLine();
Console.WriteLine("Select the property that you want to group by.");
Console.WriteLine();
var dbSet = db.Set<Customer>();
var query = dbSet.AsQueryable();
//for this example we're just prompting for a column in the customer table
//GetColumnName is a helper function that lists the available columns and allows
//one to be selected
string colName = Wrapper.GetColumnName("Customer");
MethodInfo? method = typeof(SimpleGroupByCustomer).GetMethod("GetGroupBy",
BindingFlags.Static | BindingFlags.NonPublic);
if (method != null)
{
method = method.MakeGenericMethod(new Type[] { typeof(String), query.ElementType });
method.Invoke(null, new object[] { query, colName });
}
}
}
internal static void GetGroupBy<T, TTable>(IQueryable query, string colName)
{
Type TTmp = typeof(TTable);
var param = Expression.Parameter(TTmp, "c");
var prop = Expression.PropertyOrField(param, colName);
LambdaExpression keySelector = Expression.Lambda<Func<TTable, T>>(prop, param);
var param1 = Expression.Parameter(typeof(T), "Key");
var param2 = Expression.Parameter(typeof(IEnumerable<TTable>), "Customers");
var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T), typeof(IEnumerable<TTable>) });
//var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T) });
//var ci = typeof(GroupResult).GetConstructor(new[] { typeof(IEnumerable<TTable>) });
if (ci == null)
return;
var pExp = new ParameterExpression[] { param1, param2 };
var methodExpression = Expression.Lambda<Func<T, IEnumerable<TTable>, GroupResult>>(
Expression.New(ci, new Expression[] { param1, param2 }), //<--- ERROR HERE
pExp
);
Type[] typeArgs = new Type[] { typeof(TTable), typeof(T), typeof(GroupResult) };
Expression[] methodParams = new Expression[] { query.Expression, keySelector, methodExpression };
var resultExpression = Expression.Call(typeof(Queryable), "GroupBy", typeArgs, methodParams);
IQueryable dbQuery = query.Provider.CreateQuery(resultExpression);
if (dbQuery is IQueryable<GroupResult> results)
{
foreach (var result in results)
{
Console.WriteLine("{0,-15}\t{1}", result.Name, result.NumRecords.ToString());
}
}
}
}
When I run this and try and iterate through the results I get the following exception:
System.InvalidOperationException: 'variable 'Customers' of type 'System.Collections.Generic.IEnumerable`1[ExpressionTrees3.Data.Customer]' referenced from scope '', but it is not defined'
which is being caused by the param2 ParameterExpression marked above.
If I use the GroupResult constructor that just takes the key value
var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T) });
and omit the param2 from the Lambda body definition the code works as expected and I get a collection of GroupResult records containing the distinct key values in the Name field (but obviously no summary value).
I've tried everything I can think of and just can't get past this error - it's as though the GroupBy is not actually producing the IEnumerable grouping of Customers for each key.
I suspect I'm missing something really obvious here, but just can't see it. Any help would really very much appreciated.
Please note that I am after answers to this specific issue, I'm not looking for alternative ways of doing a GroupBy (unless there's a fundamental reason why this shouldn't work) - this will be rolled into a much larger solution for building queries and I want to use the same process throughout.
Thanks Svyatoslav - as I thought, it was me being especially dumb!
Your comments, as well as a discussion with a friend who has a lot SQL knowledge pointed me in the right direction.
I had been thinking that the GroupBy expression was going to return an Enumerable for each key value and was trying to pass that into a function ... it always felt wrong, but I just ignored that and kept going.
It's obvious now that I need to tell the GroupBy what to calculate and return (i.e. your comment about aggregation).
So for this easy example, the solution is very simple:
var pExp = new ParameterExpression[] { param1, param2 };
var countTypes = new Type[] { typeof(TTable) };
var countParams = new Expression[] { param2 };
var countExp = Expression.Call(typeof(Enumerable), "Count", countTypes, countParams);
var methodExpression = Expression.Lambda<Func<T, IEnumerable<TTable>, GroupResult>>(
Expression.New(ci, new Expression[] { param1, countExp }),
pExp
);
Just by adding the 'Count' expression into the GroupBy method call it works!
.. and adding a new ctor for GroupResult:
public GroupResult(string name, int count)
{
Name = name;
NumRecords = count;
}
(yep, I feel a bit stupid!)
Related
I'm trying to pass a dynamically generated LambdaExpression to an IncludeFilter, as follows:
EDIT: I've changed my test code to the following, as (correctly) I wasn't implementing my "Where" statement. The correct where statement is being generated, but I can't pass the lambda statement into the IncludeFilter call:
DbSet<MyTestEntity> dbSet = db.Set<MyTestEntity>();
ParameterExpression parameter = Expression.Parameter(typeof(MyTestEntity), "t");
Expression idProperty = Expression.Property(parameter, "mytestentityid");
Expression delProperty = Expression.Property(parameter, "deleted");
Expression delTarget = Expression.Constant(false, typeof(bool));
Expression deletedMethod = Expression.Call(delProperty, "Equals", null, delTarget);
Expression<Func<MyTestEntity, bool>> lambda = Expression.Lambda<Func<MyTestEntity, bool>>(deletedMethod, parameter);
IQueryable<MyTestEntity> query = dbSet.Where(lambda);
Console.WriteLine("Current Query: {0}", query.ToString());
foreach (string include in includes)
{
Type subType = db.GetType().Assembly.GetTypes().SingleOrDefault(x => x.Name.EndsWith(include));
Assert.IsNotNull(subType);
ParameterExpression innerParam = Expression.Parameter(subType, subType.Name);
Assert.IsNotNull(innerParam);
MemberExpression inrDelProp = Expression.Property(innerParam, "deleted");
Assert.IsNotNull(inrDelProp);
ConstantExpression inrDelCstProp = Expression.Constant(false, typeof(bool));
Assert.IsNotNull(inrDelCstProp);
MethodCallExpression inrDelMthd = Expression.Call(inrDelProp, "Equals", null, inrDelCstProp);
Assert.IsNotNull(inrDelMthd);
var delegateType = typeof(Func<,>).MakeGenericType(subType, typeof(bool));
dynamic inrLmbdaExpr = Expression.Lambda(delegateType, inrDelMthd, innerParam);
Assert.IsNotNull(inrLmbdaExpr);
Console.WriteLine("inrLmbdaExpr: {0}", inrLmbdaExpr.ToString()); // Result: MyTestEntityChild => MyTestEntityChild.deleted.Equals(false)
query = query.IncludeFilter(inrLmbdaExpr); // ERROR HERE
Assert.IsNotNull(query);
Console.WriteLine("-------------------------------------------------");
Console.WriteLine("Current Query: {0}", query.ToString());
}
This is built into an abstract class allowing me to pass in an entity type, retrieve the records, and reuse the method irrespective of the entity type; however, I'm also trying to filter out child entities that are marked as deleted (thus the use of EF+).
How can I do this?
EDIT 2: So, I realized I also have Linq.Dynamic.Core (!) in my solution, so I already have access to parsing a LambdaExpression from string. However, the error I get says that IncludeFilter doesn't know which method it's trying to use. (I see in the Object Browser that one uses Expression> and one uses Expression>>. If I could just figure out how to get the IncludeFilter to recognize which method, I think I'd be done! Here's a sample of the code I've rewritten:
string myIncStr = String.Format("x => x.{0}.Where(s => s.deleted.Equals(false)).Where(x => x.MyEntityId.Equals(IncomingId)",includedEntityName);
IEnumerable<MyEntity> result = db.MyEntity.IncludeFilter(System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(typeof(MyChildEntity), myIncStr, null));
Is there a way to "force" (for lack of a better term) the IncludeFilter to use one method? Is it by passing a value instead of null in the Parser?
BTW, thanks for your help. Your EFP library is actually excellent.
Disclaimer: I'm the owner of the project Entity Framework Plus
Yes, it's possible but only if you can specify the generic argument type required by the method explicitly for the QueryFilter (As you mentioned in your comment).
Otherwise, you will need to also call the QueryFilter via the expression to make everything generic.
However, your current expression seems to have some error such as not calling the Where methods.
What you want to achieve is probably something similar to this:
query = query.IncludeFilter(x => x.Childs.Where(y => !y.Deleted));
Disclaimer: I'm the owner of the project Eval-Expression.NET
This library is not free but makes working with a dynamic expression easier and faster.
Once you get used, you can quickly create a dynamic expression in only a few lines as you normally write LINQ. Here is a code that could handle a similar scenario as your:
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Windows.Forms;
using Z.Expressions;
namespace Z.EntityFramework.Plus.Lab.EF6
{
public partial class Form_Request_IncludeFilter_Dynamic : Form
{
public Form_Request_IncludeFilter_Dynamic()
{
InitializeComponent();
// CLEAN
using (var context = new EntityContext())
{
context.MyEntityClasses.RemoveRange(context.MyEntityClasses);
context.MyEntityClassToFilters.RemoveRange(context.MyEntityClassToFilters);
context.SaveChanges();
}
// SEED
using (var context = new EntityContext())
{
var entity1 = context.MyEntityClasses.Add(new MyEntityClass {ColumnInt = 1, Childs = new List<MyEntityClassToFilter>()});
entity1.Childs.Add(new MyEntityClassToFilter {ColumnInt = 1, Deleted = true});
entity1.Childs.Add(new MyEntityClassToFilter {ColumnInt = 2, Deleted = false});
context.MyEntityClasses.Add(new MyEntityClass {ColumnInt = 2});
context.MyEntityClasses.Add(new MyEntityClass {ColumnInt = 3});
context.SaveChanges();
}
// TEST
using (var context = new EntityContext())
{
// You must register extension method only once
// That should not be done here, but for example purpose
EvalManager.DefaultContext.RegisterExtensionMethod(typeof(QueryIncludeFilterExtensions));
// That could be also dynamic. I believe you already handle this part
IQueryable<MyEntityClass> query = context.MyEntityClasses;
// The path to include
var include = "Childs";
// The dynamic expression to execute
var dynamicExpression = "IncludeFilter(x => x." + include + ".Where(y => !y.Deleted));";
query = query.Execute<IQueryable<MyEntityClass>>(dynamicExpression);
// The result
var list = query.ToList();
}
}
public class EntityContext : DbContext
{
public EntityContext() : base("CodeFirstEntities")
{
}
public DbSet<MyEntityClass> MyEntityClasses { get; set; }
public DbSet<MyEntityClassToFilter> MyEntityClassToFilters { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Types().Configure(x =>
x.ToTable(GetType().DeclaringType != null
? GetType().DeclaringType.FullName.Replace(".", "_") + "_" + x.ClrType.Name
: ""));
base.OnModelCreating(modelBuilder);
}
}
public class MyEntityClass
{
public int ID { get; set; }
public int ColumnInt { get; set; }
public List<MyEntityClassToFilter> Childs { get; set; }
}
public class MyEntityClassToFilter
{
public int ID { get; set; }
public int ColumnInt { get; set; }
public bool Deleted { get; set; }
}
}
}
EDIT: Answer question
Please review my changed code
You are still missing the where clause.
What you have is something similar to this as you commented
// Result: MyTestEntityChild => MyTestEntityChild.deleted.Equals(false)
What you want is something similar to this
// Result: MyTestEntityChild => MyTestEntityChild.Where(x => x.deleted.Equals(false))
EDIT: Answer question
Oh sorry, I now understand the problem with it.
If you don't know the type, you will need to call the IncludeFilter in an expression as well to make everything generic. It cannot be called explicitely like you are trying to do.
The question is similar to this one, but answer does not provide 2 critical things to me:
I need the code to work over navigational properties
I am trying to build extension method
I want to write queries like this:
this.context.User
.Where(t => t.Id > 10)
.SelectCustom(t => t.Address.Country.Title)
.OrderBy(t => t.DisplayName)
.Skip(10).Take(5);
With answer in provided link I get this far:
public class SelectList<TSource>
{
private List<MemberInfo> members = new List<MemberInfo>();
public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
{
var member = ((MemberExpression)selector.Body).Member;
members.Add(member);
return this;
}
public Expression<Func<TSource, TResult>> ToDynamicColumns()
{
return this.members.??????????;
}
}
public static IQueryable<T> SelectCustom<T>(this IQueryable<T> query, Expression<Func<TSource, TKey>> FirstAdditional = null)
{
var columns = new SelectList<T>();
columns.Add(t => t.Id);
columns.Add(t => t.DisplayName)
if (FirstAdditional != null)
columns.Add(FirstAdditional);
return query.Select(columns.ToDynamicColumns);
}
Can this be done with EF Core 2.0?
EF will look through lambda invoke operations as if the body of that expression was inlined. So I would recommend leaving the source expressions alone and just generate expressions to invoke them.
Also I would keep the result type simple, and just return each row as an array of object. This should result in less overhead than creating lots of dictionaries. If you do need to access fields by name, you should create a single dictionary to maintain the relationship between names and column numbers.
public class SelectList<TSource>
{
private List<LambdaExpression> members = new List<LambdaExpression>();
public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
{
members.Add(selector);
return this;
}
public Expression<Func<TSource, TResult>> ToDynamicColumns()
{
var parameter = Expression.Parameter(typeof(TSource), "e");
return Expression.Lambda<Func<TSource, object[]>>(
Expression.NewArrayInit(
typeof(object),
members.Select(m =>
Expression.Convert(Expression.Invoke(m, parameter), typeof(object))
)
),
parameter);
}
}
Though in your case, since you are writing an extension method to only return the same key details and a single additional field, you could probably define a single generic type to hold the results, and avoid any mucking around with Linq expressions at all;
public class UserResult<V>{
public int Id { get; set; }
public string DisplayName { get; set; }
public V Value { get; set; }
}
public static IQueryable<UserResult<V>> SelectCustom<V>(this IQueryable<User> query, Expression<Func<User, V>> ValueGetter)
{
return query.Select(u => new UserResult<V>{
Id = u.Id,
DisplayName = u.DisplayName,
Value = ValueGetter(u)
});
}
Well almost, if c# would just allow you to compile a call of one Expression<Delegate> from within another. Instead we can implement an ExpressionVisitor to unwrap any call to Compile;
public class DontCompile : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
// Inline any lambda arguments that are expressions
if (node.Expression is ConstantExpression lambdaArgs
&& node.Member is FieldInfo field
&& typeof(Expression).IsAssignableFrom(field.FieldType))
return (Expression)field.GetValue(lambdaArgs.Value);
return base.VisitMember(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
// Don't compile lambda expressions
if (node.Method.Name == "Compile"
&& typeof(LambdaExpression).IsAssignableFrom(node.Object.Type))
return Visit(node.Object);
return base.VisitMethodCall(node);
}
public static Expression<T> Tidy<T>(Expression<T> func) => (Expression<T>)new DontCompile().Visit(func);
}
...
return query.Select(DontCompile.Tidy<...>(u => new UserResult<V>{
Id = u.Id,
DisplayName = u.DisplayName,
Value = ValueGetter.Compile()(u)
});
...
But that's starting to look a bit messy.
You could do this with Expression.ListInit, here the TResult must have an Add instance method e.g. Dictionary<string, object>. This could just work but I havent even compiled it. In any way this should give enough hint on how to build this the way you want.
public Expression<Func<TSource, Dictionary<string, object>>> ToDynamicColumns()
{
var addMethod = typeof(TResult).GetMethod("Add");
var paramX = Expression.Parameter(typeof(TSource), "e");
var bindings =
this.members.Select (
member =>
return Expression.ElementInit(
addMethod,
Expression.Constant(mem.Name),
Expression.Convert(Expression.Property(paramX, member), typeof(object))
);
)
var listInit = Expression.ListInit(
Expression.New(typeof(TResult)),
bindings
);
return Expression.Lambda<Func<TSource, Dictionary<string, object>>(
listInit,
paramX
);
}
I need some help here. I am trying to make a portion of an EF Query reusable.
var query = from sr in SomeEFRepository.SelectAll()
select new {
KeyValuePivotField1 = (from kvd in sr.KeyValueData
where kvd.KeyName == "FieldName1"
select kvd.Value).FirstOrDefault(),
KeyValuePivotField2 = (from kvd in sr.KeyValueData
where kvd.KeyName == "FieldName2"
select kvd.Value).FirstOrDefault(),
KeyValuePivotField3 = (from kvd in sr.KeyValueData
where kvd.KeyName == "FieldName3"
select kvd.Value).FirstOrDefault()
}
If you look you can see that I am constantly repeating myself with the following code.
(from kvd in sr.KeyValueData
where kvd.KeyName == "SomeFieldName"
select kvd.Value).FirstOrDefault(),
How can I make a method that EF will recognize in order to do something like this?
var query = from sr in SomeEFRepository.SelectAll()
select new {
KeyValuePivotField1 = GetFieldFromKeyValue("FieldName1"),
KeyValuePivotField2 = GetFieldFromKeyValue("FieldName2"),
KeyValuePivotField3 = GetFieldFromKeyValue("FieldName3"),
}
Any suggestions will help. I have been reading about Expression Trees but not sure if
that would be a good approach or even possible.
Note: I am using EF 5.0 with DBContext.
Extension methods seem like a good fit for this. I don't know exactly what you are doing so this is a example
Customer class
public class Customer{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return string.Format("Customer is {0} {1}", this.FirstName, this.LastName);
}
}
CustomerExtensionMethod
//Class MUST be static
public static class CustomerExtensionMethods
{
//Method MUST be static and use this keyword to be an extension method
public static Customer GetByFirstName(this IEnumerable<Customer> source, string value)
{
return source.Where(c => c.FirstName == value).FirstOrDefault();
}
}
Main program to show how it works
var customers = new List<Customer>
{
new Customer { Id = 1, FirstName = "Foo", LastName = "Bar" },
new Customer { Id = 2, FirstName = "Mark", LastName = "Whoknows" },
new Customer { Id = 3, FirstName = "Ronald", LastName = "McDonald" },
};
var userWithFirstNameOfRonald = customers.GetByFirstName("Ronald");
Console.WriteLine(userWithFirstNameOfRonald.ToString());
This should print "Customer Is Ronald McDonald". The important parts are commented. The Class that contains the method should be static and the method needs to be static. Along with that the first parameter of the method needs to be this to signal it is an extension method. This will allow you to call it on the specific type you put for the first parameter.
Found a great answer and article to my troubles.
http://www.codeproject.com/Articles/402594/Black-Art-LINQ-expressions-reuse
A nice NuGet Package LinqExpressionProjection 1.0.0 that is perfect for my problem.
http://nuget.org/packages/LinqExpressionProjection
I am using EF + RIA and unfortunately meet some problems with sorting by related entities.
For such purpose there is ESQL query that I implemented (found only this solution):
var queryESQL = string.Format(
#" select VALUE ent from SomeEntities as ent
join Attributes as ea ON ea.EntityId = ent.Id
where ea.AttributeTypeId = #typeId
order by ea.{0} {1}", columnName, descending ? "desc" : "asc");
var query = ObjectContext.CreateQuery<SomeEntity>(queryESQL, new ObjectParameter("typeId", attributeTypeId));
Tables have following structure:
<Attribute>:
int Id;
decimal DecimalColumn;
string StringColumn;
int EntityId;
int AttributeTypeId;
<SomeEntity>:
int Id;
string Name;
Is there any way to rewrite this stuff(sorting), using LINQ to Entities approach?
Here's my attempt, I can't guarantee it will work. I need to think more on how to get a dynamic column name, I'm not sure on that one. EDIT: you can use a string for the order column.
int typeId = 1115;
bool orderAscending = false;
string columnName = "StringColumn";
var query = from ent in SomeEntities
join ea in Attributes on ea.EntityId = ent.Id
where ea.AttributeTypeId = typeId;
if(orderAscending)
{
query = query.OrderBy(ea => columnName).Select(ea => ea.Value);
}
else
{
query = query.OrderByDescending(ea => columnName).Select(ea => ea.Value);
}
var results = query.ToList(); // call toList or enumerate to execute the query, since LINQ has deferred execution.
EDIT: I think that ordering after the select stops is from ordering by. I moved the select statement to after the order by. I also added the "query =", but I'm not sure if that is needed. I don't have a way to test this at the moment.
EDIT 3: I fired up LINQPad today and made a few tweaks to what I had before. I modeled your data in a Code-first approach to using EF and it should be close to what you have.
This approach works better if you're just trying to get a list of Attributes (which you aren't). To get around that I added an Entity property to the MyAttribute class.
This code works in LINQPAD.
void Main()
{
// add test entities as needed. I'm assuming you have an Attibutes collection on your Entity based on your tables.
List<MyEntity> SomeEntities = new List<MyEntity>();
MyEntity e1 = new MyEntity();
MyAttribute a1 = new MyAttribute(){ StringColumn="One", DecimalColumn=25.6M, Id=1, EntityId=1, AttributeTypeId = 1, Entity=e1 };
e1.Attributes.Add(a1);
e1.Id = 1;
e1.Name= "E1";
SomeEntities.Add(e1);
MyEntity e2 = new MyEntity();
MyAttribute a2 = new MyAttribute(){ StringColumn="Two", DecimalColumn=198.7M, Id=2, EntityId=2, AttributeTypeId = 1, Entity=e2 };
e2.Attributes.Add(a2);
e2.Id = 2;
e2.Name = "E2";
SomeEntities.Add(e2);
MyEntity e3 = new MyEntity();
MyAttribute a3 = new MyAttribute(){ StringColumn="Three", DecimalColumn=65.9M, Id=3, EntityId=3, AttributeTypeId = 1, Entity=e3 };
e3.Attributes.Add(a3);
e3.Id = 3;
e3.Name = "E3";
SomeEntities.Add(e3);
List<MyAttribute> attributes = new List<MyAttribute>();
attributes.Add(a1);
attributes.Add(a2);
attributes.Add(a3);
int typeId = 1;
bool orderAscending = true;
string columnName = "StringColumn";
var query = (from ent in SomeEntities
where ent.Attributes.Any(a => a.AttributeTypeId == typeId)
select ent.Attributes).SelectMany(a => a).AsQueryable();
query.Dump("Pre Ordering");
if(orderAscending)
{
// query = is needed
query = query.OrderBy(att => MyEntity.GetPropertyValue(att, columnName));
}
else
{
query = query.OrderByDescending(att => MyEntity.GetPropertyValue(att, columnName));
}
// returns a list of MyAttributes. If you need to get a list of attributes, add a MyEntity property to the MyAttribute class and populate it
var results = query.Select(att => att.Entity).ToList().Dump();
}
// Define other methods and classes here
}
class MyAttribute
{
public int Id { get; set; }
public decimal DecimalColumn { get; set; }
public string StringColumn { get; set; }
public int EntityId { get; set; }
public int AttributeTypeId { get; set; }
// having this property will require an Include in EF to return it then query, which is less effecient than the original ObjectQuery< for the question
public MyEntity Entity { get; set; }
}
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MyAttribute> Attributes { get; set; }
public MyEntity()
{
this.Attributes = new List<MyAttribute>();
}
// this could have been on any class, I stuck it here for ease of use in LINQPad
// caution reflection may be slow
public static object GetPropertyValue(object obj, string property)
{
// from Kjetil Watnedal on http://stackoverflow.com/questions/41244/dynamic-linq-orderby
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}
I have an issue. I am making an extension class for a Collection and it is generic.. like
public static class ListExtensions
{
public static ICollection<T> Search<T>(this ICollection<T> collection, string stringToSearch)
{
ICollection<T> t1=null;
foreach (T t in collection)
{
Type k = t.GetType();
PropertyInfo pi = k.GetProperty("Name");
if (pi.GetValue(t,null).Equals(stringToSearch))
{
t1.Add(t);
}
}
return t1;
}
}
But I cannot add items to t1 as it is declared null.
Error: object reference not set to an instance of the object.
I am calling the method like
List<TestClass> listTC = new List<TestClass>();
listTC.Add(new TestClass { Name = "Ishu", Age = 21 });
listTC.Add(new TestClass { Name = "Vivek", Age = 40 });
listTC.Add(new TestClass { Name = "some one else", Age = 12 });
listTC.Search("Ishu");
And the test class is
public class TestClass
{
public string Name { get; set; }
public int Age { get; set; }
}
Using : (C#3.0) & Framework - 3.5
Thanks
As you probably don't want to manipulate (add, remove,...) the Search results after you performed a Search it is better practice to return IEnumerable<T> instead of ICollection<T>. Also C# has a special syntax for this: yield
public static class ListExtensions
{
public static IEnumerable<T> Search<T>(this ICollection<T> collection, string stringToSearch)
{
foreach (T t in collection)
{
Type k = t.GetType();
PropertyInfo pi = k.GetProperty("Name");
if (pi.GetValue(t,null).Equals(stringToSearch))
{
yield return t;
}
}
}
}
Well what kind of collection do you want to use? You've got to have an actual collection to add your results to. List<T> is probably the simplest suggestion. Just change the first line of the method:
ICollection<T> t1 = new List<T>();
EDIT: Although this is the simplest change to the code, you should definitely consider using an iterator block as per Thomas's answer.