I have a RepositoryBase class where I define basic crud methods for my Entity Framework Context. I have these two overloads of the All() method:
public virtual IQueryable<T> All<TKey>(Expression<Func<T, bool>> predicate)
{
return All().Where(predicate);
}
public virtual PagedResult<T> All<TKey>(int startRowIndex, int maximumRows,
Expression<Func<T, TKey>> orderingKey, Expression<Func<T, bool>> predicate,
bool sortDescending = false)
{
var subset = All().Where(predicate);
IEnumerable<T> result = sortDescending
? subset.OrderByDescending(orderingKey).Skip(startRowIndex).Take(maximumRows)
: subset.OrderBy(orderingKey).Skip(startRowIndex).Take(maximumRows);
//More code ommited
}
The first method always needs me to explicitly specify the entity type, but the second doesn't. Why is this?
Example, this doesn't compile:
return All(s => s.LoanApplicationId == loanApplicationId)
And instead I must call it like this:
return All<LoanApplication>(s => s.LoanApplicationId == loanApplicationId)
But this DOES compile:
return All(0,10, s => s.Name, s => s.LoanApplicationId == loanApplicationId, false)
TKey is in the parameter list of the second (via Expression<Func<T, TKey>> orderingKey) and not the first. That supplies enough for the second to successfully infer the type when you use it with your supplied arguments (s => s.Name). You don't give yourself that luxury in the first version, so the compiler forces you to fill in the details by supplying the type parameter explicitly.
And from the looks of it, you don't need TKey in the first anyway, so possibly get rid of it (unless there is more code visible than that relatively simple implementation). And I don't think it means what your sample invocation thinks it means. TKey in the second is likely string (whatever the type of s.Name is), for example.
Related
In a standard class if i wanted to include ProductImages table for all the products as part f my query, i could write something like
from prods in dataContext.Products.Include(i => i.ProductImages) select prods;
I now want to turn this into a generic method so no matter what source it is, it would be able to include the table i require. I started off with a generic method (leaving out the class declaration for brevity T is declared as part of the class i.e. Public Class<T>):
public IEnumerable<T> GetAll()
{
return _entities;
}
This works with no matter what source i have whether it be a product table or customer table i can pass in the table and it returns that object (so T can be a Product or Customer). I now want to introduce the Include functionality.
I start off by looking at the source code for Include which is
public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path)
{
Check.NotNull(source, "source");
Check.NotNull(path, "path");
if (!DbHelpers.TryParsePath(path.Body, out var path2) || path2 == null)
{
throw new ArgumentException(Strings.DbExtensions_InvalidIncludePathExpression, "path");
}
return source.Include(path2);
}
however i noticed it has an additional TProperty which i dont know where or how i should implement this into my generic method?
I attempted to do the following based on the source
IEnumerable<T> GetAll(Expression<Func<T>> path);
but again i dont know how to configure/introduce the TProperty (or equivalent) in this fashion so it works like my first example?
The methods System.Linq.Queryable.OrderBy, .OrderByDescending, .ThenBy, and .ThenByDescending have a parameter "keySelector" with two type parameters: TSource and TKey.
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
I would like to create the keySelector parameter beforehand and then pass it on to the aforementioned four methods.
Imagine I have an entity class MyEntity with three properties, e.g. a string, an integer and a boolean (and some entity types that inherit MyEntity). I would like to create the keySelector parameter for the sorting methods through another method.
private static Expression<Func<TSource, TKey>> GetExpression<TSource, TKey>(string propertyName) where TSource : MyEntity
{
return propertyName switch
{
"propa" => entity => entity.MyPropertyA,
"propb" => entity => entity.MyPropertyB,
"propc" => entity => entity.MyPropertyC,
_ => throw new Exception("Unknown property: " + propertyName)
}
}
Now I'm getting two errors:
CS0029 - Cannot implicitly convert type 'string' to 'TKey'
CS1662 - Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type
CS1662 can be resolved by returning entity => (TKey)entity.MyPropertyA, but then CS0029 logically remains.
Is there any way to build the keySelector parameter in advance? Because currently I am forced to repeat that switch expression four times. (And there are actually nine properties.)
Solving the problem from a different angle because what's asked for in the title of the question isn't really doable:
Is there any way to build the keySelector parameter in advance? Because currently I am forced to repeat that switch expression four times. (And there are actually nine properties.)
You can reduce the repetition by a) Applying a silly OrderBy expression first so that you're always working with a IOrderedQueryable<T> and b) Supply your own extension methods that decides which ThenXxx method to chain based on a boolean parameter1:
public static IOrderedQueryable<TSource> ThenBy<TSource, TKey>(
this IOrderedQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
bool descending = false)
{
if(descending) return source.ThenByDescending(keySelector);
return source.ThenBy(keySelector);
}
Then in your calling code, you have
var myUnorderedQuery = ...
var mySortedQuery = myUnsortedQuery.OrderBy(x=>1);
foreach(<sort item to apply>)
{
bool descending = <logic>
switch (propertyName)
{
case "propa":
mySortedQuery = mySortedQuery.ThenBy(entity=>entity.PropertyA, descending);
break;
...
}
}
And you only have that switch appearing once in your code now.
If this was IEnumerable<T> instead, it would be even easier since the moral equivalent of the above extension method is built-in as CreatedOrderedEnumerable, albeit in that case you have to supply a comparer or know to default that to Comparer<T>.Default.
1The alternative is an OrderBy extension method that accepts IQueryable<T>, checks whether it's in fact IOrderedQueryable<T> and uses that to select whether it uses OrderBy or ThenBy on it. But I find that approach a bit more "magic" and a pain if you want to override the order of something that turns out already to be IOrderedQueryable<T>.
If you use object as the common key type (SQL Translation will not use the type for sorting) and specify the TSource type explicitly, you can do:
public static Expression<Func<MyEntity, Object>> GetExpression(string propName) {
switch (propName) {
case "propa":
return (MyEntity s) => s.MyPropertyA;
case "propb":
return (MyEntity s) => s.MyPropertyB;
case "propc":
return (MyEntity s) => s.MyPropertyC;
default:
throw new Exception($"Unrecognized property: {propName}");
}
}
I have a method as part of a generic class for use in the context of querying for a collection of objects where a boolean clause in an expression is met, and the result is ordered by some property on the object type.
The current method signature:
Task<T> GetFirstWhere(Expression<Func<T, bool>> whereExp, Expression<Func<T, DateTime>> orderByExp)
With an example implementation:
public async Task<T> GetFirstWhere(Expression<Func<T, bool>> whereExp, Expression<Func<T, DateTime>> orderByExp) {
return await _entities.Where(whereExp)
.OrderByDescending(orderByExp) // I would like to use any valid orderByExp type here
.FirstOrDefaultAsync();
}
For use in scenarios like:
var foundArticleTag = await _tags.GetFirstWhere(
tag => tag.Name == articleTag,
tag => tag.CreatedOn);
I would like orderByExp function to use any valid type of property on T, rather than explicitly DateTime. I would prefer not to make the type dynamic, so that only valid types of properties on T might be used. I suppose reflection must be required for this, though I am not sure how to enforce the type constraint.
It seems that your method is part of generic type of T, you can make your method generic also (accepting another generic type parameter for ordering expression):
Task<T> GetFirstWhere<TOrder>(
Expression<Func<T, bool>> whereExp,
Expression<Func<T, TOrder>> orderByExp);
That will require adding the generic parameter to the example implementation:
public async Task<T> GetFirstWhere<TOrder>(Expression<Func<T, bool>> whereExp, Expression<Func<T, TOrder>> orderByExp) {
return await _entities.Where(whereExp)
.OrderByDescending(orderByExp) // I would like to use any valid orderByExp type here
.FirstOrDefaultAsync();
}
And usage should remain untouched, cause compiler should be able to infer generic TOrder type parameter from the usage.
You can use as many as generic parameters as you want, so this can be easily extended for a second generic:
public async Task<T> GetFirstWhere<T, O>(Expression<Func<T, bool>> whereExp, Expression<Func<T, O>> orderByExp) {
return await _entities.Where(whereExp)
.OrderByDescending(orderByExp)
.FirstOrDefaultAsync();
}
I need to implement an API based on extensions methods (i.e. I have to use a static non-generic class). API should work smoothly with LINQ fluent API and mostly with IQueryable arguments. Like this:
public static class SomeExtensions
{
public static IQueryable<TEntity> SomeMethod<TEntity>(this IQueryable<TEntity> set, ... some arguments)
{
}
}
Now, suppose the method should take some arguments plus an Expression<Func<TEntity, TResult>> one:
public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
this IQueryable<TEntity> set,
...,
Expression<Func<TEntity, TResult>> orderByExpression)
{
}
I would like to pass the orderByExpression to OrderBy method of fluent API. Or do somethong else if orderByExpression == null.
Naturally, I'd like to have something like this:
public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
this IQueryable<TEntity> set,
...,
Expression<Func<TEntity, TResult>> orderByExpression = null)
{
}
...but when calling this method w/o optional argument I have to implicitly pass generic types, because compiler doesn't know the type of TResult.
I see some possible approaches, but I don't like them much.
Define two methods: one with this argument and one w/o, and call the first from the second. I don't like it because, actually, there are many such methods in the API, and I would have to define one additional method for every one of them.
Use Expression<Func<TEntity, object>> instead of Expression<Func<TEntity, TResult>> (it is currently so). I got rid of generic type, but there is a problem with simple (value) types like int: LINQ raises an exception when trying to cast System.Int32 to System.Object.
Maybe (haven't tried yet) I could use Expression<Func<TEntity, dynamic>> - but I don't think this is a good approach at all.
Any other ideas, anyone?
Option (1) is the best from the caller perspective. Remember the main goal for the API is to make the caller's life easier, so putting additional efforts on the implementation side should be worth enough.
Option (3) is not good. You don't want to enter complications introduced by dynamic types. And EF does not like dynamic expressions.
Option (2) actually is not so bad. So if it's what you use currently, you can stay on it. All you need to make EF happy is to convert the passed expression by removing the Convert introduced for value type properties. To do so, you can use the following helper method:
internal static IQueryable<T> ApplyOrderBy<T>(
this IQueryable<T> source,
Expression<Func<T, object>> orderByExpression = null)
{
if (orderByExpression == null) return source;
var body = orderByExpression.Body;
// Strip the Convert if any
if (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression)body).Operand;
// Create new selector
var keySelector = Expression.Lambda(body, orderByExpression.Parameters[0]);
// Here we cannot use the typed Queryable.OrderBy method because
// we don't know the TKey, so we compose a method call instead
var queryExpression = Expression.Call(
typeof(Queryable), "OrderBy", new[] { typeof(T), body.Type },
source.Expression, Expression.Quote(keySelector));
return source.Provider.CreateQuery<T>(queryExpression);
}
Here is a small test showing how the above works for different property types:
var input = new[]
{
new { Id = 2, Name = "B", ParentId = (int?)1 },
new { Id = 1, Name = "A", ParentId = (int?)null },
}.AsQueryable();
var output1 = input.ApplyOrderBy(e => e.Id).ToList();
var output2 = input.ApplyOrderBy(e => e.Name).ToList();
var output3 = input.ApplyOrderBy(e => e.ParentId).ToList();
Sample usage with your example:
public static IQueryable<TEntity> SomeMethod<TEntity>(
this IQueryable<TEntity> source,
...,
Expression<Func<TEntity, object>> orderByExpression = null)
{
var result = source;
result = preprocess(result);
result = result.ApplyOrderBy(orderByExpression);
result = postprocess(result);
return result;
}
The first option you specify is the obvious and the cleanest, though most maintenance-heavy way to do this.
Additionally, you could introduce another step in your fluent syntax. Like defining:
public interface ISortableQueryable<T> : IQueryable<T>
{
IQueryable<T> WithSorting<TResult>(Expression<Func<TEntity, TResult>> orderByExpression);
}
returning it:
public static ISortableQueryable<TEntity> SomeMethod<TEntity>(
this IQueryable<TEntity> #this, ...)
{ ... }
and providing implementation of this interface where regular IQueryable calls either redirect to the IQueryable instance it receives in constructor, or some logic is performed based on the fact whether the WithSorting method was called or not.
I have a set of Reports which I need to perform filtering on before returning the output. I would like to perform this with a single anonymous method to avoid duplicating the same code in different repositories. I'm using Entity Framework so the model types all related to the database and inherit from a base class called ReportBase.
This is what how I currently implement the filtering, each report type has to implement this method with a different context and returning a different IQueryable type.
private IQueryable<ReviewAgreement> GetFiltered(ReportFilter filter)
{
IQueryable<ReviewAgreement> reviewAgreementQueryable = Context.ReviewAgreements.Where(p => p.ClientWorkflowId == filter.ClientWorkflowId);
if (filter.AppraisalLevelId.HasValue)
{
reviewAgreementQueryable = reviewAgreementQueryable.Where(p => p.AppraisalLevelId == filter.AppraisalLevelId.Value);
}
return reviewAgreementQueryable;
}
I've been trying to implement this anonymously so I can reuse it, as in this non functional example.
public IQueryable<T> GetFiltered(ReportFilter filter)
{
IQueryable<T> reportQueryable = Context.Set<T>();
reportQueryable = reportQueryable.Where(p => p.ClientWorkflowId == filter.ClientWorkflowId);
if (filter.AppraisalLevelId.HasValue)
{
reportQueryable = reportQueryable.Where(p => p.AppraisalLevelId == filter.AppraisalLevelId.Value);
}
return reportQueryable;
}
The issue I am having is of course that the use of Where is ambiguous, so it cannot resolve p.ClientWorkflowId.
I have tried using a Func<T, TResult> delegate to pass in the filtering options this but the Where operation seems to want to return a list.
Is there actually a method I can use to achieve the effect I want?
Declare an interface that has the two ID properties that you need to perform this operation.
Ensure that your entities implement that interface.
Add a constraint to the generic argument that it implements that interface.
Note that if your base class defines both of the properties in question then you don't need an interface, and can simply constrain the type to that base class:
public IQueryable<T> GetFiltered<T>(ReportFilter filter) where T : ReportBase
{
// body unchanged
}
If you want to go down the route of accepting parameters to represent these properties than it's also possible. The first thing is that you'll need to accept expressions, not Func objects, so that the query provider can analyze them. This means changing the function signature to:
public IQueryable<T> GetFiltered<T>(ReportFilter filter,
Expression<Func<T, int>> clientIdSelector,
Expression<Func<T, int>> appraisalIdSelector)
{
Next, to turn these selectors into predicates that compare the value to an ID that we have is a bit more involved for expressions than it is for regular delegates. What we really need here is a Compose method; for delegates it's simple enough, to compose one method with another you just invoke it with the parameter being the result of the first. With expressions this means taking the body of one expression and replacing all instance of the parameter with the body of another, and then wrapping the whole thing up in a new Lambda.
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
This itself is dependent on the ability to replace all instances of one expression with another. To do that we'll need to use the following:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now that we have all of this in place we can actually compose our selectors with a comparison to the ID values of the filter:
IQueryable<T> reportQueryable = Context.Set<T>();
reportQueryable = reportQueryable
.Where(clientIdSelector.Compose(id => id == filter.ClientWorkflowId));
if (filter.AppraisalLevelId.HasValue)
{
reportQueryable = reportQueryable
.Where(clientIdSelector.Compose(id => id == filter.AppraisalLevelId.Value));
}
return reportQueryable;