When this gets called two times, the second invocation doesn't re-run the query on the database, the query caching works.
var query = from p in session.Query<Product>()
where p.YearIntroduced >= 0
select p;
query = query.Cacheable();
var t = query.ToList();
However, when I put some join on the query, the query cache is not working anymore, hence when this is invoked two times, the query is invoked on database two times too:
var query = from p in session.Query<Product>()
join l in session.Query<ProductLanguage>()
on p.ProductId equals l.ProductId
where p.YearIntroduced >= 0
select new { p, l };
query = query.Cacheable();
var t = query.ToList();
Might be a dumb question, is query caching can work on one table only, hence when adding a join, the query is not cacheable anymore?
What's the solution to make a query cacheable even it has a join?
Another oddity, the query caching with join will work if I remove the where clause. When this gets called two times, the second invocation doesn't re-run the query on database, the query caching works
var query = from p in session.Query<Product>()
join l in session.Query<ProductLanguage>()
on p.ProductId equals l.ProductId
select new { p, l };
query = query.Cacheable();
var t = query.ToList();
But what's the use of a query when you can't put a where clause on it?
Is this an NHibernate bug, or am I just using query caching the wrong way?
Found the solution, put the Where clause on the final part of the query:
var query = from p in session.Query<Product>()
join l in session.Query<ProductLanguage>()
on p.ProductId equals l.ProductId
select new { p, l };
query = query.Where(x => x.p.ProductId && x.l.LanguageCode == "en").Cacheable();
var t = query.ToList();
Related
Just assume a simple example:I have two table and use this query:
select *
from People
where PersonGuid = (select PersonGuid from Sellers where Guid = '')
How can I write this query with linq?
I tried this:
var person = from p in loginContext.Person where p.PersonGuid =
(loginContext.Seller.Where(s => s.Guid == sellerGuid).FirstOrDefaultAsync());
but that's wrong.
What is the right way to write it?
If you were to rewrite your SQL query as
select *
from People AS PE
JOIN
Sellers AS Sellers
ON PE.PersonGuid = SE.PersonGuid
WHERE
SE.Guid = ''
then the LINQ becomes a little more obvious:
var person = (from p in loginContext.Person
join s in loginContext.Seller on p.PersonGuid equals s.PersonGuid
where s.guid == ""
select p)
.FirstOrDefault();
Although your SQL would suggest FirstOrDefault is not required.
Warning - not tested.
You could probably try using LINQ JOIN clause.
It should be something like:
from p in loginContext.Person
join s in loginContext.Seller on new { p.PersonGuid = s.Guid }
where s.Guid = sellerGuid
You can know more about LINQ JOIN here -> Microsoft C# Reference
Hope it helps!
I will assume that select PersonGuid from Sellers where Guid = '' will provide a single value no matter what, otherwise your SQL statement would fail.
Accepted answer works, but if you want a solution that keeps the spirit of the SQL query, you can rely on Any function. Something along the following:
var person = await loginContext.Person
.Where(p => loginContext.Sellers
.Any(s => s.PersonGuid == p.PersonGuid && s.Guid = ""))
.FirstOrDefaultAsync();
I expect this to be transformed into an SELECT ... WHERE EXISTS which is similar to your initial query.
I've built a MySQL query that returns 452 entries when I try it via phpMyAdmin, here's the query :
SELECT csp.id FROM child_subscription_prices csp
JOIN child_moments cm ON csp.child_moment_id = cm.id
JOIN moments m ON cm.moment_id = m.id
JOIN poles p ON m.pole_id = p.id
JOIN persons pr ON pr.id = csp.payer_id
WHERE cm.day BETWEEN '2013-1-15' AND '2013-1-17' AND p.type_id IN (1,2,3) AND csp.center_id = 1
ORDER BY pr.lastname ASC
But when I call it using Ebean (Play!Framework 2.1.1), like this :
SimpleDateFormat format = new SimpleDateFormat("y-M-d");
StringBuilder querySql = new StringBuilder();
querySql.append("SELECT csp.id FROM child_subscription_prices csp ");
querySql.append("JOIN child_moments cm ON csp.child_moment_id = cm.id ");
querySql.append("JOIN moments m ON cm.moment_id = m.id ");
querySql.append("JOIN poles p ON m.pole_id = p.id ");
querySql.append("JOIN persons pr ON pr.id = csp.payer_id ");
querySql.append("WHERE cm.day BETWEEN :start AND :end AND p.type_id IN (:poles) AND csp.center_id = :center ");
querySql.append("ORDER BY pr.lastname ASC;");
SqlQuery query = Ebean.createSqlQuery(querySql.toString());
query.setParameter("start", format.format(start));
query.setParameter("end", format.format(end));
query.setParameter("poles", StringUtils.join(poleIds.toArray(), ","));
query.setParameter("center", Session.getCenter().getId());
List<SqlRow> rows = query.findList();
rows.size(); // Return 409 !!
Of course, I tested the parameter in Java and compared them, they are identical ! (I even updated my first query to match the dates (2013-01-15 => 2013-1-15, to be like the one made in Java!
I don't have any clue why I have 50 entries less using Java, is there a particular Ebean configuration that doesn't follow some related database for any reason, or something like this that would explain the differences ?
Update
I also tried to count the number of results from the SQL query made in Java:
querySql.append("SELECT COUNT(csp.id) as total FROM child_subscription_prices csp ");
// ...
query.findUnique().getLong("total"); // Also 409 !
So it clearly seems to be a difference between the configuration in PhpMyAdmin and EBean, but I can't see which one!
For information, the problem was coming from this line :
query.setParameter("poles", StringUtils.join(poleIds.toArray(), ","));
Instead, I just had to replace it to this :
// poleids is a List<Long>
query.setParameter("poles", poleIds);
And everything worked fine :)
I have this SQL query:
select
sum(h.nbHeures)
from
Heures h
join
HeuresProjets hp on h.HpGuid=hp.HPId
join
ActivityCodes ac on h.Code=ac.ActivityId
join
MainDoeuvre mdo on ac.ActivityId=mdo.CodeGuid
where
hp.ExtraGuid = '61E931C8-3268-4C9C-9FF5-ED0213D348D0'
and mdo.NoType = 1
It runs in less than a second, which is good. My project uses LINQ to entities to get data. This (very similar to the sql) query is terribly slow, taking more than a minute.
var result = (from hp in this.HeuresProjets
join h in ctx.Heures on hp.HPId equals h.HpGuid
join ac in ctx.ActivityCodes on h.Code equals ac.ActivityId
join mdo in ctx.MainDoeuvre on ac.ActivityId equals mdo.CodeGuid
where hp.ExtraGuid == this.EntityGuid && mdo.NoType == (int)spType
select h.NbHeures).Sum();
total = result;
I tried using nested loops instead. It's faster but still slow (~15 seconds).
foreach (HeuresProjets item in this.HeuresProjets)
{
foreach (Heures h in ctx.Heures.Where(x => x.HpGuid == item.HPId))
{
if (h.ActivityCodes != null && h.ActivityCodes.MainDoeuvre.FirstOrDefault() != null && h.ActivityCodes.MainDoeuvre.First().NoType == (int)type)
{
total += h.NbHeures;
}
}
}
Am I doing something obviously wrong? If there's no way to optimize this I'll just call a stored procedure but I would really like the keep the logic in the code.
EDIT
I modified my query according to IronMan84's advice.
decimal total = 0;
var result = (from hp in ctx.HeuresProjets
join h in ctx.Heures on hp.HPId equals h.HpGuid
join ac in ctx.ActivityCodes on h.Code equals ac.ActivityId
join mdo in ctx.MainDoeuvre on ac.ActivityId equals mdo.CodeGuid
where hp.ExtraGuid == this.EntityGuid && mdo.NoType == (int)spType
select h);
if(result.Any())
total = result.Sum(x=>x.NbHeures);
This almost works. It runs fast and gives back a decimal but:
1. It's not the right value
2. The result is clearly cached because it returns the exact same value with different parameters.
From looking at your code I'm thinking that your query is grabbing every single record from those tables that you're joining on (hence the long amount of time). I'm seeing you using this.HeuresProjets, which I'm assuming is a collection of database objects that you already had grabbed from the database (and that's why you're not using ctx.HeuresProjets). That collection, then, has probably already been hydrated by the time you get to your join query. In which case it becomes a LINQ-To-Objects query, necessitating that EF go and grab all of the other tables' records in order to complete the join.
Assuming I'm correct in my assumption (and let me know if I'm wrong), you might want to try this out:
var result = (from hp in ctx.HeuresProjets
join h in ctx.Heures on hp.HPId equals h.HpGuid
join ac in ctx.ActivityCodes on h.Code equals ac.ActivityId
join mdo in ctx.MainDoeuvre on ac.ActivityId equals mdo.CodeGuid
where hp.ExtraGuid == this.EntityGuid && mdo.NoType == (int)spType
select h).Sum(h => h.NbHeures);
total = result;
Also, if this.HeuresProjets is a filtered list of only specific objects, you can then just add to the where clause of the query to make sure that the IDs are in this.HeuresProjets.Select(hp => hp.HPId)
My LINQ isnt the best, neither is my SQL but im trying to do something like this in LINQ (its kind of in pseudo-code)
select * from CarePlan c
-- only newest Referral based on r.Date (if more than one exists)
left outer join Referral r on r.CarePlanId = c.CarePlanId
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
The data looks like this:
A Patient can have a bunch of careplans
each careplan can have 0 to n referrals (I want the newest referral per careplan - if any exist)
Would like to return a list of careplans for each patient (whether or not they have a referral or not, if it has more than one referral - grab the newest one)
Thanks for any help guys
In LINQ you use the DefaultIfEmpty to achieve a left outer join - examples at http://msdn.microsoft.com/en-us/library/bb397895.aspx
Assuming that the referrals are not a (potentially empty) collection on the care plans so you're joining two collections together ...
Your query it would be something like:
Get the latest referral per Care Plan:
var latestReferrals = from r in referrals
group r by r.CarePlanId into lr
select new { CarePlanId = lr.Key, LatestReferral = lr.OrderByDescending(lrd => lrd.Date).First()};
Find the combined details:
var q = from c in CarePlan
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
join lr in latestReferrals on c.CarePlanId equals lr.CarePlanId into gj
from subReferral in gj.DefaultIfEmpty()
select new { CarePlanId = c.CarePlanId, LatestReferral = (subReferral == null) ? null : subReferral.LatestReferral };
Depending on whether you want many referral properties or just a few you may or may not want the whole Referral object in the second part or just extract the relevant properties.
You may be able to combine these into a single query but for readability it may be easier to keep them separate. If there is a combined solution you should also compare performance of the two approaches.
EDIT: see comment for joining patients/other tables from care plans
If Patient is joined from Referral (as per comment) then its more complex because you're doing several left outer joins. So switching to the slightly more concise syntax:
var combined = from c in carePlans
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
from lr in latestReferral.Where(r => r.CarePlanId == c.CarePlanId).DefaultIfEmpty()
from p in patients.Where(patient => patient.PatientId == ((lr != null) ? lr.LatestReferral.PatientId : -1)).DefaultIfEmpty()
select new { c.CarePlanId, PatientName = (p == null) ? "no patient" : p.PatientName, LatestReferral = (lr == null) ? null : lr.LatestReferral };
I'm trying to convert "SQL Outer Apply" to Linq.
The SQL is:
select Currencies.Name, Currencies.Sign ,a.ActualPrice
from Currencies
outer apply (select CurrencyID,ActualPrice from Prices
where ProductID=5 and Currencies.ID=Prices.CurrencyID)a
I have tried the following Linq but got one row, instead of row for each currency as the SQL statement gives me.
from c in Currencies
from p in Prices.DefaultIfEmpty()
where p.ProductID.Equals(5) && c.ID==p.CurrencyID
select new {c.Name, p.ActualPrice}
Any solution for this?
Try this:
var result =
Currencies.Select(c => new
{
Name = c.Name,
Sign = c.Sign,
ActualPrice = Prices.Where(p => p.ProductID == 5 &&
p.CurrencyID == c.ID)
.FirstOrDefault()
});
I am not sure if this returns the correct result, because I don't know what happens, when the subselect in outer apply returns multiple rows...