Let's say I have this method to seach my DB for products that fit a certain keyword:
public List<Product> GetByKeyword(string keyword)
{
using(var db = new DataEntities())
{
var query = db.Products.Where(x => x.Description.Contains(keyword);
return query.ToList();
}
}
This works fine, but somewhere else in my project, I want to get the active products only, still by keyword. I would like to do something like :
...
var result = ProductStore.GetByKeyword("Apple", x => x.isActive == 1);
Therefore, I created this method:
public List<Product> GetByKeyword(string keyword, Func<Product, bool> predicate = null)
{
using(var db = new DataEntities())
{
var query = db.Products.Where(x => x.Description.Contains(keyword);
if(predicate != null)
query = query.Where(x => predicate(x));
return query.ToList();
}
}
While this compiles well, the ToList() call generates a NotSupportedException because LINQ does not support the Invoke method.
Of course, I could to it with another method
i.e. GetActiveByKeyword(string keyword) but then I would have to do one for every possible variation, including the ones I didn't think of...
How do I get this to work? Thanks!
Isn't it just this:
if(predicate != null)
query = query.Where(predicate);
it's just as AD.Net says the reason why it works with Expression before is because if you say that the compiler knows it would be a lambda expression
Related
Task: I need to give some expression with parameters into LINQ's where to get some data from database, but have an error above
This example of working expression:
var shopExp = GetPersonForShop(PersonTypeIds.Director, new Guid("adda423f-8c38-40e0-9f39-6deceb787bc0")); // id
Where(shopExp)
But i need assign id dynamically, but got error above :
_repository.Persons
.Where(GetPersonForShop(PersonTypeIds.Director, person.PersonId)
And got error:
{"Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression2' to type 'System.Linq.Expressions.LambdaExpression'."}
How does function for where(linq) look:
private Expression<Func<Person, bool>> GetPersonForShop(PersonTypeIds personTypeId, Guid personId)
{
return person => person .PeronTypeId== (int) personTypeId && person .PersonId == personId;
}
This is approximate look like out production, just change names of parametrs code
How can I add expression with parameters to Where clause??
Lambda expressions use => notation. Try something like this:
var idToFind = new Guid("adda423f-8c38-40e0-9f39-6deceb787bc0");
var result = _repository.Persons
.Where(p => p.TypeId == PersonTypeIds.Director && p.PersonId == idToFind);
In this expression, p represents each Person record in the Persons table, compared one-by-one using the boolean expression that follows it.
Depending on your datasource, the comparison for each p will either be done by .NET in memory, or it will happen inside your database using a SQL WHERE clause which is constructed from the boolean expression. The last would be optimal because it would mean that not the entire Persons table has to be transferred into .NET memory before comparison can take place.
Update - To apply the same condition multiple times without repeating it in your code, while still keeping the advantages of LINQ to SQL translation intact, you can put the condition in an Expression<Func<Person, bool>> object and then use that multiple times:
Expression<Func<Person, bool>> expression =
p => p.TypeId == PersonTypeIds.Director && p.PersonId == idToFind;
var result1 = datasource1.Where(expression);
var result2 = datasource2.Where(expression);
var result3 = datasource3.Where(expression);
Or through a method that produces the Expression object:
var result1 = datasource1.Where(GetExpression(idToFind));
var result2 = datasource2.Where(GetExpression(idToFind));
var result3 = datasource3.Where(GetExpression(idToFind));
public Expression<Func<Person, bool>> GetExpression(Guid idToFind)
{
return p => p.TypeId == PersonTypeIds.Director && p.PersonId == idToFind;
}
Or alternatively you can use a helper method:
var result1 = FilterByTypeAndId(datasource1, idToFind);
var result2 = FilterByTypeAndId(datasource2, idToFind);
var result3 = FilterByTypeAndId(datasource3, idToFind);
public IQueryable<Person> FilterByTypeAndId(IQueryable<Person> datasource, Guid idToFind)
{
return datasource.Where(p => p.TypeId == PersonTypeIds.Director && p.PersonId == idToFind);
}
based on the previous response, I am going to give you a few alternatives and suggestions.
var idToFind = new Guid("adda423f-8c38-40e0-9f39-6deceb787bc0");
var result = _repository
.Persons
.Where(p => p.TypeId == PersonTypeIds.Director)
.Where(p => p.PersonId == idToFind)
.ToList();
First is doing the where clause in 2 steps and then, adding the ToList(), with the ToList(), you will deal with collections and LINQ that is pretty useful. And by doing the where clause in 2 steps, is more for readable purposes.
I have two similar methods that take a criteria object (dumb object with a list of properties), call a "CreateExpression" method on that criteria object, and then use the returned expression to filter results.
One of my examples has an argument of only one criteria, and it works with no problem. My second method takes a List<Criteria> and then attempts to iterate through each criteria in the list, and generate the expression for it, and then "and" it to the previous expression. The end result is supposed to be one big expression that I can then use in my linq query.
However, this second method is not working. When I use the debugger, I can see the predicate with it's body and lambda expression internally, but when it hits the SQL server, all it sends is a select statement with no where clauses at all.
Here is the method that works (with one criteria object):
public static List<Segment> GetByCriteria(Criteria.SegmentCriteria myCriteria)
{
List<Segment> result = null;
List<Segment> qry = db.Segments.AsExpandable<Segment>().Where<Segment>(CreateCriteriaExpression(myCriteria)).ToList<Segment>();
qry = qry.Where<Segment>(CreateCriteriaExpressionForCustomProperties(myCriteria).Compile()).ToList<Segment>();
if (qry != null && qry.Count != 0)
{
result = qry;
}
return result;
}
Here is the one that doesn't work:
public static List<Segment> GetByCriteria(List<Criteria.SegmentCriteria> myCriteria, Common.MultipleCriteriaMatchMethod myMatchMethod)
{
List<Segment> result = null;
var predicate = PredicateBuilder.True<Segment>();
var customPropertiesPredicate = PredicateBuilder.True<Segment>();
foreach (Criteria.SegmentCriteria x in myCriteria)
{
if (myMatchMethod == Common.MultipleCriteriaMatchMethod.MatchOnAll)
{
predicate = predicate.And(CreateCriteriaExpression(x).Expand());
customPropertiesPredicate = customPropertiesPredicate.And(CreateCriteriaExpressionForCustomProperties(x).Expand());
}
else if (myMatchMethod == Common.MultipleCriteriaMatchMethod.MatchOnAny)
{
predicate = predicate.Or(CreateCriteriaExpression(x).Expand());
customPropertiesPredicate = customPropertiesPredicate.Or(CreateCriteriaExpressionForCustomProperties(x).Expand());
}
}
List<Segment> qry = db.Segments.AsExpandable<Segment>().Where<Segment>(predicate.Expand()).ToList<Segment>();
qry = qry.Where<Segment>(customPropertiesPredicate.Expand().Compile()).ToList<Segment>();
if (qry != null && qry.Count != 0)
{
result = qry;
}
return result;
}
I am using Predicate Builder to generate the initial expression. I don't believe there is a problem with those methods since they work with the first (singular) method.
Does anyone know what's going on here?
Edit
I forgot to say that the backend is entity framework.
Ok, I figured it out.
I needed to use the "Expand()" method on ALL places where I was calling a predicate in the lines where I was appending the predicate. Here is the new fixed version of the foreach loop in my second method:
foreach (Criteria.SegmentCriteria x in myCriteria)
{
Criteria.SegmentCriteria item = x;
if (myMatchMethod == Common.MultipleCriteriaMatchMethod.MatchOnAll)
{
predicate = predicate.Expand().And<Segment>(CreateCriteriaExpression(item).Expand());
customPropertiesPredicate = customPropertiesPredicate.Expand().And<Segment>(CreateCriteriaExpressionForCustomProperties(item).Expand());
}
else if (myMatchMethod == Common.MultipleCriteriaMatchMethod.MatchOnAny)
{
predicate = predicate.Expand().Or<Segment>(CreateCriteriaExpression(item).Expand());
customPropertiesPredicate = customPropertiesPredicate.Expand().Or<Segment>(CreateCriteriaExpressionForCustomProperties(item).Expand());
}
}
And now it works! I found details about this also on this other question.
I'm writing what I believe should be a relatively straight-forward Windows Form app. I'm using LINQ to SQL, though I have never used it before. We have a SQL Server database, and I'm creating a front-end to access that database. I'm trying to figure out the most efficient way to search for multiple (arbitrary number of) search parameters with it.
In the windows form, I create a dictionary with each search key and its value to search for, and pass it into my search() method. I am trying to find a way to search the database with each of those keys and their associated values. Here is what I am trying to do:
public IQueryable<Product> Search(Dictionary<string, string> searchParams)
{
DBDataContext dc = new DBDataContext();
var query = dc.Products;
foreach (KeyValuePair<string, string> temp in searchParams)
{
query = query.Where(x => x.(temp.Key) == temp.Value);
}
return query;
}
I realize that syntactically x.(temp.Key) is incorrect, but I hope that illustrates what I am trying to do. I was wondering if there is another way to go about what I am trying to do without having to do a giant switch statement (or if/else if tree).
EDIT
So, I revised it a little, but I'm still having issues with it. Here is what I currently have:
public IQueryable<Product> Search(Dictionary<string, string> searchParams)
{
DBDataContext dc = new DBDataContext();
string sQuery = "";
foreach (KeyValuePair<string, string> temp in searchParams)
{
sQuery += temp.Key + "=" + temp.Value + " AND ";
}
var query = dc.Products.Where(sQuery);
return query;
}
According to the LINQ Dynamic Query Library article, this should be OK. Here is the error I'm getting:
The type arguments for method 'System.Linq.Queryable.Where(System.Linq.IQueryable, System.Linq.Expressions.Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Here's an example that works (I just tested it), using the Dynamic LINQ Query Library.
using System.Linq.Dynamic;
// ...
Dictionary<string, string> searchParams = new Dictionary<string,string>();
searchParams.Add("EmployeeID", "78");
searchParams.Add("EmpType", "\"my emp type\"");
IQueryable<Employee> query = context.Employees;
foreach (KeyValuePair<string, string> keyValuePair in searchParams)
{
query = query.Where(string.Format("{0} = {1}", keyValuePair.Key, keyValuePair.Value));
}
List<Employee> employees = query.ToList();
And, to make it absolutely clear that this code actually works here is the actual generated SQL:
FROM [HumanResources].[Employee] AS [t0]
WHERE ([t0].[EmpType] = #p0) AND ([t0].[EmployeeID] = #p1)',N'#p0 nvarchar(11),#p1 int',#p0=N'my emp type',#p1=78
If the Dictionary is not required for some reason, I would make your Search method as follows:
public IQueryable<Product> Search( Func<Product, bool> isMatch )
{
DBDataContext dc = new DBDataContext();
return dc.Products.Where( isMatch ).AsQueryable();
}
Then, you would use the method like so:
Obj.Search( item => item.Property1 == "Hello" && item.Property2 == "World" );
Is there some reason that you can't do that?
[Edit: added AsQueryable()]
[Edit: for Dynamic Query using strings]
Take a look here and see if this helps. I haven't used it, but looks like it's what you're looking for:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
Personally, I would generally prefer the type-safe Expression> approach since this will give you compile time errors...but if strings are needed, then looks like the best way to do it.
Based on the above link, looks like you should be able to do:
query = query.Where( String.Format("{0}={1}",dict.Key,dict.Value) );
[Edit: String Building Example]
So, one of the problems is that your sql query is going to end with an AND at the end of the string but then no condition after it...so, might try changing to this...syntax might be off slightly, but should be right:
public IQueryable<Product> Search(Dictionary<string, string> searchParams)
{
DBDataContext dc = new DBDataContext();
StringBuilder sQuery = new StringBuilder();
foreach (KeyValuePair<string, string> temp in searchParams)
{
if( sQuery.Length > 0 ) sQuery.Append(" AND ");
sQuery.AppendFormat("{0}={1}",temp.Key,temp.Value);
}
var query = dc.Products.Where(sQuery.ToString());
return query;
}
This will only use "AND" on conditions after the first. Hope it helps...
FYI - It's off-topic, but 'why' I used StringBuilder is that string concatenation the way you had it would result in the string being destroyed and a new string being created in memory 4 times per loop...so changed to a StringBuilder since that will create a buffer that can be filled and only resized when necessary.
Instead of using dictionary, you can use this method:
using System.Linq.Expressions;
// ...
public static IQueryable<Product> Search(Expression<Func<Product, bool>> search)
{
DBDataContext dc = new DBDataContext();
return dc.Products.Where(search);
}
You can use it like this:
Search(p => p.Name == "Name" && p.Id == 0);
You also won't be limited to string properties.
have u try put single quote for your string?
based on your foreach loop, there will be extra 'AND' at the end of query
try this instead
string sQuery = searchParam.Select(entry => string.Format("{0} = '{1}'", entry.Key, entry.Value)).Aggregate((current, next) => current + " AND " next);
if you are willing to create a dictionary of searchable terms than I've used something close to the approach below. I've adapted what I do be more closely fit into how you were doing things, I've also changed the names of the tables to fit a datacontext / object model that I have available to me.
The idea is to create a keyed list of terms that your query supports searching by. Then add functions that return an expression which can then be passed into the where clause of your query.
You should of course add some error handling for invalid keys etc.
public IQueryable<Person> Search(Dictionary<string, string> searchParams)
{
DBDataContext dc = new DBDataContext();
var query = dc.Persons.Where(p => true); //do an 'empty predicate' because you want 'query' to be an iqueryable<Person> not a Table<Person>
//build a list of the types of things you can filter on.
var criteriaDefinitions = new Dictionary<string,Func<string,Expression<Func<Person,bool>>>>();
criteriaDefinitions.Add("FirstName",s => p => p.FirstName == s);
criteriaDefinitions.Add("LastName",s => p => p.LastName == s);
//you can do operations other than just equals
criteriaDefinitions.Add("EmailContains",s => p => p.Email.Contains(s));
//you can even create expressions that integrate joins.
criteriaDefinitions.Add("HasContactInCity",s => p => p.Contacts.Any(c => c.City == s));
foreach (KeyValuePair<string, string> temp in searchParams)
{
//grab the correct function out of the dictionary
var func = criteriaDefinitions[temp.Key];
//evaluating the function will return an expression which can passed into the where clause.
var expr = func(temp.Value);
query = query.Where(expr);
}
return query;
}
for linq search queries multiple optional parameters
Can Use This Code:
var query = dx.GetAllJobs().Where(x => x.JobName.Contains(keyword));
if (SecLink != 0)
{
query = query.Where(x => x.SectorLink.Equals(SecLink));
}
if (LocLink != 0)
{
query = query.Where(x => x.LocationLink.Equals(LocLink));
}
if (IndLink != 0)
{
query = query.Where(x => x.IndustryLink.Equals(IndLink));
}
if (VacLink != 0)
{
query = query.Where(x => x.VacancyTypeLink.Equals(VacLink));
}
var lstJobs = query.ToList();
Suppose I have the two following Linq-To-SQL queries I want to refactor:
var someValue1 = 0;
var someValue2= 0;
var query1 = db.TableAs.Where( a => a.TableBs.Count() > someValue1 )
.Take( 10 );
var query2 = db.TableAs.Where( a => a.TableBs.First().item1 == someValue2)
.Take( 10 );
Note that only the Where parameter changes. There is any way to put the query inside a method and pass the Where parameter as an argument?
All the solutions posted in the previous question have been tried and failed in runtime when I try to enumerate the result.
The exception thrown was: "Unsupported overload used for query operator 'Where'"
Absolutely. You'd write:
public IQueryable<A> First10(Expression<Func<A,bool>> predicate)
{
return db.TableAs.Where(predicate).Take(10);
}
(That's assuming that TableA is IQueryable<A>.)
Call it with:
var someValue1 = 0;
var someValue2= 0;
var query1 = First10(a => a.TableBs.Count() > someValue1);
var query2 = First10(a => a.TableBs.First().item1 == someValue2);
I believe that will work...
The difference between this and the answers to your previous question is basically that this method takes Expression<Func<T,bool>> instead of just Func<T,bool> so it ends up using Queryable.Where instead of Enumerable.Where.
If you really want reusability you can try to write your own operators. E.g. instead of repeatedly writing:
var query =
Products
.Where(p => p.Description.Contains(description))
.Where(p => p.Discontinued == discontinued);
you can write simple methods:
public static IEnumerable<Product> ByName(this IEnumerable<Product> products, string description)
{
return products.Where(p => p.Description.Contains(description));
}
public static IEnumerable<Product> AreDiscontinued(IEnumerable<Product> products, bool isDiscontinued)
{
return products.Where(p => p.Discontinued == discontinued);
}
and then use it like this:
var query = Products.ByName("widget").AreDiscontinued(false);
We're working on a Log Viewer. The use will have the option to filter by user, severity, etc. In the Sql days I'd add to the query string, but I want to do it with Linq. How can I conditionally add where-clauses?
if you want to only filter if certain criteria is passed, do something like this
var logs = from log in context.Logs
select log;
if (filterBySeverity)
logs = logs.Where(p => p.Severity == severity);
if (filterByUser)
logs = logs.Where(p => p.User == user);
Doing so this way will allow your Expression tree to be exactly what you want. That way the SQL created will be exactly what you need and nothing less.
If you need to filter base on a List / Array use the following:
public List<Data> GetData(List<string> Numbers, List<string> Letters)
{
if (Numbers == null)
Numbers = new List<string>();
if (Letters == null)
Letters = new List<string>();
var q = from d in database.table
where (Numbers.Count == 0 || Numbers.Contains(d.Number))
where (Letters.Count == 0 || Letters.Contains(d.Letter))
select new Data
{
Number = d.Number,
Letter = d.Letter,
};
return q.ToList();
}
I ended using an answer similar to Daren's, but with an IQueryable interface:
IQueryable<Log> matches = m_Locator.Logs;
// Users filter
if (usersFilter)
matches = matches.Where(l => l.UserName == comboBoxUsers.Text);
// Severity filter
if (severityFilter)
matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);
Logs = (from log in matches
orderby log.EventTime descending
select log).ToList();
That builds up the query before hitting the database. The command won't run until .ToList() at the end.
I solved this with an extension method to allow LINQ to be conditionally enabled in the middle of a fluent expression. This removes the need to break up the expression with if statements.
.If() extension method:
public static IQueryable<TSource> If<TSource>(
this IQueryable<TSource> source,
bool condition,
Func<IQueryable<TSource>, IQueryable<TSource>> branch)
{
return condition ? branch(source) : source;
}
This allows you to do this:
return context.Logs
.If(filterBySeverity, q => q.Where(p => p.Severity == severity))
.If(filterByUser, q => q.Where(p => p.User == user))
.ToList();
Here's also an IEnumerable<T> version which will handle most other LINQ expressions:
public static IEnumerable<TSource> If<TSource>(
this IEnumerable<TSource> source,
bool condition,
Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
{
return condition ? branch(source) : source;
}
When it comes to conditional linq, I am very fond of the filters and pipes pattern.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/
Basically you create an extension method for each filter case that takes in the IQueryable and a parameter.
public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}
Doing this:
bool lastNameSearch = true/false; // depending if they want to search by last name,
having this in the where statement:
where (lastNameSearch && name.LastNameSearch == "smith")
means that when the final query is created, if lastNameSearch is false the query will completely omit any SQL for the last name search.
Another option would be to use something like the PredicateBuilder discussed here.
It allows you to write code like the following:
var newKids = Product.ContainsInDescription ("BlackBerry", "iPhone");
var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
.And (Product.IsSelling());
var query = from p in Data.Products.Where (newKids.Or (classics))
select p;
Note that I've only got this to work with Linq 2 SQL. EntityFramework does not implement Expression.Invoke, which is required for this method to work. I have a question regarding this issue here.
It isn't the prettiest thing but you can use a lambda expression and pass your conditions optionally. In TSQL I do a lot of the following to make parameters optional:
WHERE Field = #FieldVar OR #FieldVar IS NULL
You could duplicate the same style with a the following lambda (an example of checking authentication):
MyDataContext db = new MyDataContext();
void RunQuery(string param1, string param2, int? param3){
Func checkUser = user =>
((param1.Length > 0)? user.Param1 == param1 : 1 == 1) &&
((param2.Length > 0)? user.Param2 == param2 : 1 == 1) &&
((param3 != null)? user.Param3 == param3 : 1 == 1);
User foundUser = db.Users.SingleOrDefault(checkUser);
}
I had a similar requirement recently and eventually found this in he MSDN.
CSharp Samples for Visual Studio 2008
The classes included in the DynamicQuery sample of the download allow you to create dynamic queries at runtime in the following format:
var query =
db.Customers.
Where("City = #0 and Orders.Count >= #1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");
Using this you can build a query string dynamically at runtime and pass it into the Where() method:
string dynamicQueryString = "City = \"London\" and Order.Count >= 10";
var q = from c in db.Customers.Where(queryString, null)
orderby c.CompanyName
select c;
You can create and use this extension method
public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
return isToExecute ? source.Where(predicate) : source;
}
Just use C#'s && operator:
var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")
Edit: Ah, need to read more carefully. You wanted to know how to conditionally add additional clauses. In that case, I have no idea. :) What I'd probably do is just prepare several queries, and execute the right one, depending on what I ended up needing.
You could use an external method:
var results =
from rec in GetSomeRecs()
where ConditionalCheck(rec)
select rec;
...
bool ConditionalCheck( typeofRec input ) {
...
}
This would work, but can't be broken down into expression trees, which means Linq to SQL would run the check code against every record.
Alternatively:
var results =
from rec in GetSomeRecs()
where
(!filterBySeverity || rec.Severity == severity) &&
(!filterByUser|| rec.User == user)
select rec;
That might work in expression trees, meaning Linq to SQL would be optimised.
Well, what I thought was you could put the filter conditions into a generic list of Predicates:
var list = new List<string> { "me", "you", "meyou", "mow" };
var predicates = new List<Predicate<string>>();
predicates.Add(i => i.Contains("me"));
predicates.Add(i => i.EndsWith("w"));
var results = new List<string>();
foreach (var p in predicates)
results.AddRange(from i in list where p.Invoke(i) select i);
That results in a list containing "me", "meyou", and "mow".
You could optimize that by doing the foreach with the predicates in a totally different function that ORs all the predicates.