Force LINQ to Entities recognize my method - c#

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

Related

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

Entity Framework: Dynamically generate multiple OR condition on JOIN

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

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.

Changing selected objects inside a query

I have a class that needs a property set inside a LINQ-to-SQL query. My first attempt was to have a "setter" method that would return the object instance and could be used in my select, like this:
public partial class Foo
{
public DateTime RetrievalTime { get; set; }
public Foo SetRetrievalTimeAndReturnSelf ( DateTime value )
{
RetrievalTime = value;
return this;
}
}
....
from foo in DataContext.GetTable<Foo> select foo.SetRetrievalTimeAndReturnSelf();
Unfortunately, such a query throws an exception like this: "System.NotSupportedException: Method 'Foo.SetRetrievalTime(System.DateTime)' has no supported translation to SQL".
Is there any alternative to converting the result to a list and iterating over it? The query is used in a custom "Get" method that wraps the DataContext.GetTable method, so will be used as the base for many other queries. Immediately converting a potentially-large result set to a list would not be optimal.
UPDATE
Here's a better example of what I'm trying to do, updated with Jason's proposed solution:
protected IQueryable<T> Get<T>() where T : class, ISecurable
{
// retrieve all T records and associated security records
var query = from entity in DataContext.GetTable<T> ()
from userEntityAccess in DataContext.GetTable<UserEntityAccess> ()
where userEntityAccess.SysUserId == CurrentUser.Id
&& entity.Id == userEntityAccess.EntityId
&& userEntityAccess.EntityClassName == typeof ( T ).Name
select new { entity, userEntityAccess };
return query.AsEnumerable ()
.Select ( item =>
{
item.entity.CanRead = item.userEntityAccess.CanRead;
item.entity.CanWrite = item.userEntityAccess.CanWrite;
item.entity.CanDelete = item.userEntityAccess.CanDelete;
return item.entity;
} ).AsQueryable ();
}
public interface ISecurable
{
int Id { get; set; }
bool CanRead { get; set; }
bool CanWrite { get; set; }
bool CanDelete { get; set; }
}
UserEntityAccess is a cross-reference table between a user and a business object record (i.e. an entity). Each record contains fields like "CanRead", "CanWrite", and "CanDelete", and determines what a specific user can do with a specific record.
ISecurable is a marker interface that must be implemented by any LINQ-to-SQL domain class that needs to use this secured Get method.
var projection = DataContext.GetTable<Foo>
.AsEnumerable()
.Select(f => f.SetRetrievalTimeAndReturnSelf());
This will then perform the invocation of SetRetrievalTimeAndReturnSelf for each instance of Foo in DataContext.GetTable<Foo> when the IEnumerable<Foo> projection is iterated over.
What do you need to know the time that object was yanked of the database for? That's potentially smelly.

EF Distinct (IEqualityComparer) Error

Good Morning!
Given:
public class FooClass
{
public void FooMethod()
{
using (var myEntity = new MyEntity)
{
var result = myEntity.MyDomainEntity.Where(myDomainEntity => myDomainEntity.MySpecialID > default(int)).Distinct(new FooComparer);
}
}
}
public class FooComparer : IEqualityComparer<MyEntity.MyDomainEntity>
{
public bool Equals(MyEntity.MyDomainEntity x, MyEntity.MyDomainEntity y)
{
return x.MySpecialID == y.MySpecialID;
}
public int GetHashCode(MyEntity.MyDomainEntity obj)
{
return obj.MySpecialID.GetHashCode();
}
}
This will compile, but on runtime I will get an Linq to Entity could not translate Comparer-Exception.
Any suggestions?
If you're providing your own comparisons, you'll need to execute the Distinct call in .NET code. To make sure that happens, use AsEnumerable to turn IQueryable<T> into IEnumerable<T>:
var result = myEntity.MyDomainEntity
.Where(myDomainEntity => myDomainEntity.MySpecialID > default(int))
.AsEnumerable()
.Distinct(new FooComparer());
Of course at that point you'll be pulling more data across from the database. An alternative is to group the data instead:
var result = from entity in myEntity.MyDomainEntity
where entity.MySpecialID > 0
group entity by entity.MySpecialID into groups
select groups.FirstOrDefault();
That will get you the first entity encountered with each ID (assuming my query-fu isn't failing me). That's basically what Distinct does anyway, but it's all at the database.
(Note to future readers: calling First() makes more sense than FirstOrDefault(), but apparently that doesn't work.)

Categories