I have a list that I need to query based on about 10 different parameters that user pass to my program.
What s the best way to do it ?
ex:
List<Users>
Query params can be : username and/or user id and/or age etc.
What you are trying to achieve is composing a dynamic query in LINQ. You could do this in two ways:
using dynamic LINQ;
using PredicateBuilder by Joseph Albahari (recommended in this case);
Briefly, this is how to use PredicateBuilder in your case:
var predicate = PredicateBuilder.True<User>();
if (!string.IsNullOrWhitespace(username))
predicate = predicate.And(a => a.Username == username);
if (!string.IsNullOrWhitespace(whatever))
predicate = predicate.And(a => a.Whatever == whatever);
/* etc. etc. */
var filteredUsers = myUsers.Where(predicate);
I guess you're talking about filtering objects based on an initially unknown number of conditions.
LINQ does not evaluate your query until you explicitly ask so by enumerating it (using foreach, ToList(), ToArray()...). And when you do that, depending on the implementation of LINQ (SQL, objects, XML, etc), the query will be optimized.
AFAIK, all implementation are able to optimize "where condition1 where condition2" into "where condition1 && condition2".
Which means you just have to add your filtering conditions one by one. For instance:
private List<User> FilterUsers(string username, string userid, int? minAge, int? maxAge)
{
IEnumerable<User> query = GetUsers();
if (!string.IsNullOrEmpty(username)) query = query.Where(u => u.Username.StartsWith(username);
if (!string.IsNullOrEmpty(userid)) query = query.Where(u => u.Userid == userid);
if (minAge != null) query = query.Where(u => u.Age >= minAge.Value);
if (maxAge != null) query = query.Where(u => u.Age <= maxAge.Value);
return query.ToList();
}
Here GetUsers() is supposed to return an IEnumerable. Can be a Table if you're using LINQ-to-SQL, ChildNodes() if you're using LINQ-to-XML. Try to enumerable the least possible before applying your where clauses.
I assume that you want to build the where part of your Linq query dynamically - I don't recommend that via a string (see link below) because it could introduce something similar to what is known as SQL injection to LINQ i.e. change of behaviour of the LINQ query with user-supplied params in ways you don't want...
That said check this link out http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx - Scott Gu demonstrates something that comes close to what you describe including a library and some sample code...
What you want to do is have a method with the folowing signature
public void Filter(Func<Account, bool> filterExpression)
{
list.Where(filterExpression).ToList();
}
this way you can support filtering for various scenarios on the UI.
Not breaking the encapsulation of your (I assume) repository, that's why I do the ToList() so no IQueryable is sent to the client.
Related
we are Entity Framework in our project. Need to know the performance impact between .ANY() and Expressions to form Where clause.
In the below function i used two approach to get result:
APPROACH 1 - Form Lambda expression query using ANY()
From my observation using .Any() is not adding where clause when query is executed(checked in sql profiler) what EF does is gets all matched inner joined records store in-memory and then apply condition specified in .ANY()
APPROACH 2 - Form Expression Query Starts
With Expressions i'm explicitly forming where clause and executing.checked the same in SQL query Profiler i'm able to see where clause.
Note: To form expression where clause i'm doing additional loops and "CombinePredicates".
Now, my doubts are:
which approach will improve performance. Do i need to go with Lambda
with .ANY() or Expressions?
what is the right way to from where clause to improve performance?
If not the two approach suggest me the right way to do it
private bool GetClientNotifications(int clientId, IList<ClientNotification> clientNotifications)
{
IList<string> clientNotificationList = null;
var clientNotificationsExists = clientNotifications?.Select(x => new { x.Name, x.notificationId
}).ToList();
if (clientNotificationsExists?.Count > 0)
{
//**Approach 1 => Form Lamada Query starts**
notificationList = this._clientNotificationRepository?.FindBy(x => clientNotificationsExists.Any(x1 => x.notificationId == x1.notificationId && x.clientId ==
clientId)).Select(x => x.Name).ToList();
**//Form Lamada Query Ends**
//**Approach 2 =>Form Expression Query Starts**
var filterExpressions = new List<Expression<Func<DbModel.ClientNotification, bool>>>();
Expression<Func<DbModel.ClientNotification, bool>> predicate = null;
foreach (var clientNotification in clientNotificationsExists)
{
predicate = a => a.clientId == clientId && a.notificationId == clientNotification .notificationId;
filterExpressions.Add(predicate);
}
predicate = filterExpressions.CombinePredicates<DbModel.ClientNotification>(Expression.OrElse);
clientNotificationList = this._clientNotificationRepository?.FindBy(predicate).Select(x => x.Name).ToList();
//**Form Expression Query Ends**
}
return clientNotificationList;
}
If non of the approaches were good please suggest me the right way to do.
I noticed the clientId being used on both approaches is used on conditions linked to clientNotificationsExists but there is no actual relationship with its objects so this condition can be moved up one level. This might bring a minuscule benefit as the overall sql will be smaller without the duplicate clauses.
From the described behavior the first approach filter is not being translated to sql so it is being resolved at the client side however, if it could be translated the performance would be similar or identical.
If you want to generate an IN sql clause you can use an array and use the Contains method within your filter expression. That could perhaps improve a little bit but don't get your hopes too high.
Try the following:
private bool GetClientNotifications(int clientId, IList<ClientNotification> clientNotifications)
{
IList<string> clientNotificationList = null;
var clientNotificationsExists = clientNotifications?
.Select(x => new { x.Name, x.notificationId }).ToList();
if (clientNotificationsExists?.Count > 0)
{
var notificationIds = clientNotificationsExists.Select(x => x.notificationId).ToArray();
clientNotificationList = this._clientNotificationRepository?
.FindBy(x => x.clientId == clientId && notificationIds.Contains(x.notificationId));
}
return clientNotificationList;
}
I'm currently writing a search function in ASP.NET MVC 4 with the Entity Framework. However, I've hit a roadblock for which I can only find "bad" solutions.
My search functions gets back a model which consists of 4 parameters:
String Name
String Street
String Code
String Province
List<Query> query = (from t in Model select t).ToList();
Now I'd like to filter on my search input. However, a user can decide to fill in as many search fields as possible. He can decide to use Name and Street, or Name, Street and Province, or ...
The only real solution I was able to find consists of making my query and IQueryable and check whether or not a field has been filled with an if, then use a .Where to update the query.
As this would currently give m 5 queries, I'm wondering on whether or not there is a better solution that I'm missing here.
Thanks for helping me.
If I understand you correct. You might want something like this:
string Name;
string Street;
string Code;
string Province;
var query=(from t in Model select t);
if(Name!=null)
{
query=query.Where (q =>q.Name==Name);
}
if(Street!=null)
{
query=query.Where (q =>q.Street==Street);
}
if(Code!=null)
{
query=query.Where (q =>q.Code==Code);
}
if(Province!=null)
{
query=query.Where (q =>q.Province==Province);
}
List<Query> ls = query.ToList();
You will have a IQueryable when you add the where statements and when you do the ToList() that sql will execute.
Update
To answer the comment of Luis Hernández. So this is how it works. When you select from the model in this case the collection type is IQueryable. This means that it has not been executed against the database. For the query to execute you need to apply some of the final metod. To tell linq that it will actually do the database call. These are for example
ToList()
FirstOrDefault()
SingleOrDefault()
Single()
First()
ToDictionary()
So when we append the Where clauses without using ToList(). There is no execution of the query.
Please try the query in LinqPad
Use the Entity filter class you find here : https://servicelayerhelpers.codeplex.com/SourceControl/changeset/view/32810#537055
so first specify your filter and after that just apply it to your query.
Example:
var filter = EntityFilter
.Where(c => c.Name == came)
.Where(c => c.City == city);
var customers = FindCustomers(filter);
Customer[] FindCustomers(IEntityFilter filter)
{
var query = context.Customers;
query = filter.Filter(query);
return query.ToArray();
}
more info on: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=66
You casn try some thing like this
from cars in tblCars
where (cars.colorID == 1) && (cars.Wieght > 500) && (cars.Active == true)
select cars;
Arion's solution is of course very good, I tried to make it a little less repetitive using reflection, hope it helps.
Type myType = myObject.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
foreach (PropertyInfo prop in props)
{
object propValue = prop.GetValue(myObject, null);
if (propValue != null)
{
query = query.Where(q => prop.GetValue(q, null) == propValue);
}
}
EDIT:
I've edited it to work on all properties. of course you still need some things around for this to work, but once you understand how to work with it, you can use it as a utility for all of your code, instead of hardcoding it for every type
I currently have a method on my repository like this:
public int GetMessageCountBy_Username(string username, bool sent)
{
var query = _dataContext.Messages.AsQueryable();
if (sent)
query = query.Where(x => x.Sender.ToLower() == username.ToLower());
else
query = query.Where(x => x.Recipient.ToLower() == username.ToLower());
return query.Count();
}
It currently builds one of two queries based on the sent boolean. Is this the best way to do this or is there a way to do this within the query itself? I want to check if x.Sender is equal to username if sent equals true. But I want to check if x.Recipient is equal to username if sent equals false.
I then want this LINQ expression to translate into SQL within Entity Framework, which I believe it is doing.
I just want to avoid repeating as much code as possible.
You could do something like this :
public int GetMessageCountBy_Username(string username, bool sent)
{
Func<Message, string> userSelector = m => sent ? m.Sender : m.Recipient;
var query =
_dataContext.Messages.AsQueryable()
.Where(x => userSelector(x).ToLower() == username.ToLower());
return query.Count();
}
Thus the choosing of the right user (the sender or the recipient) is done before the linq part, saving you from repeating it twice.
Yes, I believe this is correct way to do it. Because it is easy to create complex queries without repeating whole parts of queries.
And your thinking about translating to SQL is correct too. But beware, this is done at the moment, when data or agregation is requested. In your case, the SQL will be generated and executed when you call Count().
Just wondering if this is the most efficient method of doing this? is there a way of having all the linq within one statement instead of calling a method, like a subselect or something?
newEmployee = (from emp
in db.employees
select new
{
a.EmployeeID,
a.Username,
Status = emp.GetEmployeeCurrentStatus(a.Username)
}).ToList();
This is the GetEmployeeCurrentStatus which returns the status of the employee:
public string GetEmployeeCurrentStatus(string username)
{
using (Entities db = new Entities())
{
var times = (from d in db.TimeTables
where d.DateTime == DateTime.Today &&
d.Employee.Username == username
select d)
.OrderByDescending(d => d.TimeID).FirstOrDefault();
return (x.ClockOut == null ? "IN" : "OUT");
}
}
how about:
newEmployee = (db.employees.Select(emp => new
{
emp.EmployeeID,
emp.Username,
Status = db.TimeTables
.Where(d => d.Employee.Username == emp.Username
&& d.DateTime == DateTime.Today)
.Select(x => x.ClockOut == null ? "IN" : "OUT")
.FirstOrDefault()
})).ToList();
Your attempt may appear cleaner and is functionally ok. However, it is firing up a secondary db call. This will be bad for scalability and performance. The version i've posted uses the same initial db connection and will make the join 1-1. This will result in tighter, faster queries as well as lower resource usage.
You cannot really call a custom method inside a query (or a part of a query that will be executed using the databse). You have essentially two options:
Call ToList before performing the select that needs to call the method (this way, the method will be called on in-memory data)
Compose the query such that it can all run on the SQL server if it is possible. This can be done using AsExpandable extension in predicate builder. For more information on how this works, see also my blog post.
its fine for small data (employees count) but since each GetEmployeeCurrentStatus requires an sql new connection so its not that best practice.
I personally will get all employees (one trip to database) and then get all employees status (one trip to database) so i cashed them all, now i'll join them locally
Hope this helped
Regardless of efficiency, having GetEmployeeCurrentStatus(...) as a method makes the code clearer and more reusable.
Assuming you are using LINQ to SQL or EF, I would refactor your query to use a Join. That way, you will execute a single efficient SQL query on the database, instead of two separate queries.
I know the following is possible with linq2db4o
from Apple a in db
where a.Color.Equals(Colors.Green)
select a
What I need however is something that allows me to build my query conditionally (like I can in other linq variants)
public IEnumerable<Apple> SearchApples (AppleSearchbag bag){
var q = db.Apples;
if(bag.Color != null){
q = q.Where(a=>a.Color.Equals(bag.Color));
}
return q.AsEnumerable();
}
In a real world situation the searchbag will hold many properties and building a giant if-tree that catches all possible combinations of filled in properties would be madman's work.
It is possible to first call
var q = (from Color c in db select c);
and then continue from there. but this is not exactly what I'm looking for.
Disclaimer: near duplicate of my question of nearly 11 months ago.
This one's a bit more clear as I understand the matter better now and I hope by now some of the db4o dev eyes could catch this on this:
Any suggestions?
Yes it's definitely possible to compose optimized LINQ queries using db4o. Granted that db is defined as follows:
IObjectContainer db;
Here is your query:
public IEnumerable<Apple> SearchApples (AppleSearchbag bag)
{
var query = db.Cast<Apple> ();
// query will be a Db4objects.Db4o.Linq.IDb4oLinqQuery<Apple>
if (bag.Color != null)
query = query.Where (a => a.Color == bag.Color);
return query;
}
In that case, the query will be executed whenever the returned enumerable is being iterated over.
Another possibility is to use the IQueryable mechanism, that has the advantage of being better recognized by developers:
public IQueryable<Apple> SearchApples (AppleSearchbag bag)
{
var query = db.AsQueryable<Apple> ();
// query will be a System.Linq.IQueryble<Apple>
if (bag.Color != null)
query = query.Where (a => a.Color == bag.Color);
return query;
}
In both cases, db4o will try to deduce an optimized query from the lambda expression upon execution, and if it fails, will fallback to LINQ to objects. The first one has the advantage of being more direct, by avoiding the queryable to LINQ to db4o transformation.