EF6 OrderBy Generic Expression in abstract class - c#

I have been reading posts about Expression<Func<TModel,TResult>> for about an hour and I really do not understand this. My apologizes, but I just don't.
I have an issue that I have a abstract class that has a call to EF6 where I need to order by some property that I would like to define in the child class. That said I will add an example below.
public abstract MyController<TModel>:ApiController
{
protected IRepository<TModel> Repository {get;}
protected MyController(IRepository<TModel> repo)
{
Repository = repo;
}
protected Expression<Func<TModel,TResult>> OrderBy {get; set}
public IHttpActionResult GetItems()
{
return Ok(Repository.Get().OrderBy(x=>OrderBy(x)).ToList()); //with lots of other cool stuff.
}
}
public PersonController:MyController<Person>
{
public PersonControler(IRepository<Person> repo):base(repo)
{
OrderBy = //I need help here
}
}
Okay, so after all of this I have been reading about Expressions and I do not understand why OrderBy = (person)=> person.LastName will not work here. Can someone please explain how the Expression works and how to make this work?

Declare your property as
protected Expression<Func<TModel, object>> OrderBy { get; set; }
Assign it in PersonController
OrderBy = p => p.LastName
And use it like
Repository.Get().OrderBy(OrderBy).ToList()

Under the hood (of EF/LINQ query translation engine) Expression (actually all methods call chain) is translated to SQL query. There is expression visitor (see pattern 'visitor'), which does the job.
To get things work:
method Get of IRepository should return type DbSet<TModel> (for EF) or Table<TModel> (for LINQ to SQL)
type of ordering key should be defined either in abstract class (public abstract MyController<TModel, TKey> : ApiController) or in expression directly (protected Expression<Func<TModel, int>> OrderBy {get; set})

Related

Re-implementation of OrderBy, ThenBy and Null TypeMapping in Sql Tree error

I am trying to implement OrderBy and ThenBy in a different way to hide lambda expression from OrderBy and ThenBy extension methods. These extension methods accept classes which implement IOrderSpecification:
public class PersonOrderByAgeSpecification : OrderSpecification<Person>
{
public PersonOrderByAgeSpecification(Sort direction= Sort.Ascending) : base(direction)
{
}
public override Expression<Func<Person, IComparable>> AsExpression()
{
return personOrder => personOrder.Age;
}
}
And the usage:
var orderSpecification = new PersonOrderByAgeSpecification(Sort.Ascending);
var sortedPeople= _dbContext.People.OrderBy(orderSpecification);
It works fine when the property type in AsExpression() is just string. For example:
public override Expression<Func<Person, IComparable>> AsExpression()
{
return personOrder => personOrder.FirstName;
}
Otherwise I would get this error: (Does not work with integer or bool)
InvalidOperationException: Null TypeMapping in Sql Tree
Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.RelationalSqlTranslatingExpressionVisitor+SqlTypeMappingVerifyingExpressionVisitor.VisitExtension(Expression
node)
The source code is available here
I appreciate any help.
First off, you are using preview (beta) software, which is expected to have issues.
But the main problem is that LINQ ordering methods have second generic type argument TKey, which you are hiding behind IComparable, which for value types causes a hidden cast inside the expression.
Apart from unnecessary boxing, this is not a problem for LINQ to Objects provider because it simply compiles and executes a delegate from the lambda expression. However other IQueryable providers usually need to translate the expression to something else (usually SQL). Most of them identify such casts (Expression.Convert) and remove them during the processing. Apparently EF 3.0 preview you are using doesn't, hence the exception.
You can avoid such issues by eliminating the hidden casts yourself. It's possible to do that with expression manipulation, but the easiest is to introduce the second generic type argument to your base abstract class:
public abstract class OrderSpecification<T, TKey> : IOrderSpecification<T>
and change the abstract method signature to
public abstract Expression<Func<T, TKey>> AsExpression();
The implementation, interface and everything else except the concrete classes will remain as is.
Now all you need is to specify the actual key type in the inherited class and change the AsExpression override signature. For instance:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class PersonAgeOrderSpecification : OrderSpecification<Person, int>
{
public PersonAgeOrderSpecification(Sort direction) : base(direction) { }
public override Expression<Func<Person, int>> AsExpression()
{
return person => person.Age;
}
}
and everything will be fine.

Searching for records in several tables using EF TPT

I have a project with TPT inheritance mapping, now I need to add it a search functionality that will find records in several tables. That's what I currently have:
public abstract class Customer
{
public int Id { get; set; }
public string Memo { get; set; }
...
}
public class Person : Customer
{
public string GivenName { get; set; }
public string Surname { get; set; }
...
}
public class Company : Customer
{
public string Name { get; set; }
...
}
I also have a unit of work with bunch of repositories, and I need to add the filtering functionality to several methods of CustomerRepository. Let's say I have a Count method with the following signature
public int Count(System.Linq.Expressions.Expression<Func<Customer, bool>> filter = null)
Now I need to get the quantity of customers whose GiveName or Surname contains searchTerm in case the customer is a Person or the same searchTerm in Name field in case it is a Company.
TL;DR
How a view with a single, searchable, paged list of Customers (containing both Person and Company types) should be implemented? I mean in terms of method with signature like public IHttpActionResult Get(string searchTerm, int pageSize, int pageNumber)...
That's what I tried:
I added to each of the classes a static method that would generate an Expression to search that specific class, that's how it looks for the Person class:
public static System.Linq.Expressions.Expression<Func<Person, bool>> GetFilter(string searchTerm)
{
if (String.IsNullOrWhiteSpace(searchTerm))
{
return null;
}
var parameterExpression = System.Linq.Expressions.Expression.Parameter(typeof(Person));
System.Reflection.MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
return System.Linq.Expressions.Expression.Lambda<Func<Person, bool>>(
System.Linq.Expressions.Expression.OrElse(
System.Linq.Expressions.Expression.Call(
System.Linq.Expressions.Expression.PropertyOrField(parameterExpression, "GivenName"),
method,
System.Linq.Expressions.Expression.Constant(searchTerm, typeof(string))
),
System.Linq.Expressions.Expression.Call(
System.Linq.Expressions.Expression.PropertyOrField(parameterExpression, "Surname"),
method,
System.Linq.Expressions.Expression.Constant(searchTerm, typeof(string))
)
), parameterExpression);
}
And tried to build an Expression that would check the type of the customer and then make an appropriate data check, but here I stumped... That's what I have right now:
var parameterExpression = System.Linq.Expressions.Expression.Parameter(typeof(Customer));
var typeIsPerson = System.Linq.Expressions.Expression.TypeIs(parameterExpression, typeof(Person));
var typeIsCompany = System.Linq.Expressions.Expression.TypeIs(parameterExpression, typeof(Company));
var q = System.Linq.Expressions.Expression.Block(
System.Linq.Expressions.Expression.IfThen(typeIsPerson, Person.GetFilter(searchTerm)),
System.Linq.Expressions.Expression.IfThen(typeIsCompany, Company.GetFilter(searchTerm)),
System.Linq.Expressions.Expression.Constant(false));
var a = System.Linq.Expressions.Expression.Lambda<Func<Customer, bool>>(
q, parameterExpression);
Here I have two problems(at least?), first when I try to call Count, I get a very unpleasant NotSupportedException exception that says Unknown LINQ expression of type 'Block'. The second is that I don't know how to return the result of execution for each of GetFilters, I suspect that I will get false for any record since it is default value that is the last Expression in my Block...
May be I'm on a wrong track and this is something that should be done in a completely different manner?
Expression blocks are generally unsupported in LINQ to Entities. And normally you don't need them because you could build almost any expression just using C# conditional operator ? : (which maps to Expression.Condition).
But before even trying to build an expression dynamically, you need to find a EF supported construct working with TPT (and other EF inheritance models) polymorphic query. Which is not so easy because all the examples use OfType method which is only applicable when you need to filter a concrete derived entities. With some trial and error, luckily there are two supported constructs - is and as (important: as, not cast!).
So the statically built predicate expression in question could be like this:
Expression<Func<Customer, bool>> predicate = c =>
c is Person ?
((c as Person).GivenName.Contains(searchTerm) || (c as Person).Surname.Contains(searchTerm)) :
c is Company ?
(c as Company).Name.Contains(searchTerm) :
false;
(Frankly you don't want to look at the generated SQL, but it works)
Now you can build it dynamically if you wish. You already found the is expression method (Expression.TypeIs), for as operator the corresponding expression metod is Expression.TypeAs.
You do not need to do all that. Just create a generic method that you will close when you call it. Your generic method can be like this:
public static int Count<T>(Expression<Func<T, bool>> filter = null)
{
var ctx = new StackContext();
return ctx.Customers.OfType<T>().Where(filter).Count();
}
You can call that like this:
// Here you are closing the generic to be of type Person
var personsCount = Count<Person>(x => x.GivenName == "George");
// Here you are closing the generic to be of type Customer
var companyCount = Count<Company>(x => x.Name == "Test");

Use IQueryable.OrderBy for a composite key without loading all the items?

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.

Generic query method in Entity Framework

In my DB I have tables who have an attribute int DeleteState. I want a generic method to query those tables. In other words a method who does this: Context.Table.Where(x => x.DeleteState == 0).
I thought I could do this:
public static class Extensions
{
public static IQueryable<T> Exists<T>(this IQueryable<T> qry) where T : IDeletable
{
return qry.Where(x => x.DeleteState == 0);
}
}
Where IDeletable is this:
public interface IDeletable
{
int DeleteState { get; set; }
}
Now I only have to add the IDeletable in the EF model:
public partial class Table : EntityObject, IDeletable { ... }
I did this with the templating mechanism.
Unfortunately, it doesn't work :( It compiles fine, but throws at runtime:
Unable to cast the type 'Table' to type 'IDeletable'. LINQ to Entities only supports casting Entity Data Model primitive types
if I call it like that:
Context.Table.Exists();
How can I solve this problem? Could you think of a fix or a different method to achieve similar results? Thx
The problem you have is that the Entity Framework can only work with an Expression Tree. Your function executes a query directly instead of building an Expression Tree.
A simpler solution would be to add a Model Defined Function.
A model defined function can be called directly on an instance of your context.
Maybe:
public static IQueryable<T> Exists<T>(this IQueryable<T> qry)
{
return qry.Where(x => (!typeof(IDeletable).IsAssignableFrom(x.GetType()) || typeof(IDeletable).IsAssignableFrom(x.GetType()) && ((IDeletable)x).DeleteState == 0));
}
Tsss, this is the answer: Linq Entity Framework generic filter method
I forgot about the class here:
... where T : class, IDeletable
Have you tried converting your objects to IDeletable before you actually query? e.g.
public static IQueryable<T> Exists<T>(this IQueryable<T> qry)
{
return qry.Select<T, IDeletable>(x => x).Where(x => x.DeleteState == 0).Cast<T>();
}
I haven't tested this code, however, the error rings a bell and I remember I had to do something similar.

Accessing properties through Generic type parameter

I'm trying to create a generic repository for my models. Currently i've 3 different models which have no relationship between them. (Contacts, Notes, Reminders).
class Repository<T> where T:class
{
public IQueryable<T> SearchExact(string keyword)
{
//Is there a way i can make the below line generic
//return db.ContactModels.Where(i => i.Name == keyword)
//I also tried db.GetTable<T>().Where(i => i.Name == keyword)
//But the variable i doesn't have the Name property since it would know it only in the runtime
//db also has a method ITable GetTable(Type modelType) but don't think if that would help me
}
}
In MainViewModel, I call the Search method like this:
Repository<ContactModel> _contactRepository = new Repository<ContactModel>();
public void Search(string keyword)
{
var filteredList = _contactRepository.SearchExact(keyword).ToList();
}
Solution:
Finally went with Ray's Dynamic Expression solution:
public IQueryable<TModel> SearchExact(string searchKeyword, string columnName)
{
ParameterExpression param = Expression.Parameter(typeof(TModel), "i");
Expression left = Expression.Property(param, typeof(TModel).GetProperty(columnName));
Expression right = Expression.Constant(searchKeyword);
Expression expr = Expression.Equal(left, right);
}
query = db.GetTable<TModel>().Where(Expression.Lambda<Func<TModel, bool>>(expr, param));
Interface solution
If you can add an interface to your object you can use that. For example you could define:
public interface IName
{
string Name { get; }
}
Then your repository could be declared as:
class Repository<T> where T:class, IName
{
public IQueryable<T> SearchExact(string keyword)
{
return db.GetTable<T>().Where(i => i.Name == keyword);
}
}
Alternate interface solution
Alternatively you could put the "where" on your SearchExact method by using a second generic parameter:
class Repository<T> where T:class
{
public IQueryable<T> SearchExact<U>(string keyword) where U: T,IName
{
return db.GetTable<U>().Where(i => i.Name == keyword);
}
}
This allows the Repository class to be used with objects that don't implement IName, whereas the SearchExact method can only be used with objects that implement IName.
Reflection solution
If you can't add an IName-like interface to your objects, you can use reflection instead:
class Repository<T> where T:class
{
static PropertyInfo _nameProperty = typeof(T).GetProperty("Name");
public IQueryable<T> SearchExact(string keyword)
{
return db.GetTable<T>().Where(i => (string)_nameProperty.GetValue(i) == keyword);
}
}
This is slower than using an interface, but sometimes it is the only way.
More notes on interface solution and why you might use it
In your comment you mention that you can't use an interface but don't explain why. You say "Nothing in common is present in the three models. So i think making an interface out of them is not possible." From your question I understood that all three models have a "Name" property. In that case, it is possible to implement an interface on all three. Just implement the interface as shown and ", IName" to each of your three class definitions. This will give you the best performance for both local queries and SQL generation.
Even if the properties in question are not all called "Name", you can still use the nterface solution by adding a "Name" property to each and having its getter and setter access the other property.
Expression solution
If the IName solution won't work and you need the SQL conversion to work, you can do this by building your LINQ query using Expressions. This more work and is significantly less efficient for local use but will convert to SQL well. The code would be something like this:
class Repository<T> where T:Class
{
public IQueryable<T> SearchExact(string keyword,
Expression<Func<T,string>> getNameExpression)
{
var param = Expression.Parameter(typeof(T), "i");
return db.GetTable<T>().Where(
Expression.Lambda<Func<T,bool>>(
Expression.Equal(
Expression.Invoke(
Expression.Constant(getNameExpression),
param),
Expression.Constant(keyword),
param));
}
}
and it would be called thusly:
repository.SearchExact("Text To Find", i => i.Name)
Ray's method is quite good, and if you have the ability to add an interface definitely the superior however if for some reason you are unable to add an interface to these classes (Part of a class library you can't edit or something) then you could also consider passing a Func in which could tell it how to get the name.
EG:
class Repository<T>
{
public IQueryable<T> SearchExact(string keyword, Func<T, string> getSearchField)
{
return db.GetTable<T>().Where(i => getSearchField(i) == keyword);
}
}
You'd then have to call it as:
var filteredList = _contactRepository.SearchExact(keyword, cr => cr.Name).ToList();
Other than these two options you could always look into using reflection to access the Name property without any interface, but this has the downside that there's no compile-time check that makes sure the classes you're passing actually DO have a Name property and also has the side-effect that the LINQ will not be translated to SQL and the filtering will happen in .NET (Meaning the SQL server could get hit more than is needed).
You could also use a Dynamic LINQ query to achieve this SQL-side effect, but it has the same non type-safe issues listed above.

Categories