I have implemented Specific Pattern following vkhorikov/SpecificationPattern
For example, i have a product table and it has many to many relation with WarehouseProducts table. I need to find all products for a given list of wearhouses. So, this is what i have kind of
public class Products
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public ICollection<WarehouseProduct> Warehouses { get; set; }
}
public class WarehouseProduct
{
[Key]
public int Id { get; set; }
public int WarehouseId { get; set; }
[ForeignKey("ProductId")]
public int ProductId { get; set; }
}
public class WarehouseProductSpecification : Specification<Products>
{
private readonly List<int> _ids;
public WarehouseProductSpecification(IEnumerable<int> warehouseIds)
{
_ids = warehouseIds.ToList();
}
public override Expression<Func<Products, bool>> ToExpression()
{
Expression<Func<WarehouseProduct, bool>> expr =
(w) => _ids.Contains(w.WarehouseId);
return
q => !_ids.Any()
|| (_ids.Any() && q.Warehouses != null && q.Warehouses.Any(expr.Compile()));
}
}
But, when i execute I got the following error
System.NotSupportedException Cannot compare elements of type
'System.Collections.Generic.ICollection`1[[Data.TableObjects.WarehouseProduct,
Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. Only
primitive types, enumeration types and entity types are supported.
I am really struggling to create the specification for a ICollection. Is there any way to achieve that?
FYI, I am using EF6 to connect to a SQLServer database.
Updated
// To resond to the first comment..
I have use the spec on the repostiory so the following code gets error
var products = _context.Products
.Include("WarehouseProducts")
.Where(warehouseProductSpec.ToExpression())
.ToList();
so the to list gets the error
Update 2
I tried to use the code added by #Evk
if (_ids.Count == 0)
return x => true;
return q => q.Warehouses.Any(w => _ids.Contains(w.WarehouseId));
I got the following error while trying your code
Test [0:10.297] Failed: System.ArgumentException: Property 'System.String WearhouseId' is not defined for type 'Data.TableObjects.Products'
System.ArgumentException
Property 'System.String WearhouseId' is not defined for type 'Data.TableObjects.Products'
at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
at Infrastructure.Expressions.AndSpecification`1.ToExpression() in C:\..\Expressions\AndSpecification
You have to always remember that your expression is converted to SQL query by entity framework. For example, think about how this
q.Warehouses.Any(expr.Compile())
can be translated to SQL? It cannot, because result of expr.Compile() is basically .NET code - it's not an expression tree any more. You can use third party libraries, like LinqKit, to be able to integrate one expression into another, but without that it will not just work. In your specific case that is not needed though.
First you need to clean up your expression. If list of _ids is empty - you don't need to filter anything. So return expression which just returns true (matches all):
if (_ids.Count == 0)
return x => true;
Now, after if, we know that there are ids in a list, so we can skip all checks related to that. Then, you don't need to check Warehouses for null. This check is what causes exception you observe. It doesn't make sense given that this expression will be converted to SQL query, so this check can be removed. Code inside expression will never be executed directly (at least in EF6), so no null reference exceptions are possible.
That leaves us only with one expression, which actually does useful work, so final ToExpression will be:
if (_ids.Count == 0)
return x => true;
return q => q.Warehouses.Any(w => _ids.Contains(w.WarehouseId));
Related
I'm trying to make a service that returns a catalog based on the filters.
I've seen a few results on the internet, but not quite my issue. I hope you can help me with mine.
The issue is that this query build cannot be translated into a store expression:
'LINQ to Entities does not recognize the method 'System.Linq.IQueryable'1[App.Data.Models.Subgroup] HasProductsWithState[Subgroup](System.Linq.IQueryable'1[App.Data.Models.Subgroup], System.Nullable`1[System.Boolean])' method, and this method cannot be translated into a store expression.'
How can I make it so the query can be translated into a store expression.
Please don't suggest .ToList() as a answer as I don't want this to run in memory.
So what I have is:
bool? isActive = null;
string search = null;
DbSet<Maingroup> query = context.Set<Maingroup>();
var result = query.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(x => x.SortOrder)
.Select(x => new CatalogViewModel.MaingroupViewModel()
{
Maingroup = x,
Subgroups = x.Subgroups.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(y => y.SortOrder)
.Select(y => new CatalogViewModel.SubgroupViewModel()
{
Subgroup = y,
Products = y.Products.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(z => z.SortOrder)
.Select(z => new CatalogViewModel.ProductViewModel()
{
Product = z
})
})
});
return new CatalogViewModel() { Maingroups = await result.ToListAsync() };
In the code below you can see that I recursively call the extension to try and stack the expression. But when I walk through my code at runtime it does not enter the function again when
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
is called.
public static class ProductServiceExtensions
{
public static IQueryable<TEntity> HasProductsWithState<TEntity>(this IQueryable<TEntity> source, bool? state)
{
if (source is IQueryable<Maingroup> maingroups)
{
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Subgroup> subgroups)
{
return subgroups.Where(x => x.Products.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Product> products)
{
return products.Where(x => x.IsActive == state) as IQueryable<TEntity>;
}
return source;
}
public static IQueryable<TEntity> HasChildrenWithName<TEntity>(this IQueryable<TEntity> source, string search)
{
if (source is IQueryable<Maingroup> maingroups)
{
return maingroups.Where(x => search == null || x.Name.ToLower().Contains(search) || x.Subgroups.AsQueryable().HasChildrenWithName(search).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Subgroup> subgroups)
{
return subgroups.Where(x => search == null || x.Name.ToLower().Contains(search) || x.Products.AsQueryable().HasChildrenWithName(search).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Product> products)
{
return products.Where(x => search == null || x.Name.ToLower().Contains(search)) as IQueryable<TEntity>;
}
return source;
}
}
UPDATE
Missing classes:
public class Maingroup
{
public long Id { get; set; }
public string Name { get; set; }
...
public virtual ICollection<Subgroup> Subgroups { get; set; }
}
public class Subgroup
{
public long Id { get; set; }
public string Name { get; set; }
public long MaingroupId { get; set; }
public virtual Maingroup Maingroup { get; set; }
...
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public long SubgroupId { get; set; }
public virtual Subgroup Subgroup { get; set; }
...
public bool IsActive { get; set; }
}
The cause of your problem
You have to be aware between an IEnumerable and an IQueryable. An IEnumerable object has everything in it to enumerate over all the elements: you can ask for the first element of the sequence, and once you've got an element, you can ask for the next element, until there are no more elements.
An IQueryable seems similar, however, the IQueryable does not hold everything to enumerate the sequence. It holds an Expression and a Provider. The Expression is a generic form of what must be queried. The Provider knows who must execute the query (usually a database management system), how to communicate with this executor and which language to use (usually something SQL-like).
As soon as you start enumerating, either explicitly by calling GetEnumerator and MoveNext, or implicitly by calling foreach, ToList, FirstOrDefault, Count, etc, the Expression is sent to the Provider, who will translate it into SQL and call the DBMS. The returned data is presented as an IEnumerable object, which is enumerated, using GetEnumerator
Because the Provider has to translate the Expression into SQL, the Expression may only call functions that can be translated into SQL. Alas, the Provider does not know HasProductsWithState, nor any of your own defined functions, and thus can't translate it into SQL. In fact, the entity framework provider also does not know how to translate several standard LINQ functions, and thus they can't be used AsQueryable. See Supported and Unsupported LINQ methods.
So you'll have to stick to functions that return an IQueryable where the Expression contains only supported functions.
Class Description
Alas you forgot to give us your entity classes, so I'll have to make some assumptions about them.
Apparently have a DbContext with at least three DbSets: MainGroups, SubGroups and Products.
There seems to be a one-to-many (or possible many-to-many) relation between MaingGroups and SubGroups: every MainGroup has zero or more SubGroups.
It seems that there is also a one-to-many relation between SubGroups and Products: every SubGroup has zero or more Products.
Alas you forgot to mentions that return relation: does every Product belong to exactly one SubGroup (one-to-many), or does every Product belong to zero or more SubGroups (many-to-many`)?
If you've followed the entity framework code first conventions, you will have classes similar to this:
class MainGroup
{
public int Id {get; set;}
...
// every MainGroup has zero or more SubGroups (one-to-many or many-to-many)
public virtual ICollection<SubGroup> SubGroups {get; set;}
}
class SubGroup
{
public int Id {get; set;}
...
// every SubGroup has zero or more Product(one-to-many or many-to-many)
public virtual ICollection<Product> Products{get; set;}
// alas I don't know the return relation
// one-to-many: every SubGroup belongs to exactly one MainGroup using foreign key
public int MainGroupId {get; set;}
public virtual MainGroup MainGroup {get; set;}
// or every SubGroup has zero or more MainGroups:
public virtual ICollection<MainGroup> MainGroups {get; set;}
}
Something similar for Product:
class Product
{
public int Id {get; set;}
public bool? IsActive {get; set;} // might be a non-nullable property
...
// alas I don't know the return relation
// one-to-many: every Productbelongs to exactly one SubGroup using foreign key
public int SubGroupId {get; set;}
public virtual SubGroup SubGroup {get; set;}
// or every Product has zero or more SubGroups:
public virtual ICollection<SubGroup> SubGroups {get; set;}
}
And of cours your DbContext:
class MyDbContext : DbContext
{
public DbSet<MainGroup> MainGroups {get; set;}
public DbSet<SubGroup> SubGroups {get; set;}
public DbSet<Product> Products {get; set;}
}
This is all that entity framework needs to know to detect your tables, the columns in your tables and the relations between the tables (one-to-many, many-to-many, one-to-zero-or-one). Only if you want to deviate from the standard naming you'll need attributes of fluent API.
In entity framework the columns of the tables are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many).
Note that although the SubGroups of a MainGroup is declared as a collection, if you query the SubGroups of the MaingGroup with Id 10 you'll still get an IQueryable.
Requirements
Given a queryable sequence of Products and a nullable Boolean State, HasProductsWithState(products, state) should return the queryable sequence of Products that have a value of IsActive equal to State
Given a queryable sequence of SubGroups and a nullable Boolean State, HasProductsWithState(subGroups, state) should return the queryable sequence of SubGroups that have at least one Product that "HasProductsWithState(Product, State)1
Given a queryable sequence of MainGroups and a nullable Boolean State, HasProductsWithState(mainGroups, state) should return the queryable sequence of MainGroups, that contains all MainGroups that have at least one SubGroup that HasProductsWithState(SubGroup, State)
Solution
Well If you write the requirements like this, the extension methods are easy:
IQueryable<Product> WhereHasState(this IQueryable<Product> products, bool? state)
{
return products.Where(product => product.IsActive == state);
}
Because this function does not check whether a Product has this state, but returns all Product that have this state, I chose to use a different name.
bool HasAnyWithState(this IQueryable<Product> products, bool? state)
{
return products.WhereHasState(state).Any();
}
Your code will be slightly different if IsActive is a non-nullable property.
I'll do something similar with SubGroups:
IQueryable<SubGroup> WhereAnyProductHasState(this IQueryable<SubGroup> subGroups, bool? state)
{
return subgroups.Where(subGroup => subGroup.Products.HasAnyWithState(state));
}
bool HasProductsWithState(this IQueryable<SubGroup> subGroups, bool? state)
{
return subGroups.WhereAnyProductHasState(state).Any();
}
Well, you'll know the drill by now for MainGroups:
IQueryable<MainGroup> WhereAnyProductHasState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return maingroups.Where(mainGroup => mainGroup.SubGroups.HasProductsWithState(state));
}
bool HasProductsWithState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return mainGroups.WhereAnyProductHasState(state).Any();
}
If you look really closely, you'll see that I didn't use any self-defined function. My function calls will only change the Expression. The changed Expression can be translated into SQL.
I've separated the function into a lot of smaller functions, because you didn't say whether you want to use HasProductsWithState(this IQueryable<SubGroup>, bool?) and HasProductsWithState(this IQueryable<Product>, bool?).
TODO: do something similar for similar for HasChildrenWithName: separate into smaller functions that contain only LINQ functions, and nothing else
If you'll only call HasProductsWithState(this IQueryable<MainGroup>, bool?) you can do it in one function, using `SelectMany:
IQueryable<MainGroup> HasProductsWithState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return mainGroups
.Where(mainGroup => mainGroup.SelectMany(mainGroup.SubGroups)
.SelectMany(subGroup => subGroup.Products)
.Where(product => product.IsActive == state)
.Any() );
}
But when I walk through my code at runtime it does not enter the function again when
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state)
Welcome to the world of expression trees!
x => x.Subgroups.AsQueryable().HasProductsWithState(state)
is lambda expression (Expression<Func<...>) with body
x.Subgroups.AsQueryable().HasProductsWithState(state)
The body is expression tree, in other words - code as data, hence is never executed (except if compiled to delegate as in LINQ to Objects).
It's easily overlooked since visually lambda expressions look like delegates. Even Harald in their answer after all explanations that one should not use custom methods, as a solution actually provides several custom methods with the rationale "I didn't use any self-defined function. My function calls will only change the Expression. The changed Expression can be translated into SQL". Sure, but if your functions are called! Which of course does not happen when they are inside expression tree.
With that being said, there is no good general solution. What I can offer is solution for your particular problem - transforming custom methods which receive IQueryable<T> plus other simple parameters and return IQueryable<T>.
The idea is to use custom ExpressionVisitor which identifies the "calls" to such method inside expression tree, actually calls them and replaces them with the result of the call.
The problem is to call
x.Subgroups.AsQueryable().HasProductsWithState(state)
when we have no actual x object. The trick is to call them with fake queryable expression (like LINQ to Objects Enumerable<T>.Empty().AsQueryble()) and then use another expression visitor to replace the fake expression with the original expression in the result (pretty much like string.Replace, but for expressions).
Here is the sample implementation of the above:
public static class QueryTransformExtensions
{
public static IQueryable<T> TransformFilters<T>(this IQueryable<T> source)
{
var expression = new TranformVisitor().Visit(source.Expression);
if (expression == source.Expression) return source;
return source.Provider.CreateQuery<T>(expression);
}
class TranformVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.IsStatic && node.Method.Name.StartsWith("Has")
&& node.Type.IsGenericType && node.Type.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& node.Arguments.Count > 0 && node.Arguments.First().Type == node.Type)
{
var source = Visit(node.Arguments.First());
var elementType = source.Type.GetGenericArguments()[0];
var fakeQuery = EmptyQuery(elementType);
var args = node.Arguments
.Select((arg, i) => i == 0 ? fakeQuery : Evaluate(Visit(arg)))
.ToArray();
var result = (IQueryable)node.Method.Invoke(null, args);
var transformed = result.Expression.Replace(fakeQuery.Expression, source);
return Visit(transformed); // Apply recursively
}
return base.VisitMethodCall(node);
}
static IQueryable EmptyQuery(Type elementType) =>
Array.CreateInstance(elementType, 0).AsQueryable();
static object Evaluate(Expression source)
{
if (source is ConstantExpression constant)
return constant.Value;
if (source is MemberExpression member)
{
var instance = member.Expression != null ? Evaluate(member.Expression) : null;
if (member.Member is FieldInfo field)
return field.GetValue(instance);
if (member.Member is PropertyInfo property)
return property.GetValue(instance);
}
throw new NotSupportedException();
}
}
static Expression Replace(this Expression source, Expression from, Expression to) =>
new ReplaceVisitor { From = from, To = to }.Visit(source);
class ReplaceVisitor : ExpressionVisitor
{
public Expression From;
public Expression To;
public override Expression Visit(Expression node) =>
node == From ? To : base.Visit(node);
}
}
Now all you need is to call .TransformFilters() extension methods at the end of your queries, for instance in your sample
var result = query.AsQueryable()
// ...
.TransformFilters();
You can also call it on intermediate queries. Just make sure the call is outside expression tree :)
Note that the sample implementation is processing static methods having first parameter IQueryable<T>, returning IQueryable<T> and name starting with Has. The last is to skip Queryable and EF extension methods. In the real code you should use some better criteria - for instance the type of the defining class, or custom attribute etc.
I have model as below
class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
class Employee : Person
{
public string Dep { get; set; }
}
class Client : Person
{
public string Type { get; set; }
}
Now I would like to query Person by a property of Employee as follows
context.Set<Person>().Where(x => ((Employee)x).Dep == "dep").ToList();
But I get the following error
Unable to cast the type 'DomainModel.Person' to type
'DomainModel.Employee'. LINQ to Entities only supports casting EDM
primitive or enumeration types.
I know that I could simply use
context.Set<Employee>().Where(x => x.Dep == "dep").ToList();
But the problem is that I use a generic search control, that control can deal only with one type to search into, the search criteria are passed to this control as lambda expressions of that determined type and search statements are also returned by the search control as lambda expressions that then are passed as predicate to the Where method, now I would like to use this search control to search Employee and Person at the same time, and since the search control can deal with only one type I passed the parent type to it which is Person so that I can access all its children types properties in the search, but I faced the problem mentioned above. Any idea?
When it comes to EF inheritance, the cast operator is not supported in LINQ to Entities query. However, the is and as operator are perfectly supported, so the correct way of writing such filters is like this:
context.Set<Person>()
.Where(x => x is Employee && (x as Employee).Dep == "dep")
.ToList();
Having spent a long time solving this problem, I wanted to share the solution.
Background
I maintain a large web application with the primary function of managing orders. It is an MVC over C# application using EF6 for data.
There are LOTS of search screens. The search screens all have multiple parameters and return different object types.
The Problem
Every search screen had:
A ViewModel with the search parameters
A Controller method to handle the Search event
A method to pull the correct data for that screen
A method to apply all the search filters to the dataset
A method to convert the results into a NEW results ViewModel
The Results ViewModel
This adds up quickly. We have about 14 different search screens, which means about 84 models & methods to handle these searches.
My Goal
I wanted to be able to create a class, analogous to the current search parameter ViewModel, that would inherit from a base SearchQuery class such that my Controller could simply trigger the search to run to populate a Results field of the same object.
An Example of My Ideal State (Because It's a Bear To Explain)
Take the following class structure:
public class Order
{
public int TxNumber;
public Customer OrderCustomer;
public DateTime TxDate;
}
public class Customer
{
public string Name;
public Address CustomerAddress;
}
public class Address
{
public int StreetNumber;
public string StreetName;
public int ZipCode;
}
Let's assume I have lots of those records in a queryable format--an EF DBContext object, an XML object, whatever--and I want to search them. First, I create a derived class specific to my ResultType(in this case, Order).
public class OrderSearchFilter : SearchQuery
{
//this type specifies that I want my query result to be List<Order>
public OrderSearchFilter() : base(typeof(Order)) { }
[LinkedField("TxDate")]
[Comparison(ExpressionType.GreaterThanOrEqual)]
public DateTime? TransactionDateFrom { get; set; }
[LinkedField("TxDate")]
[Comparison(ExpressionType.LessThanOrEqual)]
public DateTime? TransactionDateTo { get; set; }
[LinkedField("")]
[Comparison(ExpressionType.Equal)]
public int? TxNumber { get; set; }
[LinkedField("Order.OrderCustomer.Name")]
[Comparison(ExpressionType.Equal)]
public string CustomerName { get; set; }
[LinkedField("Order.OrderCustomer.CustomerAddress.ZipCode")]
[Comparison(ExpressionType.Equal)]
public int? CustomerZip { get; set; }
}
I use attributes to specify what field/property of the target ResultType any given search field is linked to, as well as the comparison type (== < > <= >= !=). A blank LinkedField means that the name of the search field is the same as the name of the target object field.
With this configured, the only things I should need for a given search are:
A populated search object like the one above
A data source
No other scenario-specific coding should be required!
The Solution
For starters, we create:
public abstract class SearchQuery
{
public Type ResultType { get; set; }
public SearchQuery(Type searchResultType)
{
ResultType = searchResultType;
}
}
We'll also create the attributes we used above to define the search field:
protected class Comparison : Attribute
{
public ExpressionType Type;
public Comparison(ExpressionType type)
{
Type = type;
}
}
protected class LinkedField : Attribute
{
public string TargetField;
public LinkedField(string target)
{
TargetField = target;
}
}
For each search field, we'll need to know not only WHAT search is done, but also WHETHER the search is done. For example, if the value of "TxNumber" is null, we wouldn't want to run that search. So we create a SearchField object that contains, in addition to the actual search value, two expressions: one that represents performing the search, and one that validates whether the search should be applied.
private class SearchFilter<T>
{
public Expression<Func<object, bool>> ApplySearchCondition { get; set; }
public Expression<Func<T, bool>> SearchExpression { get; set; }
public object SearchValue { get; set; }
public IQueryable<T> Apply(IQueryable<T> query)
{
//if the search value meets the criteria (e.g. is not null), apply it; otherwise, just return the original query.
bool valid = ApplySearchCondition.Compile().Invoke(SearchValue);
return valid ? query.Where(SearchExpression) : query;
}
}
Once we have created all our filters, all we need to do is loop through them and call the "Apply" method on our dataset! Easy!
The next step is creating the validation expressions. We'll do this based on the Type; every int? is validated the same as every other int?.
private static Expression<Func<object, bool>> GetValidationExpression(Type type)
{
//throw exception for non-nullable types (strings are nullable, but is a reference type and thus has to be called out separately)
if (type != typeof(string) && !(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)))
throw new Exception("Non-nullable types not supported.");
//strings can't be blank, numbers can't be 0, and dates can't be minvalue
if (type == typeof(string )) return t => !string.IsNullOrWhiteSpace((string)t);
if (type == typeof(int? )) return t => t != null && (int)t >= 0;
if (type == typeof(decimal? )) return t => t != null && (decimal)t >= decimal.Zero;
if (type == typeof(DateTime?)) return t => t != null && (DateTime?)t != DateTime.MinValue;
//everything else just can't be null
return t => t != null;
}
This was all I needed for my application, but there is definitely more validation that could be done.
The search expression is slightly more complicated and required a parser to "De-qualify" Field/Property names (there's probably a better word, but if so, I don't know it). Basically, if I specified "Order.Customer.Name" as a linked field and I'm searching through Orders, I need to turn that into "Customer.Name" because there is no Order Field inside an Order object. Or at least I hope not. :) This isn't certain, but I considered it better to accept and correct fully-qualified object names than to support that edge case.
public static List<string> DeQualifyFieldName(string targetField, Type targetType)
{
var r = targetField.Split('.').ToList();
foreach (var p in targetType.Name.Split('.'))
if (r.First() == p) r.RemoveAt(0);
return r;
}
This is just straight text parsing, and returns the Field name in "levels" (e.g. "Customer"|"Name").
All right, let's get our search expression together.
private Expression<Func<T, bool>> GetSearchExpression<T>(
string targetField, ExpressionType comparison, object value)
{
//get the property or field of the target object (ResultType)
//which will contain the value to be checked
var param = Expression.Parameter(ResultType, "t");
Expression left = null;
foreach (var part in DeQualifyFieldName(targetField, ResultType))
left = Expression.PropertyOrField(left == null ? param : left, part);
//Get the value against which the property/field will be compared
var right = Expression.Constant(value);
//join the expressions with the specified operator
var binaryExpression = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(binaryExpression, param);
}
Not so bad! What we're trying to create is, for example:
t => t.Customer.Name == "Searched Name"
Where t is our ReturnType--an Order, in this case. First we create the parameter, t. Then, we loop through the parts of the property/field name until we have the full title of the object we're targeting (naming it "left" because it's the left side of our comparison). The "right" side of our comparison is simple: the constant provided by the user.
Then we create the binary expression and turn it into a lambda. Easy as falling off a log! If falling off a log required countless hours of frustration and failed methodologies, anyway. But I digress.
We've got all the pieces now; all we need is a method to assemble our query:
protected IQueryable<T> ApplyFilters<T>(IQueryable<T> data)
{
if (data == null) return null;
IQueryable<T> retVal = data.AsQueryable();
//get all the fields and properties that have search attributes specified
var fields = GetType().GetFields().Cast<MemberInfo>()
.Concat(GetType().GetProperties())
.Where(f => f.GetCustomAttribute(typeof(LinkedField)) != null)
.Where(f => f.GetCustomAttribute(typeof(Comparison)) != null);
//loop through them and generate expressions for validation and searching
try
{
foreach (var f in fields)
{
var value = f.MemberType == MemberTypes.Property ? ((PropertyInfo)f).GetValue(this) : ((FieldInfo)f).GetValue(this);
if (value == null) continue;
Type t = f.MemberType == MemberTypes.Property ? ((PropertyInfo)f).PropertyType : ((FieldInfo)f).FieldType;
retVal = new SearchFilter<T>
{
SearchValue = value,
ApplySearchCondition = GetValidationExpression(t),
SearchExpression = GetSearchExpression<T>(GetTargetField(f), ((Comparison)f.GetCustomAttribute(typeof(Comparison))).Type, value)
}.Apply(retVal); //once the expressions are generated, go ahead and (try to) apply it
}
}
catch (Exception ex) { throw (ErrorInfo = ex); }
return retVal;
}
Basically, we just grab a list of fields/properties in the derived class (that are linked), create a SearchFilter object from them, and apply them.
Clean-Up
There's a bit more, of course. For example, we're specifying object links with strings. What if there's a typo?
In my case, I have the class check whenever it spins up an instance of a derived class, like this:
private bool ValidateLinkedField(string fieldName)
{
//loop through the "levels" (e.g. Order / Customer / Name) validating that the fields/properties all exist
Type currentType = ResultType;
foreach (string currentLevel in DeQualifyFieldName(fieldName, ResultType))
{
MemberInfo match = (MemberInfo)currentType.GetField(currentLevel) ?? currentType.GetProperty(currentLevel);
if (match == null) return false;
currentType = match.MemberType == MemberTypes.Property ? ((PropertyInfo)match).PropertyType
: ((FieldInfo)match).FieldType;
}
return true; //if we checked all levels and found matches, exit
}
The rest is all implementation minutia. If you're interested in checking it out, a project that includes a full implementation, including test data, is here. It's a VS 2015 project, but if that's an issue, just grab the Program.cs and Search.cs files and throw them into a new project in your IDE of choice.
Thanks to everyone on StackOverflow who asked the questions and wrote the answers that helped me put this together!
I have problems designing LINQ query for EntityFramework that combines multiple conditions on attached entities with OR condition.
My classes (simplified):
public class EventMessage : EntityBase
{
public IList<EventParameter> Parameters { get; set; }
}
public class EventParameter : EntityBase
{
public string Name { get; set; }
public string Value { get; set; }
}
The goal is to obtain EventMessages which have at least one EventParameter which Value and Name equals one of the passed arguments (passed to query below by QueryParameters of type IList<EventParameter>). The problems is that I want my query to be dynamic, ie. it would be able to generate condition independently on QueryParameters.Count()
I have tried following approaches:
//Throws NotSupportedException when generating SQL
//Message: "Unable to create a constant value of type 'EventParameter'. Only primitive types or enumeration types are supported in this context."
DataContext.Queryable<EventMessage>()
.Where(em => QueryParameters.Any(qp => em.Parameters.Any(p => p.Name == qp.Name && p.Value == qp.Value)));
//Throws NotSupportedException when generating SQL
//Message: "LINQ to Entities does not recognize the method 'FilterByParameters'"
DataContext.Queryable<EventMessage>().Where(em => FilterByParameters(em, QueryParameters));
public bool FilterByParameters(EventMessage eventMessage, IList<EventParameter> queryParameters)
{
bool result = false;
foreach (EventParameter queryParam in queryParameters)
{
result |= eventMessage.Parameters.Any(x => x.Name == queryParam.Name && x.Value == queryParam.Value);
}
return result;
}
Of course, it would be no problem to design query for given number of QueryParameters - I could join OR-conditions within and expression inside .Where(), however I would like to have a query that work independently on paramemeters' count. Is it possible? Using EF 6.1.3.
You should be able to use Union.
DataContext.Queryable<EventMessage>().Where(p => p.Bla == blub).Where(p => something else)
will be an AND. For or you should be able to do something like
DataContext.Queryable<EventMessage>().Where(p => p.Bla == blub).Union(p => something else)
EDIT:
If it is not clear right away: You can combine these filters like this:
IQueryable<...> ApplyOrFilter(IQueryable<...> query, ...)
{
return query.Union(...);
}
I have come across a very confusing problem that I hope someone can help me with. In my application I have the following data structures:
public struct EntityDetails
{
public string EntityName { get; set; }
public List<AttributeDetails> Attributes { get; set; }
public bool EntityExists { get; set; }
}
public struct AttributeDetails
{
public string AttributeName { get; set; }
public string DisplayName { get; set; }
public string DataType { get; set; }
public string Description { get; set; }
public bool AttributeExists { get; set; }
}
I instantiate the object with the following:
public static List<EntityDetails> entityList { get; set; }
So, what I need to do is to be able to return a filtered list of attributes based on an entity name and an attribute name. To do this I wrote the following piece of LINQ:
public static List<AttributeDetails> GetFilteredAttributeList(string pEntityName, string pAttributeFilter)
{
return (List<AttributeDetails>)entityList.Where(e => e.EntityName == pEntityName)
.Select(e => e.Attributes
.Where (a => a.AttributeName
.Contains (pAttributeFilter)));
}
Initially, when I did this I didn't have the cast at the start, but it brought up a compile time error, so I added the cast to allow it to compile. However, when it gets to this method I get the following message:
{"Unable to cast object of type 'WhereSelectListIterator2[MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer+EntityDetails,System.Collections.Generic.IEnumerable1[MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer+AttributeDetails]]' to type 'System.Collections.Generic.List`1[MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer+AttributeDetails]'."}
Now, from the research I've done it would be appear that one is of type IEnumerable and the other is list, which I understand, but I can't for the life of me work out how to cast it so it is acceptable! I've also tried ToList(), casting it through the extension methods and various other things. I've also confirmed the data structure contains the correct data.
Any help would be appreciated.
UPDATE
Apologies, but for some reason I can't reply to answers for 8 hrs sigh. I have followed the advice of everyone to use ToList and now I get the following error:
Thanks for the answers so far. In my mind ToList() was the only logical way to go, but when I do that I get the following compile-time error:
error CS0029: Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.IEnumerable<MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer.AttributeDetails>>' to 'System.Collections.Generic.List<MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer.AttributeDetails>'
The actual error message when I hover over it is "System.ArgumentNullException".
You can simply end your LINQ query with a call to .ToList() to convert the results to a List<T>.
One thing to keep in mind is that calling .ToList() on a LINQ query "realizes" it, meaning that execution is no longer deferred;the results are now stored in memory in your List<T>.
In addition, I believe you want to use the .SelectMany() clause, instead of .Select(). e.Attributes is a List<AttributeDetails>. If you use Select(), it will create an IEnumarable<list<AttributeDetails>>, with each element being the attributes from one of your entities. SelectMany will combine the returned lists and return an IEnumerable<AttributeDetails>, which appears to be what you want.
Ultimately, you want to use the following:
return entityList.Where(e => e.EntityName == pEntityName)
.SelectMany(e => e.Attributes
.Where (a => a.AttributeName
.Contains(pAttributeFilter)))
.ToList();
Use this code:
return entityList.Where(e => e.EntityName == pEntityName)
.Select(e => e.Attributes
.Where (a => a.AttributeName
.Contains (pAttributeFilter))).ToList()
LINQ returns IEnumerable which is not a list, so you cannot cast this object to list. When you call ToList() the linq query will be executed and converted to list
You need SelectMany to pull out the attributes into a single list. You also need ToList() to convert the result to a List(). The cast will be unnecessary.
Try
return entityList.Where(e => e.EntityName == pEntityName)
.SelectMany(e => e.Attributes
.Where (a => a.AttributeName
.Contains (pAttributeFilter)))
.ToList();
Given that your Attributes are in fact instances of AttributeDetails, then you can call .ToList() at the end of your query. However, another appropriate way could be for you to change the signature of the method to return an IEnumerable<AttributeDetails>, depending on what functionality the caller is expecting.
In fact, the caller can still transform the query results at their leisure if they need to do so, where you can reliably return a further readable and queryable collection. So...
public static IEnumerable<AttributeDetails> GetFilteredAttributeList(
string pEntityName, string pAttributeFilter) {
return entityList
.Where(e => e.EntityName == pEntityName)
.Select(e => e.Attributes
.Where (a => a.AttributeName.Contains (pAttributeFilter)
)
);
}
public static List<AttributeDetails> GetFilteredAttributeList(string pEntityName, string pAttributeFilter)
{
var entityList = new List<EntityDetails>(); //added just to makte it compile
var filtered =
from entity in entityList
where entity.EntityName == pEntityName
from attribute in entity.Attributes
where attribute.AttributeName.Contains(pAttributeFilter)
select attribute;
return filtered.ToList();
}