In LightSwitch I have a PreprocessQuery event as follows:
partial void ValidOrders_PreprocessQuery(ref IQueryable<Order> query)
{
query = query.Where(order => OrderIsValid(order));
}
public bool OrderIsValid(Order order)
{
return true;
}
This fails with a message (on the HTMLClient side !) "method cannot be null".
But this works fine:
partial void ValidOrders_PreprocessQuery(ref IQueryable<Order> query)
{
query = query.Where(order => true);
}
Can someone see why?
Thanks,
Paul
The query provider is only shown the method OrderIsValid, and as that method has already been compiled down to IL it can no longer "look into it" to see it's implementation, as it would need to to create Expression objects to represent it.
There are a number of options that you have, ranging from inlining the method as you did yourself, or having the method itself return an expression, rather than doing the work:
public Expression<Func<Order, bool>> OrderIsValid()
{
return order => true;
}
This would let you write:
partial IQueryable<Order> ValidOrders_PreprocessQuery(IQueryable<Order> query)
{
return query.Where(OrderIsValid());
}
As a side note, I would strongly advise you to not pass the query by reference, but rather return a new query instead; that would be the more idiomatic approach.
Related
I have a list of sorts stored in this format:
public class ReportSort
{
public ListSortDirection SortDirection { get; set; }
public string Member { get; set; }
}
And I need to turn it into a lambda expression of type Action<DataSourceSortDescriptorFactory<TModel>>
So assuming I have the following collection of Report Sorts as:
new ReportSort(ListSortDirection.Ascending, "LastName"),
new ReportSort(ListSortDirection.Ascending, "FirstName"),
I would need to transform it into such a statement to be used like so:
.Sort(sort => {
sort.Add("LastName").Ascending();
sort.Add("FirstName").Ascending();
})
And the sort method signature is:
public virtual TDataSourceBuilder Sort(Action<DataSourceSortDescriptorFactory<TModel>> configurator)
So I have some method right now:
public static Action<DataSourceSortDescriptorFactory<TModel>> ToGridSortsFromReportSorts<TModel>(List<ReportSort> sorts) where TModel : class
{
Action<DataSourceSortDescriptorFactory<TModel>> expression;
//stuff I don't know how to do
return expression;
}
...and I have no idea what to do here.
EDIT: Answer is:
var expression = new Action<DataSourceSortDescriptorFactory<TModel>>(x =>
{
foreach (var sort in sorts)
{
if (sort.SortDirection == System.ComponentModel.ListSortDirection.Ascending)
{
x.Add(sort.Member).Ascending();
}
else
{
x.Add(sort.Member).Descending();
}
}
});
I was thinking at first I had to dynamically build a lambda expression from scratch using the Expression class. Luckily that wasn't the case.
...and I have no idea what to do here.
Well, reason it out.
What have you got in hand? A List<ReportSort> called sorts.
What do you need? An Action<Whatever>.
You've already taken the first step: you've make a method that takes the thing you have and returns the thing you need. Great first step.
Action<DataSourceSortDescriptorFactory<TModel>> expression;
//stuff I don't know how to do
return expression;
And you've called out what you don't know how to do -- yet. This is a good technique.
Start by filling in something that compiles but doesn't work properly.
Action<DataSourceSortDescriptorFactory<TModel>> expression =
sort => {
sort.Add("LastName").Ascending();
sort.Add("FirstName").Ascending();
};
return expression;
Excellent. Now you have a compiling program which means you can run your tests and verify that if this case is expected, the test passes, and if anything else is expected, the test fails.
Now think, what have I got in hand? I've got a list of stuff, and I'm doing an Action. That means that a side effect is happening, probably involving every item on the list. So there's probably a foreach in there somewhere:
Action<DataSourceSortDescriptorFactory<TModel>> expression =
sort => {
sort.Add("LastName").Ascending();
sort.Add("FirstName").Ascending();
foreach(var sort in sorts) {
// Do something
}
};
return expression;
Compile it. It fails. Ah, we have confused the sort we are adding to with the new sort we are adding. Fix the problem.
Action<DataSourceSortDescriptorFactory<TModel>> expression =
existingSort => {
existingSort.Add("LastName").Ascending();
existingSort.Add("FirstName").Ascending();
foreach(var newSort in sorts) {
// Do something
}
};
return expression;
Great, now again we are in a position to compile and run tests.
The pattern here should be clear. Keep it compiling, keep running tests, gradually make your program more and more correct, reason about the operations you can perform on the values that you have in hand.
Can you finish it off?
You could use the following lambda expression that you could assign to the Action<T> delegate. In that lambda expression, capture the List<T> variable and loop over it:
public static Action<DataSourceSortDescriptorFactory<TModel>> ToGridSortsFromReportSorts<TModel>(List<ReportSort> sorts) where TModel : class
{
Action<DataSourceSortDescriptorFactory<TModel>> expression =
result =>
{
foreach (var sort in sorts)
{
if (sort.SortDirection == ListSortDirection.Ascending)
result.Add(sort.Member).Ascending();
else // or whatever other methods you want to handle here
result.Add(sort.Member).Descending();
}
};
return expression;
}
I've been using LinqKit to create generic queries for quite some time.
One thing that has always bothered me is the fact that you always have to test whether the value sent in the filter is valid.
For example: Suppose I have a string filter. Conditions can be Equal, StartsWith, EndsWith and Contains.
My method would look something like this:
public List<MyModel> Get(MyModelFilter filter)
{
if (string.IsNullOrEmpty(filter.prop))
{
predicate = predicate.And(_myModel => myModel.Prop.Contains(filter.prop));
}
// Plus a giant amount of if's with multiple filters
return DbSet.AsExpandable()
.Where(predicate)
.ToList();
}
To end this bunch of If's, I decided to create a generic method to apply the filter to the properties.
My idea is to pass the property where the filter will be applied, and the filter definition, and encapsulate the Expression creation logic
It would be something of the type:
public List<MyModel> Get(MyModelFilter filter)
{
predicate = predicate.And(_myModel => myModel.Prop, filter.PropFilterDefinition);
// Goodnye If's, Only others filter impl
return DbSet.AsExpandable()
.Where(predicate)
.ToList();
}
For this, I've created some extension methods to handle this
public static Expression<Func<TPredicate, bool>> And<TPredicate>(
this ExpressionStarter<TPredicate> predicate,
Func<TPredicate, string> property, StringFilterDefinition filter,
bool ignoreNull = true)
{
if (InvalidStringFilter(filter, ignoreNull))
{
return predicate;
}
// This is LinqKit's And Extension Method
return predicate.And(BuildPredicate(property, filter));
}
private static Expression<Func<TPredicate, bool>> BuildPredicate<TPredicate>(
Func<TPredicate, string> property,
StringFilterDefinition filter)
{
if (filter.Filter == StringFilterComparators.Equal)
{
return x => property.Invoke(x) == filter.Value;
}
if (filter.Filter == StringFilterComparators.BeginsWith)
{
return x => property.Invoke(x).StartsWith(filter.Value);
}
if (filter.Filter == StringFilterComparators.EndsWith)
{
return x => property.Invoke(x).EndsWith(filter.Value);
}
return x => property.Invoke(x).Contains(filter.Value);
}
private static bool InvalidStringFilter(
StringFilterDefinition filter,
bool ignoreNullValue = true)
{
if (filter?.Filter == null)
{
return true;
}
return ignoreNullValue && string.IsNullOrEmpty(filter.Value);
}
The problem is that the filter is not applied, and the answer is in Invoke right up there. EF can not translate the above expression to SQL.
The EF error is
Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryCompilationContextFactory[8]
The LINQ expression '(__property_0.Invoke([x]) == __filter_Value_1)'
could not be translated and will be evaluated locally. To configure
this warning use the DbContextOptionsBuilder.ConfigureWarnings API
(event id 'RelationalEventId.QueryClientEvaluationWarning').
ConfigureWarnings can be used when overriding the
DbContext.OnConfiguring method or using AddDbContext on the
application service provider.
The question is:
How can I make this construction work?
Also, any suggestions on how best this?
You seem to forgot that besides the PredicateBuilder, the really useful feature provided by LINQKit AsExpandable, Expand and Invoke custom extension methods is to be able to correctly embed expressions inside the expression tree.
In order to utilize that feature, you should use Expression<Func<...>> instead of Func<...>. In the posted code, replace all occurrences of Func<TPredicate, string> with Expression<Func<TPredicate, string>> and the issue should be solved.
I have the following:
public IQueryable<T> GetQueryable()
{
var results = _repository.Table;
if (typeof(IStoreScopedEntity).IsAssignableFrom(typeof(T)))
{
results = results.Where(e => ((IStoreScopedEntity)e).Stores.Select(s => s.Id).Contains(EngineContext.Current.StoreScopeId));
}
return results;
}
and am getting the error in the post title. I know I could call ToList() on the entities so they are retrieved from the database and then cast but I'd like to avoid bring back data from the database when I don't need it.
Is there any way I can get this to work without loading the entire list of items from the database into memory and then selecting?
A bit tricky, but doable.
First we need a helper generic constrained function. Since from your definition looks like the GetQueryable function is part of a generic class, let put the helper function in a separate class
public static class StoreScopedEntity
{
public static Expression<Func<T, bool>> IdPredicate<T>(int id)
where T : IStoreScopedEntity
{
return e => e.Stores.Select(s => s.Id).Contains(id);
}
}
I assume StoreScopeId is of type int, but you can change it to actual type if it's different.
Now the only remaining is how to call that function. There are several ways of doing that, here I'll use pure reflection
public IQueryable<T> GetQueryable()
{
var results = _repository.Table;
if (typeof(IStoreScopedEntity).IsAssignableFrom(typeof(T)))
{
results = results.Where((Expression<Func<T, bool>>)
typeof(StoreScopedEntity)
.GetMethod("IdPredicate", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(typeof(T))
.Invoke(null, new object[] { EngineContext.Current.StoreScopeId }));
}
return results;
}
I'm looking to implement a cache so that I check to see if specific queries have been executed already, and if they have, return the data using the in memory cache. Rather than implement the cache itself, I can make use of the in memory view that EntityFramework exposes through the Local property of the DBSet.
So, I want to pass the entire query to a method that will run it against the database and/or the in memory cache. However, I'm getting my Expression/Func/IQueryable mixed up and can't see out how to pass the query/expression:
So the base class would be something like this (this obviously isn't working yet...):
public abstract class ServiceBase<TEntity> where TEntity : class
{
Entities _context; //instantiated in the constructor
protected List<TEntity> GetData<TEntity>(Expression<Func<TEntity, TEntity>> query)
{
if (!HasQueryExecuted(query))
{
_context.Set<TEntity>().Select(query).Load();
AddQueryToExecutedList(query);
}
return _context.Set<TEntity>().Local.AsQueryable().Select(query).ToList();
}
}
And I would have multiple classes that define their queries something like this:
public class CustomersService : ServiceBase<Customer>
{
public List<Customer> GetCustomersWithOrders()
{
return GetData(c => c.Where(c => c.OrderCount > 0));
}
public List<Customer> GetLargestCustomersByOrder(int TopCount)
{
return GetData(c => c.OrderBy(c=>c.OrderCount).Take(TopCount));
}
}
public class ProductsService : ServiceBase<Product>
{
public List<Customer> GetAllProducts()
{
return GetData(p => p);
}
public List<Customer> GetMostOrderedProducts(int minimumOrders)
{
return GetData(p => p.Where(p=> p.OrderCount > minimumOrders)
.OrderByDescending(p=>p.OrderCount));
}
public List<Customer> GetAllProducts()
{
return GetData(p => p);
}
}
These are just contrived examples, but the point is the queries could be varied, not just limited to a where clause but making use of all the standard extension methods on IQueryable to query the underlying data.
First of all, is this possible? Can the GetData method define a parameter that can take any query? With the above example, I declared the query parameter to accept the same type as specified for the IQueryable extension Select method, but when I try and call it with a simple Where clause or OrderBy clause, I get various compilation errors such as :
Cannot implicitly convert type 'bool' to 'Entities.Customer
or
Cannot implicitly convert type 'System.Linq.IOrderedEnumerable' to 'Entities.Customer'
And yet, it compiles just fine if I run the same query directly against the context. So what am I doing wrong?
Your issue is this Expression<Func<TEntity, TEntity>> query
Simply put you are saying that your expression is a function type that takes a TEntity and returns a TEntity. Since your class is typed with Entities.Customer it will expect a function that takes an Entities.Customer and returns an Entities.Customer.
If you look at your services classes p => p will work fine but p => p.Where() will not because Where returns an IEnumerable.
I think you should take a different approach and take advantage of lazy-loading.
public IQueryable<TEntity> GetData<TEntity>() where TEntity : class
{
return _context.Set<TEntity>();
}
public IQueryable<Customer> GetAllProducts()
{
return GetData();
}
public IQueryable<Customer> GetMostOrderedProducts(int minimumOrders)
{
return GetData().Where(p => p.OrderCount > minimumOrders)
.OrderByDescending(p=>p.OrderCount));
}
You don't need to build the query into GetData because the query can be built at any point up until it's evaluated. If you really wanted you could return List<T> from here but you shouldn't really have to.
Otherwise this should do the trick for your current code.
protected List<TEntity> GetData<TEntity>(Func<IEnumerable<TEntity>,
IEnumerable<TEntity>> query) where TEntity : class
{
if (!HasQueryExecuted(query))
{
AddQueryToExecutedList(query);
return query(_context.Set<TEntity>()).ToList();
}
return query(_context.Set<TEntity>().Local).ToList();
}
I have
public IQueryable<Guid> AccessibleCities
{
get
{
return CityRepository
.FindAll(a => <CONDITIONS>);
}
}
CityRepository.FindAll is implemented as:
public virtual IQueryable<TLookup> FindAll(Expression<Func<TLookup, bool>> predicate)
{
return DataContext.GetSet<TLookup>().Where(predicate);
}
And I call this
anotherRepository
.FindAll(a => AccessibleCities.Any(b => ANOTHER CONDITION));
When I call the last one, it generates two queries instead of adding AccessibleCities as query.
Please help me :)
Your final query just doesn't work that way; it doesn't concatenate by default.
Try the PredicateBuilder class. That looks like it should work for what you're trying to achieve.