Linq, should I join those two queries together? - c#

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;

Related

How to filter collection in linq based on some subobjects in that list

I want to get only those employees that have at least one service, and which that service is younger than current date (dt)
I tried with .Any() but it returns me all employees with all services (it doesnt check that date)
var employees =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(x => x.IsCounter && x.Services != null && x.Services.Count > 0 &&
x.Services.Any(u => u.ActiveTo >= dt.Value));
I want to filter just those employees which have at least one service or more where ActiveTo is not in the past (dt is a current datetime.now)
You can chain multiple .Where and .Select statements after one another. Your LINQ query is very hard to read without more specific information about your objects.
To make it more readable, I would suggest splitting your requirements into separate queries, like so:
var employeesWithActiveServices =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(e => e.IsCounter && e.Services.Count >= 1)
.Select(e => e.Services.Contains(s => s.ActiveTo >= DateTime.Now)).ToList();
Notice how I removed your e.Services != null check. It is redundant when you're already checking e.Services.Count.
This was made quickly of the top of my head, so you may need to tweak it to suit your needs.
It is still a hard LINQ query to read without seeing the objects it is querying, but this at least makes the query itself more readable.
Try to remove any additional null, Count checks, otherwise SQL will be complex and slow:
var employees =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(x => x.IsCounter &&
x.Services.Any(u => u.ActiveTo >= dt.Value));
EF Core translates your query into the SQL, which do not have NullReference exception.

Select rows without joining other tables regarding other tables

I'm working in asp.net web forms 4.5 version.
I have trouble with linq.
I want to bring a table data.. (I don't want to join it.. as I would want it to be deleted and edited by the autodelete and autoedit button of gridview)
But I'm lost with linq.
I would like to do something like this..
public Iqueryable detailGrid_getData(){
string fromDStr = fromTBox.Text;
DateTime fromD = Convert.ToDateTime(fromDStr);
string toDStr = toTBox.Text;
DateTime toD = Convert.ToDateTime(toDStr);
var items = from s in db.salesOrderDetail_T where
db.salesOrder_T
.Select(so => so.poDate <= toD && so.poDate >=fromD)
.Contatins(s.soIdx) && s.stat == stat;
return items;
}
at which I got the idea from here : LINQ, select ID from Table where it does not match ID in another table
but for some reason, it doesn't work.
Will someone tell me why this is not working??
edit : It says a query body must end with a select clause or a group clause
The error message is quite clear here. Your query (items) does not end in a select clause, I think this is what you want:
var items = from s in db.salesOrderDetail_T where
db.salesOrder_T.Where(so => so.poDate <= toD && so.poDate >=fromD).Select(p=>p.Id).Contains(s.soIdx)
&& s.stat == stat
select s;
I would like to answer this because I figured it out totally different way.
Turns out that in LINQ, I can get around things in a lot of way.
This is the way I got through this..
Still not that expert in LINQ, but getting it.
var items = from so in db.salesOrderDetail_T
where so.poDate <= toD && so.poDate >= fromD
select so.idx;
soIdxList = items.toList();
items = items.Where(it => soIdxList.Contains((int) it.soIdx)
and then, use it.
Again, I'm not sure this is a good way to do things,
but it's easy and works.

how to take 100 records from linq query based on a condition

I have a query, which will give the result set . based on a condition I want to take the 100 records. that means . I have a variable x, if the value of x is 100 then I have to do .take(100) else I need to get the complete records.
var abc=(from st in Context.STopics
where st.IsActive==true && st.StudentID == 123
select new result()
{
name = st.name }).ToList().Take(100);
Because LINQ returns an IQueryable which has deferred execution, you can create your query, then restrict it to the first 100 records if your condition is true and then get the results. That way, if your condition is false, you will get all results.
var abc = (from st in Context.STopics
where st.IsActive && st.StudentID == 123
select new result
{
name = st.name
});
if (x == 100)
abc = abc.Take(100);
abc = abc.ToList();
Note that it is important to do the Take before the ToList, otherwise, it would retrieve all the records, and then only keep the first 100 - it is much more efficient to get only the records you need, especially if it is a query on a database table that could contain hundreds of thousands of rows.
One of the most important concept in SQL TOP command is order by. You should not use TOP without order by because it may return different results at different situations.
The same concept is applicable to linq too.
var results = Context.STopics.Where(st => st.IsActive && st.StudentID == 123)
.Select(st => new result(){name = st.name})
.OrderBy(r => r.name)
.Take(100).ToList();
Take and Skip operations are well defined only against ordered sets. More info
Although the other users are correct in giving you the results you want...
This is NOT how you should be using Entity Framework.
This is the better way to use EF.
var query = from student in Context.Students
where student.Id == 123
from topic in student.Topics
order by topic.Name
select topic;
Notice how the structure more closely follows the logic of the business requirements.
You can almost read the code in English.

Combining LINQ queries in LINQ to CRM causes problems?

Something weird is going on.
If I do this:
var allAccountsQuery = from acc in baseQ
where
//high potential check - 1, 2, 3
(acc.mcpl_potencjal_klienta == 1 || acc.mcpl_potencjal_klienta == 2 || acc.mcpl_potencjal_klienta == 3) &&
//directors block check
((acc.mcpl_blokada_dyrektorska == true && acc.mcpl_blokada_do <= date) || acc.mcpl_blokada_dyrektorska == false || acc.mcpl_blokada_dyrektorska == null) &&
//sack assign date check
(acc.mcpl_dataprzypisaniazworka == null || acc.mcpl_dataprzypisaniazworka < date) &&
//owner change check
(acc.mcpl_datazmianywasciciela == null || acc.mcpl_datazmianywasciciela < date) &&
//creation date check
//TODO:For testing!
//(acc.mcpl_data_utworzenia_test < date)
(acc.createdon < date)
select acc;
var joinQuery = from acc in allAccountsQuery
join opp in ctx.opportunityopportunities on acc.accountid equals opp.customerid.Value
select new
{
Account = acc,
Opportunity = opp
};
Plugins.Common.XrmHelper.ClearCache("account");
var joinResult = joinQuery.ToList();
Then I'll get an unknown platform error when executing this query. I need to copy-paste the WHOLE where clause from allAccountsQuery to the joinQuery and use baseQ again, and then it works.
What's going on here? I thought you can safely join LINQ queries as long as you're not doing any unsupported operations.
PS. The STRANGEST part is that the pasted code WILL work with slightly different where conditions.
PPS. baseQ is just an even simpler where query, much like the allAccountsQuery.
Maybe is not the answer but as I can't leave a comment and no one has answer I think this could help.
Why you don't do the join in the first query? As from I know the LINQ CRM queries have problems joining tables when in the clause WHERE we have the OR Predicate, and not when we try to select from different tables, I think for you query should work. I have one post explaining what I learned.
Linq-to-CRM has a limited set of supported operations compared to other providers life EF or Linq-to-SQL.
You may have better success hydrating one or both of the two queries. Since your account query has a where clause try hydrating it:
var joinQuery = from acc in allAccountsQuery.ToList() // call ToList() to hydrate the query
join opp in ctx.opportunityopportunities
on acc.accountid equals opp.customerid.Value
select new
{
Account = acc,
Opportunity = opp
};
If you have a LARGE number of Opportunities you may want to try and filter that query based on the accounts returned from the first query before doing the Join.

Calling functions from within linq statement

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.

Categories