Calling functions from within linq statement - c#

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.

Related

Why is my object being disposed?

I'm trying to access SQL server using LINQ with c# in asp.net framework.
I'm writing a simple log-in form on a webpage. The following is my c# code to check if it is in the database.
public int getUserId(string un, string pw)
{
IEnumerable<int> query;
using (var context = new IngredientsLinqDataContext())
{
query = from c in context.USERs
where c.Username == un && c.Password == pw
select c.UserID;
}
if(query.Count() >= 1)
{
return query.Min();//a very serious kludge. Need to fix this
}
else { return -1; }
}
}
An object-disposed-exception is thrown when checking if the count of the query is greater than or equal to one. I had thought that, since query gets declared outside of the curly braces, this shouldn't be an issue of scope.
Additionally, what is a good way to fix the kludge? It should only be returning a single int, not a list of ints. How can I treat it that way? Thanks!
Change
query = from c in context.USERs
where c.Username == un && c.Password == pw
select c.UserID;
to
query = (from c in context.USERs
where c.Username == un && c.Password == pw
select c.UserID).ToList();
LINQ is using deferred execution, so you query is not evaluated immediately, but it is evaluated only at query.Count(). The context at that moment is already disposed. That's why you receive error.
context is disposed because you are trying to retrieve the data from database out of the context using block;
using (var context = new IngredientsLinqDataContext())
{
query = from c in context.USERs
where c.Username == un && c.Password == pw
select c.UserID;
if (query.Count() >= 1)
{
return query.Min();//a very serious kludge. Need to fix this
}
else { return -1; }
}
You have to be aware that a Linq query can beAsEnumerable or AsQueryable.
If your query is AsEnumerable it holds all information needed to access the elements in your query: You can ask for the first element in your sequence (if there is one), and once you've got an element you can ask for the next one (if there is one). If your query uses other functions in your process, it has all information to access these other functions. In short: AsEnumerable is processed in local memory.
If your query is AsQueryable it holds an Expression and a Provider. The Provider knows which process is designated to process the Expression. It knows which language this process uses. It is the task of the Provider to convert the Expression into the language of the destination processor and to send it to this processor.
For Entity Framework this is usually a database like SQL or MySQL, but it could also be a spreadsheet or a CSV file. The nice thing about a DbContext is that you don't need to know which language it uses to communicate with the other process. You could use the same IDbSet classes to communicate with a completely different process.
So an IQueryable does not hold the information to enumerate over the elements in the query. It only knows how to translate it into the language of the designated process and who to ask to execute this query.
The translation and execution is not done until you ask for elements. This means that you have to keep the providing object that your query uses alive until you don't need any more results from this providing object anymore.
Executing the query is done whenever you ask for a function that doesn't use deferred execution (as is described in the remarks section of every linq function). These are functions like ToList(), FirstOrDefault(), Single(), Count(), etc.
Functions like Where, Select, GroupBy and others that use deferred execution only change the Expression.
Conclusion
Make sure you've fetched all data that you need from your provider before
you Dispose() it
May be this will fix your issue.
public int getUserId(string un, string pw)
{
int query;
using (var context = new IngredientsLinqDataContext())
{
--change here
query = (from c in context.USERs
where c.Username == un && c.Password == pw
select c.UserID).FirstOrDefault();
}
if(query != 0) --change here
{
return query;//a very serious kludge. Need to fix this
}
else { return -1; }
}
Your use of query in this construct instructs LINQ to wait until the object is accessed before executing the actual query against the database. In this case query is accessed outside your using block, so that's why you're getting the error.
Try this:
result = (from c in context.USERs
where c.Username == un && c.Password == pw
select c.UserID).Min();

LINQ Querying Collection with different parameters

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.

How to structure a partially dynamic LINQ statement?

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

Linq, should I join those two queries together?

I have a Logins table which records when user is login, logout or loginFailed and its timestamp. Now I want to get the list of loginFailed after last login and the loginFailed happened within 24 hrs.
What I am doing now is get the last login timestamp first. then use second query to get the final list. do you think I should join those two queries together? Why not? Why yes? And how if yes?
var lastLoginTime = (from inRecord in db.Logins
where inRecord.Users.UserId == userId
&& inRecord.Action == "I"
orderby inRecord.Timestamp descending
select inRecord.Timestamp).Take(1);
if (lastLoginTime.Count() == 1)
{
DateTime lastInTime = (DateTime)lastLoginTime.First();
DateTime since = DateTime.Now.AddHours(-24);
String actionStr = "F";
var records = from record in db.Logins
where record.Users.UserId == userId
&& record.Timestamp >= since
&& record.Action == actionStr
&& record.Timestamp > lastInTime
orderby record.Timestamp
select record;
}
In the long run, I don't think it'd matter. No matter how you actually build the query in LINQ to SQL, the ultimate sequence of events on the DB server will be
get lastInTime
use lastInTime as part of records filter
Now... doing it as part of a single query will save on roundtrips of the actual date-time, so you can get some performance that way. But I would suggest that you only try to merge them if you absolutely need to because your performance profiling suggested that query was a bottleneck.
I don't think you should combine them because your current queries are quite readable. I think if they were combined it would be more difficult to understand the code.
I wouldn't merge, for reasons already stated by everyone else, but you can simplify the first query a bit: instead of
orderby inRecord.Timestamp descending
select inRecord.Timestamp).Take(1);
you can simply say:
select inRecord.Timestamp).Max();
It'll do the same thing, but it's a bit clearer than your way.
You can also use the IQueryable objects to compose more complex queries and still keep the code pretty easy to read. (I mixed the Extension syntax and query syntax just to show it can be done. You can just as easily swap this code around to separate it out as you would any other code in your solution.)
var usersRecords = db.Logins.Where(r => r.Users.UserId == userId);
var userLoginTimes = usersRecords.Where(r => r.Action == "I")
.Select(r => r.Timestamp);
var usersFunctions = usersRecords.Where(r => r.Action == "F");
var records = from record in usersFunctions
where userLoginTimes.Any()
let lastLoginTime = userLoginTimes.Max()
where record.Timestamp >= since
&& record.Timestamp > lastLoginTime
select record;

Lambda syntax in linq to db4o?

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.

Categories