Entity Framework: Dynamically generate multiple OR condition on JOIN - c#

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(...);
}

Related

Nested expression building with linq and Entity Framework

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.

Specification Pattern - Implemeting an expression using ICollection

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));

Check if List is not null when using "contains" in LINQ query else select all records

I have a Search functionality in my application and it has 4 criterias basically Location, Status,PropertyType [List of String] and PriceRange [Min and Max Price] and below is the model
public class SearchFilters
{
public SearchFilters()
{
MinPrice = "10000";
MaxPrice = "8000000";
}
public IEnumerable<SelectListItem> Categories { get; set; }
public string[] CategoriesId { get; set; }
public IEnumerable<SelectListItem> Locations { get; set; }
public string[] LocationID { get; set; }
public IEnumerable<SelectListItem> Status { get; set; }
public string[] StatusID { get; set; }
public string MinPrice { get; set; }
public string MaxPrice { get; set; }
}
When data is received in controller List of Values Selected from SelectList will be stored in CategoriesId, LocationID and StatusID. Now Selecting values from each of List is optional, it can be single or multiple. So I need to filter Database and also if user does not select any item then this List will be null since it is an optional search criteria.
For Example
Values for status can be "Ongoing","Upcoming" and "Completed". So I used below LINQ to extract data.
[HttpGet]
public ActionResult Search(SearchFilters smodel)
{
var query=db.tblProperties.Where(p => smodel.StatusID.Contains(p.PropertyLocation)).Select(x=>x).ToList();
//.....
//.....
}
Just added one property comparison to demonstrate
This returns records without any problem, but if this smodel.StatusID comes null i.e. User does not select any value then this query fails with an exception. So how to overcome this? Also how to fetch all the records when no value is selected? Went through this post but the solution there wasn't useful to overcome this problem? Basically how can I incorporate a query for search in these situation?
Posted answers are correct and gave you the solution you need, I will go with checking for null, if you need this behavior in couple of places. If this request is repetitive in many places, i will go with the below solution.
There is another, more cleaner way if you're doing a lot of this checking, will be to add Extension Methods to do it for you.
Extension methods enable you to "add" methods to existing types
without creating a new derived type, recompiling, or otherwise
modifying the original type. Extension methods are a special kind of
static method, but they are called as if they were instance methods on
the extended type. For client code written in C# and Visual Basic,
there is no apparent difference between calling an extension method
and the methods that are actually defined in a type.
Code:
public static class CollectionExtension
{
public static bool CheckContainsIfHasValue<T>(this IEnumerable<T> source, T value)
{
return source == null || source.Contains(value);
}
}
Usage:
var query = db.tblProperties
.Where(p => smodel.StatusID.CheckContainsIfHasValue(p.PropertyLocation))
.ToList();
So if smodel.StatusID is null, you want to return all the records?
var query=db.tblProperties.Where(p => smodel.StatusID == null || smodel.StatusID.Contains(p.PropertyLocation))
.Select(x=>x).ToList();
So if you look at the Where clause now, if smodel.StatusID == null then every item will pass the Where clause. Note that the .Contains won't be called because of short cutting (if the first term of an OR is true, there's no point evaluating the second, so it won't).
You might also consider doing something like this:
.Where(p => smodel.StatusID == null ||
!smodel.StatusID.Any() ||
smodel.StatusID.Contains(p.PropertyLocation))
That way you are checking both that StatusID is not null and that the collection StatusID isn't empty.
If you can make StatusID default to an empty collection instead of null (for example, set it in the constructor of whatever class smodel is), then you can do this:
.Where(p => !smodel.StatusID.Any() ||
smodel.StatusID.Contains(p.PropertyLocation))
Since you won't need the null check anymore and Any should be translatable into LINQ to SQL.
Another option is to do the null check outside of the query
var initialQuery = db.tblProperties;
if(smodel.StatusID != null)
{
initialQuery = initialQuery.Where(p => smodel.StatusID.Contains(p.PropertyLocation));
}
var query = initialQuery.ToList();
Or as a helper method
public static IEnumerable<T> ConditionalWhere<T>(
this IEnumerable<T> collection,
Func<bool> condition,
Expression<Func<T, bool>> predicate)
{
if(condition())
return collection.Where(predicate);
return collection;
}
And then
var query = db.tblProperties.ConditionalWhere(
() => smodel.StatusID != null,
p => smodel.StatusID.Contains(p.PropertyLocation));
And you can chain them together
var query = db.tblProperties.ConditionalWhere(
() => smodel.StatusID != null,
p => smodel.StatusID.Contains(p.PropertyLocation))
.ConditionalWhere(
() => someOtherCollection != null,
p => someOtherCollection.Contains(p.PropertyLocation));
This will avoid running whatever the condition is multiple times for Linq-to-Objects and will allow you to use something that cannot be translated to SQL for EF or Linq-to-SQL.
You can simply add a null check in your where clause:
var query=db.tblProperties.Where(p => smodel.StatusID == null || smodel.StatusID.Contains(p.PropertyLocation))
.Select(x=>x).ToList();
C# uses Short-circuit evaluation of Boolean expressions. This means that C# stops evaluating an expression as soon as its result can be determined.
For instance in a && b, the result is known to be false if a is false, so b will not be evaluated. In a || b the result is known to be true if a is true, so b will not be evaluated.
You can use this to protect you from an exception by adding a null-test:
var query = db.tblProperties
.Where(p => smodel.StatusID == null ||
smodel.StatusID.Contains(p.PropertyLocation))
.ToList();
You can also drop the .Select(x=>x) part as it is doing nothing.
If you are using LINQ to EF then the text above does not apply. You cannot perform a null check on a collection, as this cannot translate to SQL. Instead make the check before:
bool ignore = smodel.StatusID == null || !smodel.StatusID.Any();
var query = db.tblProperties
.Where(p => ignore ||
smodel.StatusID.Contains(p.PropertyLocation))
.ToList();

Force LINQ to Entities recognize my method

I have these code(this is only example code)
public SomeService ()
{
public Queryable<CarDto> GetDtos()
{
context.Cars.Select(c => new CarDto
{
CarDtoId = c.Id,
Name = c.Name,
Status = GetCarStatus(c)
})
}
private CarStatusEnum GetCarStatus(Car car)
{
if(car.statusId == 2 || car.statusId == 3)
{
return 4;
}
return 5;
}
}
This code throw exception(LINQ to Entities does not recognize method GetCarStatus).
I know:
why it throws
I can do ToList() but I need Iqueryble
I can write method code inline without using method
But I want know way How can I do so,that LINQ to Entities can recognize my method?
Not sure if LINQ to Entities is able to convert such function to SQL query. You might need to use Expression for such cases.
Let the DTO do the conversion:
class CarDto
{
...
public int StatusId { get; set; }
public CarStatusEnum CarStatus
{
get { return StatusId == 2 || StatusId == 3
? CarStatusEnum.Four
: CarStatusEnum.Five; }
}
}
So in the query you just copy the TypeId value from the Car. And in the application you access CarStatus.
Note that you should return CarStatusEnum members, not plain integers (even when they match CarStatusEnum values. These may change in the future, which won't break your code, but will surely cause interesting bugs).

C# Linq where clause according to property name

Let's say I have the following class :
public class Person {
public string FirstName { get; set; }
public string SurName { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
}
Also, I have the following method and I am reaching out to person data via a repository.
public IEnumerable<Person> getPeople(string searchField, string searchTerm) {
//_repo.GetAll() returns IEnumerable<Person>
var model = _repo.GetAll();
//Need the logic here for filtering
return model;
}
As you can see I am getting two parameter for the method : searchField and searchTerm.
searchField is for the field name whose value will be used for filtering. searchTerm is the value which will be used to compare with retrived value (sorry if I am not clear here but this is the most I can come up with)
What I would normally do is as follows :
public IEnumerable<Person> getPeople(string searchField, string searchTerm) {
//_repo.GetAll() returns IEnumerable<Person>
var model = _repo.GetAll();
switch(searchField) {
case "FirstName":
model = model.Where(x => x.FirstName == searchTerm);
break;
case "SurName":
model = model.Where(x => x.SurName == searchTerm);
break;
//Keeps going
}
return model;
}
Which will work just fine. But if I make a change on my class, this code will have a change to break or be in lack of some functions if I add new properties this class.
What I am looking for is something like below :
NOTE :
This below code completely belongs to my imagination and there is no such a
thing exists.
model = model.Where(x => x.GetPropertyByName(searchField) == searchTerm);
Am I flying too high here if it is impossible or being complete idiot if there is already a built in way for this?
Looks like you need Dynamic Linq queries:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
I use this extension method to achieve what you want.
public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source, string propertyName, string value)
{
Expression<Func<TEntity, bool>> whereExpression = x => x.GetType().InvokeMember(propertyName, BindingFlags.GetProperty, null, x, null).ObjectToString().IndexOf(value, StringComparison.InvariantCultureIgnoreCase) >= 0;
return source.Where(whereExpression);
}
Note: ObjectToString is just another extension method that returns string.Empty if the Object passed in is NULL
For linq2Object You can use reflection as bellow(it's not very fast):
model.Where(x => x.GetType().GetProperty(propName).GetValue(x, null) == propVal);
but for linq2Entity I think this doesn't work, it works for linq2objects.
I think the following implementation looks an awful lot like what you originally intended, although changing this to a generic method likely makes more sense.
public IEnumerable<Person> getPeople(string searchField, string searchTerm) {
PropertyInfo getter=typeof(Person).GetProperty(searchField);
if(getter==null) {
throw new ArgumentOutOfRangeException("searchField");
}
return _repo.GetAll().Where(x => getter.GetValue(x, null).ToString()==searchTerm);
}
This should be type-safe:
public IEnumerable<T> Where<T,U>(Func<T,U> propertySelector, U value)
{
return model.Where(x => propertySelector(x) == value);
}
usage:
Where((MyClass x) => x.PropertyName, propertyValue);
Or:
public IEnumerable<T> Where<T>(Func<T,bool> entitySelector)
{
return model.Where(entitySelector);
}
usage:
Where<MyClass>(x => x.PropertyName == propertyValue && x.OtherProperty == otherValue);
Use Reflection
model = model.Where(x =>
((string)x.GetType().GetProperty("searchField").GetValue(0, null)) == searchTerm);
Rather than messing with reflection, custom expression trees, etc., when using Entity Framework, consider using the Builder Method extensions to the standard LINQ operators which take strings rather than lambdas. These are much easier to build for dynamic query requirements:
string filter = String.Format("it.{0} = #value", fieldName);
var model = context.People.Where(filter, new ObjectParameter("value", searchValue));
Of course, this would mean that you yould need to modify your repository to return IObjectSet rather than IEnumerable. It would perform better as well. By returning IEnumerable, you are hydrating every row in your database to your repository and then filtering via LINQ to Objects rather than applying the filter back in your database.
For more information about the Builder Methods in EF, see the BuilderMethodSamples.cs in http://archive.msdn.microsoft.com/EFQuerySamples/Release/ProjectReleases.aspx?ReleaseId=4422.

Categories