I have a question regarding a Linq to SQL query.
I have following situation:
I have a search with lots of options, like location, availability, name, language etc ...
For this options i have to execute a query to retrieve the results according to options selected, how can i best do it, i cannot write a linq query like for each possibility and combination of options, but i cannot write one for all of them as it will not work, for example:
from p in context.people where p.location==model.location && p.availability==model.availability .... select p
In this case imagine availability is not selected and should not be searched for, but in this case it will be passed as false, or if location is not set and is null so it will only search for empty locations, although i just need all.
So my question is how do people handle this kind of behaviour with queries?
As you long as you do not execute the linq query immediately you can just add where clauses to it. You can do this for example:
var query = from p in context.people;
if(searchOnLocation)
{
query = query.where(p => p.location == model.location);
}
if(otherSearch)
{
query = query.where(p => p.someOtherProperty == someotherValue);
}
var result = query.ToList();
As long you don't call ToList() on your IQueryable, the linq will not be translated into SQL. It's only in the last call, that the linq will be translated and executed against the database
IQueryable<Person> query = context.people;
if(model.location != null)
query = query.Where(x => x.location == model.location);
if(model.availability != null)
query = query.Where(x => x.availability == model.availability);
// etc
Basically, you can compose more and more restrictions as you go.
If you want to implement query without if condition than you can use following syntax:
var query = context.people.
where(p => p.location == (model.location ?? p.location)
&& p.availability == (model.availability ?? p.availability))
.ToList();
Related
I have a linQ query which does not fetch data when written as follows.
if (this.locationRepository.GetAvailableLocations(accountId)
.All(x => x.Orders.Any(y => y.OrderSubtypeId == int)OrderSubtype.Values.MigrationAdd)))
{
//Logic Implementation
}
But when same query is splitt into 2 queries and assigned to different variables, it returns true condition as follows.
var availableLocationsOrders = this.locationRepository.GetAvailableLocations(accountId).SelectMany(x => x.Orders);
var addMigrationOrders = availableLocationsOrders.Where(x => x.OrderSubtypeId == (int)OrderSubtype.Values.MigrationAdd);
if(addMigrationOrders.Any()){
//Control enters inside as the if condition returns records
}
Can anyone let me know why the first query does not yield any result?
My application is running under ASP.NET 4.0, which uses BLToolkti as ORM tool.
I have some queryable expression:
var q = db.GetTable<T>()
.Where(tb=>tb.TeamId==MyTeamId && tb.Season==MySeasonId)
.OrderByDescending(tb=>tb.Id)
.Take(20)
.Reverse()
Attempt to convert q.ToList() causes the following error:
Sequence 'Table(TeamBudget).Where(tb => ((tb.TeamId ==
value(VfmElita.DataLogicLayer.Teams.Team+TeamBudget+<>c__DisplayClass78).teamId)
AndAlso (tb.Season ==
value(VfmElita.DataLogicLayer.Teams.Team+TeamBudget+<>c__DisplayClass78).season))).OrderByDescending(tb
=> Convert(tb.Id)).Take(20).Reverse()' cannot be converted to SQL.
If I remove ".Reverse()" from the queryable object everything works fine.
What is the reason why queryable object with .Reverse() cannot be converted into SQL? Is that BLToolkit limitation? Is there any solution workaround for that?
Thank you!
It's pretty clear what the other LINQ methods convert to (where, order by, top(20)), but what would Reverse() convert to? I can't think of an SQL statement I've seen that mimics that behavior, and when you're querying the database your LINQ statement must ultimately resolve to valid SQL.
This may not be what you're going for, but one option would be to execute the query first using ToList(), then apply Reverse():
var q = db.GetTable<T>()
.Where(tb => tb.TeamId == MyTeamId && tb.Season == MySeasonId)
.OrderByDescending(tb => tb.Id)
.Take(20)
.ToList()
.Reverse();
Alternatively, you could get the count and skip that many records first, although this could be inaccurate if the number of records change between calls. Plus it's two queries instead of just one.
var totalRecords = db.GetTable<T>()
.Count(tb => tb.TeamId == MyTeamId && tb.Season == MySeasonId);
var q = db.GetTable<T>()
.Where(tb => tb.TeamId == MyTeamId && tb.Season == MySeasonId)
.Order(tb => tb.Id)
.Skip(totalRecords)
.Take(20);
The following Entity Framework query runs without error.
Predicate<Program> filterProgram;
if (programId.HasValue)
filterProgram = (p => p.Id == programId && !p.IsDeleted);
else
filterProgram = (p => !p.IsDeleted);
var analytics = (from a in repository.Query<Analytic>()
where (a.Marker == "Open" || a.Marker == "LastTouch") &&
a.EntityType == "Proposal" &&
a.Site == "C"
join p in repository.Query<Program>()
on a.EntityId equals p.Id
//where filterProgram(p)
group a
by new { a.LoginSessionId, a.EntityId, p.Id, p.Name } into g
let f = g.OrderBy(x => x.TimestampUtc).FirstOrDefault(x => x.Marker == "Open")
where f != null
let t = g.FirstOrDefault(x => x.Marker == "LastTouch" && x.TimestampUtc > f.TimestampUtc)
select new
{
ProgramId = g.Key.Id,
Program = g.Key.Name,
ProposalId = g.Key.EntityId,
FirstOpen = f,
LastTouch = (t ?? f).TimestampUtc
}).ToList();
However, if I uncomment the line where filterProgram(p), I get the run-time error:
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
I was expecting that LINQ would be able to incorporate my predicate into the query and convert it to SQL. Why am I getting this error, and is there a way to dynamically modify a where predicate this way?
The problem is caused because Entity Framework needs to be able to convert your LINQ query into SQL. Your LINQ query is compiled into a data structure called an Expression tree which is then passed to Entity Framework for conversion to SQL.
You have two options:
Replace your call to filterProgram with a more basic C# expression. Depending on the complexity of filterProgram this may not be possible.
Remove you call to filterProgram, and convert this query to an IEnumerable<T>, maybe by calling .ToList(). You can then further filter the results of this query using filterProgram
Example of 2
var query = /* Your Query With filterProgram commented out */
var resultsFromSql = query.ToList();
var fullyFiltered = resultsFromSql.Select(filterProgram);
Change filterProgram's type to Expression<Func<Program, bool>> and then it should be usable in a LINQ Where clause.
One caveat, however: I managed to get it to work in method chain syntax, but not in query syntax.
For example, this works:
dataContext.Programs.Where (filterProgram)
but this does not:
from p in dataContext.Programs
where filterprogram
(The compiler complains that it cannot resolve method Where, which is available on both IEnumerable and IQueryable.)
In your case, it might be acceptable to replace the line
join p in repository.Query<Program>()
with
join p in repository.Query<Program>().Where(filterProgram)
In Linq to Entities it is not possible to call an external method without converting the query to IEnumerable. Thus, since filterProgram is a method, you may not call inside the query.
If possible you may call ToList before calling the FilterProgram and it will work.
Another option maybe inserting the logic of filterProgram into the query.
The problem is that linq2entities tries to translate the expression tree to SQL your expression tree has an invocation of the method Invoke on a delegate type (filterProgram)
however there's nothing stopping you from inlining that predicate
var id= programId.HasValue ? programId.GetValueOrDefault() : -1;
var analytics = (from a in repository.Query<Analytic>()
where (a.Marker == "Open" || a.Marker == "LastTouch") &&
a.EntityType == "Proposal" &&
a.Site == "C"
join p in repository.Query<Program>()
on a.EntityId equals p.Id
where !p.IsDeleted && (!hasValue || p.Id == id)
....
This assumes that -1 is an invalid programId
I have the following query in LINQ to Entities:
var query = from p in db.Products
where p.Price > 10M
select p;
At this point the query has not executed, and I want to write a query that will return true/false based on some conditions:
return query.Any(p => p.IsInStock &&
(p.Category == "Beverage" ||
p.Category == "Other"));
This works fine; however, I would like to get some reuse out of my code. I have many methods that need to filter based on if the category is a Beverage or Other, so I tried creating a delegate:
Func<Product, bool> eligibleForDiscount = (product) => product.Category == "Beverage" || product.Category == "Other";
I wanted to substitute the inline check with the delegate:
return query.Any(p => p.IsInStock && eligibleForDiscount(p));
This gives me an error saying that LINQ to Entities doesn't support Invoke. Why can't I substitute the inline code for a delegate like this, and is there any way I can accomplish my reuse some other way?
Recall that under the hood Linq-to-{DATABASE} is just transforming the IQueryable you've produced into Sql.
You can't inline code like that because an Invoke (the method you're actually calling when you call a Func or Action) has no consistent way to transform it into a sql statement (you could be doing anything in there).
That said you can reuse parts by splitting it up:
var query = from p in db.Products
where p.Price > 10M
select p;
query = query.Where(p => p.IsInStock);
query = query.Where(p => p.Category == "Beverage" || p.Category == "Other");
return query.Any();
Both those can be put into methods that take an IQueryable<Product> and return the same (but filtered). Then you can reuse to your heart's content!
The problem is that IQueryable needs to generate a SQL expression to pass to RDBMS, and it cannot do it when all it has is an opaque predicate.
The obvious but inefficient way is to rewrite your query as follows:
return query.Where(p => p.IsInStock).AsEnumerable().Any(eligibleForDiscount);
A less trivial way would be as follows:
bool GotEligible(Expression<Func<Product,bool>> pred) {
return query.Where(p => p.IsInStock).Any(pred);
}
Note how instead of a predicate this method takes a predicate expression. Now it is transparent to the EF, and can be converted to a SQL query without a problem.
As long as you stick with IQueryable, you can keep reusable querys in functions.
public IQueryable<Product> EligibleForDiscount(IQueryable<Product> products)
{
return products.Where(p => product.Category == "Beverage" ||
product.Category == "Other");
}
Now call it like any other function:
IQueryable<Product> query = (from p in db.Products
where p.Price > 10M
select p);
query = EligibleForDiscount(query);
I have some table and the following condition of query: if parameter A is null take all, if not, use it in the query. I know how to do that in 2 steps:
List<O> list = null;
if (A = null)
{
list = context.Obj.Select(o => o).ToList();
}
else
{
list = context.Obj.Where(o.A == A).ToList();
}
Is it possible to have the same as one query?
Thanks
How about:
list = context.Obj.Where(o => A == null || o.A == A)
.ToList();
You can do it in one query but still using a condition:
IEnumerable<O> query = context.Obj;
if (A != null)
{
query = query.Where(o => o.A == A);
}
var list = query.ToList();
Or you could use a conditional operator to put the query in a single statement:
var query = A is null ? context.Obj : context.Obj.Where(o => o.A == A);
var list = query.ToList();
I would personally suggest either of the latter options, as they don't require that the LINQ provider is able to optimise away the filter in the case where A is null. (I'd expect most good LINQ providers / databases to be able to do that, but I'd generally avoid specifying a filter when it's not needed.)
I opted for
var list = context.Obj.Where(o => A.HasValue ? o.a == A : true);
I would probably write the query like this:
IQueryable<O> query = context.Obj;
if (A != null)
query = query.Where(o => o.A == A);
var list = query.ToList()
It's not one expression, but I think it's quite readable.
Also, this code assumes that context.Obj is IQueryable<O> (e.g. you are using LINQ to SQL). If that's not the case, just use IEnumerable<O>.