converting sql statement back to lambda expression - entity-framework

I have the query below, and its sql code. It's running really slow, so it was re written in sql, now I'm just not sure how to convert the sql back to a lambda expression.
This is the part of the expression giving me the problems, somewhere in
r.RecordProducts.Any()
records = records
.Include(r => r.Employer)
.Include(r => r.Contractor)
.Include(r => r.RecordProducts)
.ThenInclude(rp => rp.ProductDefendant.Defendant)
.Where(r => EF.Functions.Like(r.Employer.DefendantCode, "%" + input.DefendantCode + "%")
|| EF.Functions.Like(r.Contractor.DefendantCode, "%" + input.DefendantCode + "%")
|| r.RecordProducts.Any(rp => EF.Functions.Like(rp.ProductDefendant.Defendant.DefendantCode, "%" + input.DefendantCode + "%") && rp.IsActive == true));
the any clause does an exist and some funky stuff in the sql where clause below
SELECT [t].[Id], [t].[StartDate], [t].[EndDate], [t].[WitnessName], [t].[SourceCode], [t].[JobsiteName], [t].[ShipName], [t].[EmployerCode]
FROM (
SELECT DISTINCT [r].[RecordID] AS [Id], [r].[StartDate], [r].[EndDate], [r.Witness].[FullName] AS [WitnessName], CASE
WHEN [r].[SourceID] IS NOT NULL
THEN [r.Source].[SourceCode] ELSE N'zzzzz'
END AS [SourceCode], CASE
WHEN [r].[JobsiteID] IS NOT NULL
THEN [r.Jobsite].[JobsiteName] ELSE N'zzzzz'
END AS [JobsiteName], CASE
WHEN [r].[ShipID] IS NOT NULL
THEN [r.Ship].[ShipName] ELSE N'zzzzz'
END AS [ShipName], CASE
WHEN [r].[EmployerID] IS NOT NULL
THEN [r.Employer].[DefendantCode] ELSE N'zzzzz'
END AS [EmployerCode]
FROM [Records] AS [r]
LEFT JOIN [Ships] AS [r.Ship] ON [r].[ShipID] = [r.Ship].[ShipID]
LEFT JOIN [Jobsites] AS [r.Jobsite] ON [r].[JobsiteID] = [r.Jobsite].[JobsiteID]
LEFT JOIN [Sources] AS [r.Source] ON [r].[SourceID] = [r.Source].[SourceID]
LEFT JOIN [Witnesses] AS [r.Witness] ON [r].[WitnessID] = [r.Witness].[WitnessID]
LEFT JOIN [Defendants] AS [r.Contractor] ON [r].[ContractorID] = [r.Contractor].[DefendantID]
LEFT JOIN [Defendants] AS [r.Employer] ON [r].[EmployerID] = [r.Employer].[DefendantID]
WHERE ([r].[IsActive] = 1) AND (([r.Employer].[DefendantCode] LIKE (N'%' + 'cert') + N'%' OR [r.Contractor].[DefendantCode] LIKE (N'%' + 'cert') + N'%') OR EXISTS (
SELECT 1
FROM [Records_Products] AS [rp]
INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID]
INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID]
WHERE ([rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%' AND ([rp].[IsActive] = 1)) AND ([r].[RecordID] = [rp].[RecordID])))
) AS [t]
ORDER BY [t].[SourceCode]
OFFSET 0 ROWS FETCH NEXT 500 ROWS ONLY
Here is the new sql that works better, just not sure how to convert it back to a lambda expression
SELECT [t].[Id]
,[t].[StartDate]
,[t].[EndDate]
,[t].[WitnessName]
,[t].[SourceCode]
,[t].[JobsiteName]
,[t].[ShipName]
,[t].[EmployerCode]
FROM (
SELECT DISTINCT [r].[RecordID] AS [Id]
,[r].[StartDate]
,[r].[EndDate]
,[r.Witness].[FullName] AS [WitnessName]
,CASE
WHEN [r].[SourceID] IS NOT NULL
THEN [r.Source].[SourceCode]
ELSE N'zzzzz'
END AS [SourceCode]
,CASE
WHEN [r].[JobsiteID] IS NOT NULL
THEN [r.Jobsite].[JobsiteName]
ELSE N'zzzzz'
END AS [JobsiteName]
,CASE
WHEN [r].[ShipID] IS NOT NULL
THEN [r.Ship].[ShipName]
ELSE N'zzzzz'
END AS [ShipName]
,CASE
WHEN [r].[EmployerID] IS NOT NULL
THEN [r.Employer].[DefendantCode]
ELSE N'zzzzz'
END AS [EmployerCode]
FROM [Records] AS [r]
LEFT JOIN [Ships] AS [r.Ship] ON [r].[ShipID] = [r.Ship].[ShipID]
LEFT JOIN [Jobsites] AS [r.Jobsite] ON [r].[JobsiteID] = [r.Jobsite].[JobsiteID]
LEFT JOIN [Sources] AS [r.Source] ON [r].[SourceID] = [r.Source].[SourceID]
LEFT JOIN [Witnesses] AS [r.Witness] ON [r].[WitnessID] = [r.Witness].[WitnessID]
LEFT JOIN [Defendants] AS [r.Contractor] ON [r].[ContractorID] = [r.Contractor].[DefendantID]
LEFT JOIN [Defendants] AS [r.Employer] ON [r].[EmployerID] = [r.Employer].[DefendantID]
LEFT JOIN (
SELECT [rp].[RecordID]
FROM [Records_Products] AS [rp]
INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID]
INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID]
WHERE (
[rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
AND ([rp].[IsActive] = 1)
)
) AS RecordProduct ON [r].[RecordID] = RecordProduct.[RecordID]
WHERE ([r].[IsActive] = 1)
AND (
(
[r.Employer].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
OR [r.Contractor].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
)
OR RecordProduct.RecordID IS NOT NULL --OR EXISTS ( -- SELECT 1 -- FROM [Records_Products] AS [rp] -- INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID] -- INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID] -- WHERE ([rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%' -- AND ([rp].[IsActive] = 1)) AND ([r].[RecordID] = [rp].[RecordID]) -- ) )) AS [t]ORDER BY [t].[SourceCode]OFFSET 0 ROWS FETCH NEXT 500 ROWS ONLY
)
)

The linq expression you supplied and the SQL generated do not match. For one, the linq expression is performing an Include on the various related tables which would have included all of those entity columns in the top-level SELECT which are not present in your example SQL. I also don't see conditions in the Linq expression for the Take 500 & OrderBy, or IsActive assertion on Record.
To be able to help determine the source of any performance concern we need to see the complete Linq expression and the resulting SQL.
Looking at the basis of the Linq expression you provided:
records = records
.Include(r => r.Employer)
.Include(r => r.Contractor)
.Include(r => r.RecordProducts)
.ThenInclude(rp => rp.ProductDefendant.Defendant)
.Where(r => EF.Functions.Like(r.Employer.DefendantCode, "%" + input.DefendantCode + "%")
|| EF.Functions.Like(r.Contractor.DefendantCode, "%" + input.DefendantCode + "%")
|| r.RecordProducts.Any(rp => EF.Functions.Like(rp.ProductDefendant.Defendant.DefendantCode, "%" + input.DefendantCode + "%") && rp.IsActive == true));
There are a few suggestions I can make:
There is no need for the Functions.Like. You should be able to achieve the same with Contains.
Avoid using Include and instead utilize Select to retrieve the columns from the resulting structure that you actually need. Populate these into ViewModels or consume them in the code. The less data you pull back, the better optimized the SQL can be for indexing, and the less data pulled across the wire. Consuming entities also leads to unexpected lazy-load scenarios as systems mature and someone forgets to Include a new relation.
.
records = records
.Where(r => r.IsActive
&& (r.Employer.DefendantCode.Contains(input.DefendantCode)
|| r.Contractor.DefendantCode.Contains(input.DefendantCode)
|| r.RecordProducts.Any(rp => rp.IsActive
&& rp.ProductDefendant.Defendant.DefendantCode.Contains(input.DefendantCode))
.OrderBy(r => r.SourceCode)
.Select(r => new RecordViewModel
{
// Populate the data you want here.
}).Take(500).ToList();
This also adds the IsActive check, OrderBy, and Take(500) based on your sample SQL.

Related

Kentico document query API always returns empty result

I have the following query:
var newsItems = tree.SelectNodes()
.Types(pageTypesArray)
.Path(path)
.OrderBy(orderBy)
.CombineWithDefaultCulture(false)
.Page(page, count)
.OnCurrentSite()
.NestingLevel(-1)
.Culture(CurrentDocument.DocumentCulture)
.InCategories(categories)
.TopN(topN)
.Where("ListableDocumentImage is not null AND ListableDocumentImage != ''")
.Columns(columns);
Which translates like so:
WITH AllData AS
(
SELECT TOP 6 * for brevity, ROW_NUMBER() OVER (ORDER BY [NewsOccurrenceDate] DESC) AS [CMS_RN]
FROM View_CMS_Tree_Joined AS V WITH (NOLOCK, NOEXPAND) INNER JOIN SOS_News AS C WITH (NOLOCK) ON [V].[DocumentForeignKeyValue] =
[C].[NewsID] AND V.ClassName = N'News' LEFT OUTER JOIN COM_SKU AS S WITH (NOLOCK) ON [V].[NodeSKUID] = [S].[SKUID]
WHERE [NodeSiteID] = #NodeSiteID AND (([DocumentCanBePublished] = 1 AND ([DocumentPublishFrom] IS NULL OR [DocumentPublishFrom] <= #Now)
AND ([DocumentPublishTo] IS NULL OR [DocumentPublishTo] >= #Now))
AND [NodeAliasPath] LIKE #NodeAliasPath AND [DocumentCulture] = #DocumentCulture
**AND 0 = 1)** <<<------------------- WHERE DID THIS COME FROM?????
)
SELECT *, (SELECT COUNT(*) FROM AllData) AS [CMS_TOT]
FROM AllData
WHERE CMS_RN BETWEEN 4 AND 6
ORDER BY CMS_RN
Has anyone ever come across anything like this before? I can't figure out why they're sticking in the AND 0=1 in my where clause.
Properly structuring your document API call will help with this. In your query results, you can see your WHERE condition is not even being added so it does make a difference the order of the methods being called.
For instance:
var newsItems = tree.SelectNodes()
.Types(pageTypesArray)
.Path(path)
.Where("ListableDocumentImage is not null AND ListableDocumentImage != ''")
.TopN(topN)
.OrderBy(orderBy)
.CombineWithDefaultCulture(false)
.Page(page, count)
.NestingLevel(-1)
.Culture(CurrentDocument.DocumentCulture)
.InCategories(categories)
.OnCurrentSite()
.Columns(columns);

EF Core FromSql Returning Odd Error Message

This code throws an exception when rawVoters.Count() is called:
string sql = #"select * from Voters as v
inner join Homes as h on v.HomeID = h.ID
inner join Locations as l on h.LocationID = l.ID
inner join Streets as s on l.StreetID = s.ID
inner join Cities as c on s.CityID = c.ID
inner join VoterAgencies as va on v.CountyID = va.CountyID and v.VoterID = va.VoterID
where (va.AgencyID = #agencyID)
and (c.Name like '%' + #city + '%')
and (v.FirstName like '%' + #firstName + '%')
and (v.LastName like '%' + #lastName + '%')
and (s.Name like '%' + #street + '%')
and ((#voterID = 0) or (v.VoterID = #voterID))";
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add( new SqlParameter( "#agencyID", agencyID ) );
parameters.Add( new SqlParameter( "#city", model.City ) );
parameters.Add( new SqlParameter( "#firstName", model.FirstName ) );
parameters.Add( new SqlParameter( "#lastName", model.LastName ) );
parameters.Add( new SqlParameter( "#street", model.Street ) );
parameters.Add( new SqlParameter( "#voterID", model.VoterID ) );
IQueryable<Voter> rawVoters = _context.Voters
.AsNoTracking()
.FromSql( sql, parameters.ToArray() );
int numVoters = 0;
try
{
numVoters = rawVoters.Count();
}
catch( Exception e )
{
int i = 9;
i++;
}
The error message is:
"The column 'ID' was specified multiple times for 'v'."
I thought this might be because EF Core doesn't like the "as x" phrasing, so I substituted the table names for each of the identifiers...and got the same error message.
I'm curious as to what's going on here.
The problem was that the T-SQL was returning all fields (select *). Under EF Core, the returned fields must match the fields specified for the entity being returned, in this case Voter.
An inner join, like the one I was using, by default returns far more than just the Voter fields.
Changing the SQL to be select v.* (where v is the alias for Voters) solved the problem.
Hence you're just getting the Count,you can specify your column name as shown below.Then no issues :)
string sql = #"select v.ID from Voters as v

Trouble with sums and a having clause

I have a query that populates a report of "written off" hours, the total of which is the sum RegHrs, OvtHrs, and SpecialOvtHrs, but only those values with a positive value (each of these fields may have positive and negative values - positive values are "written off").
The query I am using (which doesn't work) is:
select
LD.Employee,
max(EM.LastName + ', ' + Em.FirstName) as EMName,
LD.WBS1, LD.WBS2,
sum(LD.RegHrs + LD.OvtHrs + LD.SpecialOvtHrs) as [Hours],
CL.Name as ClientName, pctf.CustProgram,
max(PR.Name) as ProjName,
LD.PKey, ISNULL(BillingDirectives.Comment, 'None')
from AnvilProd..LD
left join AnvilProd..PR on LD.WBS1 = PR.WBS1 and PR.WBS2 = ' ' and PR.WBS3 = ' '
left join AnvilProd..EM on LD.Employee = EM.Employee
left join AnvilProd..CL on PR.ClientID = CL.ClientID
left join AnvilProd..ProjectCustomTabFields pctf on PR.WBS1 = pctf.WBS1 and pctf.WBS2 = ' ' and pctf.WBS3 = ' '
left join InterfaceDev..BillingDirectives on BillingDirectives.PKey = LD.PKey
where LD.BillStatus = 'X'
and LD.WrittenOffPeriod = #custPeriod
and LD.WBS1 not in (select distinct WBS1 from AnvilProd..BT where FeeBasis = 'L')
and LD.WBS1 not in (select distinct WBS1 from InterfaceDev..CircledHoursReportEliminatedJobs where ActiveStatus = 'Active')
group by pctf.CustProgram, CL.Name, LD.WBS1, LD.WBS2, LD.Employee, BillingDirectives.Comment, LD.PKey
-- having ((sum(LD.RegHrs) > 0) or (sum(LD.OvtHrs) > 0) or (sum(LD.SpecialOvtHrs) > 0))
order by pctf.CustProgram, CL.Name, LD.WBS1, WBS2, EMName
I need to find written off hours for each Employee, WBS1, WBS2 combination.
I have tried a dozen different things with that having clause and cannot get it to give me an accurate result.
Use a case inside the sum():
select blah, blah,
sum(
case when LD.RegHrs > 0 then LD.RegHrs else 0 end +
case when LD.OvtHrs > 0 then LD.OvtHrs else 0 end +
case when LD.SpecialOvtHrs > 0 then LD.SpecialOvtHrs else 0 end
) as [Hours], blah, blah
from blah join blah ...
group by blah, blah
-- no having clause
As a mathematical curiosity, you could also code the sum this way:
sum((LD.RegHrs + abs(LD.RegHrs) +
(LD.OvtHrs + abs(LD.OvtHrs) +
(LD.SpecialOvtHrs + abs(LD.SpecialOvtHrs)) / 2
which although is a bit less readable, it uses less code and may impress your colleagues more :)

Possible Bug in JDBC?

I am currently facing a weird problem.
Whenever a user types something into the search bar that starts with an 's', the request crashes.
What you see next is a sample sql code generated by the search engine I programmed for this project.
SELECT Profiles.ProfileID,Profiles.Nickname,Profiles.Email,Profiles.Status,Profiles.Role,Profiles.Credits, Profiles.Language,Profiles.Created,Profiles.Modified,Profiles.Cover,Profiles.Prename, Profiles.Lastname,Profiles.BirthDate,Profiles.Country,Profiles.City,Profiles.Phone,Profiles.Website, Profiles.Description, Profiles.Affair,Scores.AvgScore, coalesce(Scores.NumScore, 0) AS NumScore, coalesce(Scores.NumScorer, 0) AS NumScorer, (
(SELECT count(*)
FROM Likes
JOIN Comments using(CommentID)
WHERE Comments.ProfileID = Profiles.ProfileID)) NumLikes, (
(SELECT count(*)
FROM Likes
JOIN Comments using(CommentID)
WHERE Comments.ProfileID = Profiles.ProfileID) /
(SELECT coalesce(nullif(count(*), 0), 1)
FROM Comments
WHERE Comments.ProfileID = Profiles.ProfileID)) AvgLikes, Movies.MovieID, Movies.Caption, Movies.Description, Movies.Language, Movies.Country, Movies.City, Movies.Kind, Movies.Integration,
(SELECT cast(least(25 + 5.000000 * round((75 * ((0.500000 * SIZE/1024.0/1024.0 * 0.001250) + (0.500000 * Duration/60.0 * 0.050000))) / 5.000000), 100) AS signed int)
FROM Streams
WHERE MovieID = Movies.MovieID
AND Tag = "main"
AND ENCODING = "mp4") AS ChargeMain,
(SELECT cast(least(25 + 10.000000 * round((75 * ((0.200000 * SIZE/1024.0/1024.0 * 0.001000) + (0.800000 * Duration/60.0 * 0.016667))) / 10.000000), 100) AS signed int)
FROM Streams
WHERE MovieID = Movies.MovieID
AND Tag = "notes"
AND ENCODING = "mp4") AS ChargeNotes,
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "main") AS MainViews,
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "notes") AS NotesViews,
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "trailer") AS TrailerViews,
(SELECT coalesce(greatest(
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "trailer"),
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "main")), 0)) AS MaxMainTrailerViews,
(SELECT avg(Score)
FROM Scores
WHERE Scores.MovieID = Movies.MovieID) AS Score,
(SELECT coalesce(group_concat(cast(Score AS signed int)), "")
FROM Scores
WHERE Scores.MovieID = Movies.MovieID) AS Scores, Movies.Cover, Movies.Locked, Movies.Created, Movies.Modified,
(SELECT coalesce(group_concat(name separator ','),"")
FROM Tags
JOIN TagLinks using(TagID)
WHERE TagLinks.MovieID = Movies.MovieID
ORDER BY name ASC) AS Tags,
(SELECT count(*)
FROM Purchases
WHERE MovieID = Movies.MovieID
AND ProfileID = %s
AND TYPE = "main") AS PurchasedMain,
(SELECT count(*)
FROM Purchases
WHERE MovieID = Movies.MovieID
AND ProfileID = %s
AND TYPE = "notes") AS PurchasedNotes,
(SELECT count(*)
FROM Watchlist
WHERE MovieID = Movies.MovieID
AND ProfileID = %s) AS Watchlist,
(SELECT count(*)
FROM Scores
WHERE MovieID = Movies.MovieID
AND ProfileID = %s) AS Rated,
(SELECT count(*)
FROM Comments
WHERE MovieID = Movies.MovieID
AND Deleted IS NULL) AS Comments,
(SELECT sum(Duration)
FROM Streams
WHERE Streams.MovieID = Movies.MovieID
AND Streams.Tag IN ("main",
"notes")
AND Streams.ENCODING = "mp4") AS Runtime,
(SELECT cast(count(*) AS signed int)
FROM Movies
JOIN Profiles ON Profiles.ProfileID = Movies.ProfileID
WHERE ((Movies.Locked = 0
AND
(SELECT count(*)
FROM Streams
WHERE Streams.MovieID = Movies.MovieID
AND Streams.Status <> "ready") = 0
AND Profiles.Status = "active")
OR (%s = 1)
OR (Movies.ProfileID = %s))
AS Movies,
(SELECT cast(ceil(count(*) / %s) AS signed int)
FROM Movies
JOIN Profiles using(ProfileID)
WHERE ((Movies.Locked = 0
AND
(SELECT count(*)
FROM Streams
WHERE Streams.MovieID = Movies.MovieID
AND Streams.Status <> "ready") = 0
AND Profiles.Status = "active")
OR (%s = 1)
OR (Movies.ProfileID = %s))
AS Pages
FROM Movies
JOIN Profiles using(ProfileID)
LEFT JOIN
(SELECT Movies.ProfileID AS ProfileID,
avg(Scores.Score) AS AvgScore,
count(*) AS NumScore,
count(DISTINCT Scores.ProfileID) AS NumScorer
FROM Scores
JOIN Movies using(MovieID)
GROUP BY Movies.ProfileID) AS Scores using(ProfileID)
WHERE ((Movies.Locked = 0
AND
(SELECT count(*)
FROM Streams
WHERE Streams.MovieID = Movies.MovieID
AND Streams.Status <> "ready") = 0
AND Profiles.Status = "active")
OR (%s = 1)
OR (Movies.ProfileID = %s))
ORDER BY Score DESC LIMIT %s,
%s
After countless hours of investigating and comparing possible user inputs with the generated sql code I finally nailed the problem down to some really strange behaviour of the JDBC driver which I consider a serious bug - yet I am not sure:
I spent another few hours trying to reproduce the problem with as less sql code as possible and ended up with the following:
SQL("""select * from Movies where "s" like "%s" and MovieID = {a} """)
.on('a -> 1).as(scalar[Long]*)
[SQLException: Parameter index out of range (1 > number of parameters, which is 0).]
SQL("""select * from Movies where "s" like "%samuel" and MovieID = {a} """)
.on('a -> 1).as(scalar[Long]*)
[SQLException: Parameter index out of range (1 > number of parameters, which is 0).]
SQL("""select * from Movies where "s" like "%flower" and MovieID = {a} """)
.on('a -> 1).as(scalar[Long]*)
[OK]
SQL("""select * from Movies where "s" like "%samuel" and MovieID = 1 """)
.on('a -> 1).as(scalar[Long]*)
[OK]
SQL("""select * from Movies where "s" like "%s" and MovieID = "{a}" """)
.on('a -> 1).as(scalar[Long]*)
[OK]
SQL("""select * from Movies where MovieID = {a} and "s" like "%s" """)
.on('a -> 1).as(scalar[Long]*)
[OK]
I believe to see a pattern here:
Under the exact condition that there is a %s sequence (quoted or unquoted) anywhere in a sql code, followed by a non quoted named parameter with arbitrary name and arbitrary distance
to the %s sequence, jdbc (or anorm) crashes. The crash seems to occur in JDBC, however its also possible that Anorm submits invalid values to JDBC.
Do you guys have any suggestions?
I think I found an enduring solution for the problem meanwhile. Since my sql generator needs to stay very flexible I somehow need a way to pass along sql fragments with their corresponding parameters without evaluating them right away. Instead the generator must be able to assemble and compose various sql fragments into bigger fragments at any time - just as he does now - but now with the acompanying, not yet evaluated parameters. I came up with this prototype:
DB.withConnection("betterdating") { implicit connection =>
case class SqlFragment(Fragment: String, Args: NamedParameter*)
val aa = SqlFragment("select MovieID from Movies")
val bb = SqlFragment("join Profiles using(ProfileID)")
val cc = SqlFragment("where Caption like \"%{a}\" and MovieID = {b}", 'a -> "s", 'b -> 5)
// combine all fragments
val v1 = SQL(Seq(aa, bb, cc).map(_.Fragment).mkString(" "))
.on((aa.Args ++ bb.Args ++ cc.Args): _*)
// better solution
val v2 = Seq(aa, bb, cc).unzip(frag => (frag.Fragment, frag.Args)) match {
case (frags, args) => SQL(frags.mkString(" ")).on(args.flatten: _*)
}
// works
println(v1.as(scalar[Long].singleOpt))
println(v2.as(scalar[Long].singleOpt))
}
It seems to work great! :-)
I then rewrote the last part of the freetext filter as follow:
// finally transform the expression
// list a single sql fragment
expressions.zipWithIndex.map { case (expr, index) =>
s"""
(concat(Movies.Caption, " ", Movies.Description, " ", Movies.Kind, " ", Profiles.Nickname, " ",
(select coalesce(group_concat(Tags.Name), "") from Tags join TagLinks using (TagID)
where TagLinks.MovieID = Movies.MovieID)) like "%{expr$index}%"))
""" -> (s"expr$index" -> expr)
}.unzip match { case (frags, args) => SqlFragment(frags.mkString(" and "), args.flatten: _*)
What do you think?
This is how it is being implemented right now:
/**
* This private helper method transforms a content filter string into an sql expression
* for searching within movies, owners and kinds and tags.
* #author Samuel Lörtscher
*/
private def contentFilterToSql(value: String) = {
// trim and clean and the parametric value from any possible anomalies
// (those include strange spacing and non closed quotes)
val cleaned = value.trim match {
case trimmed if trimmed.count(_ == '"') % 2 != 0 =>
if (trimmed.last == '"') trimmed.dropRight(1).trim
else trimmed + '"'
case trimmed =>
trimmed
};
// transform the cleaned value into a list of expressions
// (words between quotes are considered being one expression)
// empty expressions between quotes are being removed
// expressions will contain no quotes as they are being stripped during evaluation -
// thus counter measures for sql injection should be obsolete
// (we put an empty space at the end because it makes the lexer algorithm much
// more efficient as it will not need to check for end of file in every iteration)
val expressions = (cleaned + " ").foldLeft((List[String](), "", false)) { case ((list, expr, quoted), char) =>
// perform the lexer operation for the current character
if (char == ' ' && !quoted) (expr :: list, "", false)
else if (char == '"') (expr :: list, "", !quoted)
else (list, expr + char, quoted)
}._1.filter(_.nonEmpty).map(_.trim)
// finally transform the expression
// list into a variable length sql condition statement
expressions.map { expr =>
s"""
(concat(Movies.Caption, " ", Movies.Description, " ", Movies.Kind, " ", Profiles.Nickname, " ",
(select coalesce(group_concat(Tags.Name), "")
from Tags join TagLinks using (TagID) where TagLinks.MovieID = Movies.MovieID)) like "%$expr%")
"""
}.mkString(" and ")
}
Since the number of search expressions is variable, I cannot use Anorm arguments here. :-/
I found a simple solution now, but I am not exactly happy being forced to apply such crappy hacks.
Since putting a %s character sequence seems to trigger the bug, I was looking for possibilities to submit the same semantical outcome without directly passing this character sequence. I finally ended up replacing like "%$expr%" by like concat("%", "$expr%"). Since concat is being evaluated by the MySql Server engine BEFORE "like", he will put the original pattern back together before processing it by "like" - and without the sequence %s ever being transmitted through the anorm, jdbc data processors.
// finally transform the expression
// list into a variable length sql condition statement
// (freaking concat("%", "$expr%")) is required due to a freaking bug in either anorm or JDBC
// which results into a crash when %s is anyway submitted)
expressions.map { expr =>
s"""
(concat(Movies.Caption, " ", Movies.Description, " ", Movies.Kind, " ", Profiles.Nickname, " ",
(select coalesce(group_concat(Tags.Name), "")
from Tags join TagLinks using (TagID) where TagLinks.MovieID = Movies.MovieID)) like concat("%", "$expr%"))
"""
}.mkString(" and ")

sql query into Linq

Please anyone can help me to write this sql query into Linq.
select
P.ID,
P.Name,
Set_selected=
case when exists(
select C.ClassifierID
from dbo.ProductClassifiers C
where C.ProductID=130 and C.ClassifierID=P.ID)
then 'Yes' else 'No' end
from dbo.Classifier P
var retVal = (from s in dataContext.ProductClassifiers
join k in dataContext.Classifier
on s.ClassifierId equals k.Id
where s.ProductId == 30
select new {write here what values you want to get like s.Id,k.Name etc}).ToList();
Here's an attempt:
var query = from p in dataContext.Classifiers
select new {
p.ID,
p.Name,
p.Selected = dataContext.ProductClassifiers
.Where(c => c.ProductID == 130 &&
c.ClassifierID == p.ID)
.Any()
};
(That will make the Selected property Boolean rather than Yes/No, but that's usually going to be easier to work with.)
You should look at what the translated SQL looks like though, and in particular what the query plan is like compared with your original.
Untested, but hopefully works:
var q = classifier.Select(
p => new {
p.ID,
p.Name,
Set_selected = productClassifiers
.Select(c => c.ProductID == 130 && c.ClassifierID == p.ID)
.Any() ? "Yes" : "No"
}
);
The code assumes that you have two IEnumerable<T> representing the Classifier and ProductClassifiers tables.