How to conveniently rewrite LINQ query? - c#

I have realized that SQL Server is unable efficiently process some basic SQL queries, such as:
SELECT TOP (1) [t0].[Id], [t0].[L1], [t0].[L2], [t0].[Value]
FROM [Foos] AS [t0]
INNER JOIN [Lookup1] AS [t1] ON [t1].[Id] = [t0].[L2]
INNER JOIN [Lookup2] AS [t2] ON [t2].[Id] = [t0].[L1]
WHERE ([t1].[Name] = 'a') AND ([t2].[Name] = 'b')
ORDER BY [t0].[Value]
which is generated from LINQ expression:
// query 1
Foos
.Where(f => f.Lookup1.Name == "a" && f.Lookup2.Name == "b")
.OrderBy(f => f.Value)
.Take(1)
The schema definition is in question 'Index over multiple lookup tables in SQL Server'. #Hoots in the answer shows that the SQL query must look like:
SELECT TOP (1) [t0].[Id], [t0].[L1], [t0].[L2], [t0].[Value]
FROM [Foos] AS [t0]
CROSS JOIN (
SELECT TOP (1) [t1].[Id], [t2].[Id] AS [Id2]
FROM [Lookup1] AS [t1], [Lookup2] AS [t2]
WHERE ([t1].[Name] = 'a') AND ([t2].[Name] = 'b')
) AS [t3]
WHERE ([t0].[L1] = [t3].[Id]) AND ([t0].[L2] = [t3].[Id2])
ORDER BY [t0].[Value] DESC
which could be generated from the following LINQ expression:
// query 2
(from f in Foos
from l in (
from l1 in Lookup1s
from l2 in Lookup2s
where l1.Name == "a"
&& l2.Name == "b"
select new { L1 = l1.Id, L2 = l2.Id }).Take(1)
where f.L1 == l.L1 && f.L2 == l.L2
orderby f.Value descending
select f).Take(1)
My question is how to automatically rewrite the query 1 into query 2? So I could compose queries in multiple steps:
void Do()
{
var x = ListFoos("a", "b").OrderBy(f => f.Value).Take(2);
// ...
}
IQueryable<Foos> ListFoos(string l1, string l2)
{
var foos = Foos.AsQueryable();
if (l1 != null)
foos = foos.Where(f => f.Lookup1.Name == l1);
if (l2 != null)
foos = foos.Where(f => f.Lookup2.Name == l2);
return foos;
}
Has someone done that already? Is there a library simplifying the task?
Clarification:
The resulting expression of IQueryable<> is translated into SQL statement which SQL Server is unable efficiently evaluate. So I need to transform the expression into an expression which is translated into better SQL statement for SQL Server.
I think that I am not the first who has encountered this issue. LINQ is a longer time here and the SQL statements are pretty basic, so other developers might already have been solving this problem with SQL Server.

I'm not 100% certain I know what you're asking for, but if I am right, you should look at PredicateBuilder. It's very useful. Link here:
http://www.albahari.com/nutshell/predicatebuilder.aspx
It's part of LinqKit:
http://www.albahari.com/nutshell/linqkit.aspx
and here's some info on how it's used:
How does PredicateBuilder work
It will basically let you do something like this:
var predicate = PredicateBuilder.True<Foo>();
if (l1 != null)
predicate = predicate.And(f => f.Lookup1.Name == l1);
if (l2 != null)
predicate = predicate.Or(f => f.Lookup2.Name == l2);
return Foos.Where(predicate);
Note the above is from memory.. I have not tested this...so might have some typos...

Related

How can I make this LINQ to SQL where clause with conditions faster?

I have the following LINQ to SQL query but I want to know if there is a faster way to validate data from a post action before adding them in a where clause? ex:
bookFilter = Informations coming from the post action
int count = from b in _libraryContext.Book
join ba in _libraryContext.BookAuthor
on b.Id equals ba.BookId
where (!string.IsNullOrWhiteSpace(bookFilter.Name) ?
b.Name.Contains(bookFilter.Name.ToUpper()) : 1 == 1 )
where (!string.IsNullOrWhiteSpace(bookFilter.Decription) ?
b.Description.Contains(bookFilter.Description.ToUpper()) : 1 == 1)
where (bookFilter.BookId > 0 ? ba.BookId == bookFilter.BookId : 1 == 1)
return count;
I've never used this type of syntax much so I'm sure if you can do it this way, but you can certainly do it with LINQ and build up your query step by step like so:
var query = _libraryContext.Set<Book>();
if(!string.IsNullOrWhiteSpace(bookFilter.Name))
{
query = query.Where(x => x.Name.Contains(bookFilter.Name.ToUpper()));
}
if(!string.IsNullOrWhiteSpace(bookFilter.Description))
{
query = query.Where(x => x.Description.Contains(bookFilter.Description.ToUpper()));
}
if(bookFilter.BookId > 0)
{
query = query.Where(x => x.BookId == bookFilter.Id);
}
return query.Count();
Note: I have omitted the JOIN here as it seems unnecessary, but you can of course do the join in this syntax too if you need it.
You have accepted the solution that #Akinzekeel provided above. However, in the case that you are trying to use a single LINQ query, I would go for the solution below:
bookFilter = Informations coming from the post action
Please note here bookFilter has to be IQueryable!
int count = from b in _libraryContext.Book
join ba in _libraryContext.BookAuthor
on b.Id equals ba.BookId
where (b.Name.Contains(bookFilter.Name.ToUpper()) || string.IsNullOrWhiteSpace(bookFilter.Name)) &&
(b.Description.Contains(bookFilter.Description.ToUpper()) || string.IsNullOrWhiteSpace(bookFilter.Decription)) &&
(ba.BookId == bookFilter.BookId || bookFilter.BookId == 0 )
return count;
If you trace the code above in SQL Profiler, you will see this will generate a single SQL query; something like (OR IS NULL) conditions.
In SQL you could use COALESCE to default ignore parameters -- like the following:
SELECT *
FROM book b
JOIN BookAuthor ba on b.id = ba.Bookid
WHERE COALESCE(UPPER(name), UPPER(b.name)) = UPPER(b.name)
AND COALESCE(UPPER(description), UPPER(b.description)) = UPPER(b.description)
AND COALESCE(bookid, b.book.id) = b.bookid
Recommended pattern is to put something like that in a SP and then call the SP.

Convert SQL query to LINQ or lambda expression in C# and use in EF Core

I have 3 table
Tbl_City , Tbl_GroupCities , Tbl_CtrCar .
I want to convert this SQL query to LINQ or lambda expression in C#
declare #fk_group uniqueidentifier
SELECT #fk_group= FK_Group
FROM dbo.Tbl_User
WHERE UserName='meysam'
SELECT dbo.Tbl_City.ID_City, dbo.Tbl_City.Name_City,COUNT( dbo.Tbl_CtrCar.Cur_year)
FROM dbo.Tbl_City
INNER JOIN dbo.Tbl_CtrCar ON dbo.Tbl_City.ID_City = dbo.Tbl_CtrCar.FK_City
WHERE ID_City IN (SELECT FK_City
FROM dbo.Tbl_GroupCities
WHERE Active=1 AND ID_Group=#fk_group)
GROUP BY ID_City , Name_City
I try it but it's not work
var model = _TblUser.FirstOrDefault(x => x.UserName == "sampleUserName");
var q = _TblGroupCities.Where(x => x.IdGroup == model.FkGroup && x.Active == true);
var sample2 =
(from x in _TblCity
join a in _TblGroupCities on x.IdCity equals a.FkCity
where a.Active == true && a.IdGroup == model.FkGroup
select new
{
x.IdCity,
x.NameCity
}).ToList();
Please take a look here the features you have in your query are not yet implemented. GroupBy and i think also subselects will do an
SELECT * FROM TableName
And in memory it will do the group by or even for each row a new SQL query.
Better to use the RawSql method for this purpose.
But if you realy want to learn LINQ and convert your SQL take a look at LINQPad
This issue is done. I found my problem, I don't Understand use two joins and use group by in Linq
I use this linq for the solution and run
var model = _TblUser.SingleOrDefault(x => x.UserName == type.UserName);
var q = _TblGroupCities.Where(x => x.IdGroup == model.FkGroup && x.Active == true);
tblCityViewModel = new List<MohasebKhodro.ViewModels.TblCityViewModel>();
var sample2 =
(from x in _TblCity
join a in _TblGroupCities on x.IdCity equals a.FkCity
where a.Active == true && a.IdGroup == model.FkGroup
select new
{
x.IdCity,
x.NameCity
}).ToList();
foreach (var item in sample2)
{
var er = _TblCtrCar.Where(x => x.FkCity == item.IdCity).Max(x => x.CurYear);
tblCityViewModel.Add(new MohasebKhodro.ViewModels.TblCityViewModel
{
IdCity = item.IdCity,
NameCity = item.NameCity,
MaxCurrentYear = Convert.ToString(er)
});
}

"Case" in the order-by statement

Hello I have the following linq statement:
IEnumerable<TabTransaktion> allTransactions
= TabTransaktions1.Union(TabTransaktions2)
.Where(trans => trans.TabVorgang != null).
.OrderBy(tran => tran.TabVorgang.Wirkdatum)
.OrderByDescending(trans2 => trans2.TabVorgang.ID);
But I want the second order by descending when only trans2.TabVorgang.ID equals to 0. So I need a "case" in "order by clause" for LinQ. A LinQ equivalent of something like this:
SELECT BusinessEntityID, SalariedFlag
FROM HumanResources.Employee
ORDER BY CASE SalariedFlag WHEN 1 THEN BusinessEntityID END DESC
,CASE WHEN SalariedFlag = 0 THEN BusinessEntityID END;
GO
I would appreciate any help.
Assuming SalariedFlag is a bool (in SQL of type bit), the two ordering expressions are exclusively mutual. In other words, the main query can be separated into two disjunctive queries and the final result is the union of them:
IEnumerable<TabTransaktion> mainQuery
= TabTransaktions1.Union(TabTransaktions2)
.Where(trans => trans.TabVorgang != null);
var queryOne = mainQuery.Where(p=>p.SalariedFlag ==1)
.OrderByDescending(tran => tran.BusinessEntityID );
var queryTwo = mainQuery.Where(p=>p.SalariedFlag ==0)
.OrderBy(tran => tran.BusinessEntityID);
var finalResult = queryOne.Union(queryTwo);

Linq to objects using 'not exists' and 'group by'?

I've been assigned a new project at work which uses NHibernate. A query that I can easily write in sql has me totally stumped on how to do it in linq, which is how I've been told to do it.
So, here's the query:
select ts.BatchID, COUNT(distinct ts.UniqID) SurveyCount
from TeleformStaging.TeleformStaging ts
where ts.IsRescan = 0
and not exists (select bfr.BatchID
from TeleformStaging.BatchesForRescan bfr
where bfr.BatchTrack = ts.BatchID)
group by ts.BatchID
order by ts.BatchID
I believe I could get the 'group by' portion, but no idea on the subquery.
Thanks for any advice...
Maybe something like this:
var result= (
from ts in db.TeleformStaging
where ts.IsRescan == false //if is a boolean else == 0
&&
!(
from bfr in db.BatchesForRescan
select bfr.BatchTrack
).Contains(ts.BatchID)
orderby ts.BatchID
group ts by ts.BatchID into g
select new
{
BatchID=g.Key,
SurveyCount=g.Select (x =>x.UniqID).Distinct().Count()
}
);
where db is the linq data context
Edit
You could also do it with .Any(). Like this:
var result= (
from ts in db.TeleformStaging
where ts.IsRescan == false //if is a boolean else == 0
&&
!(
from bfr in db.BatchesForRescan
where ts.BatchID==bfr.BatchTrack
select bfr.BatchTrack
).Any()
orderby ts.BatchID
group ts by ts.BatchID into g
select new
{
BatchID=g.Key,
SurveyCount=g.Select (x =>x.UniqID).Distinct().Count()
}
);
Edit 1
Useful links:
Contains
Any
Simple group by
Basic LINQ Query Operations (C#)
Order by
LINQ is slightly reversed, but does offer the complexity with a few lambda expressions; how does this look:
var result = from ts in TeleformStaging.TeleformStaging
where !ts.IsRescan && !TeleformStaging.BatchesForRescan.Any(bfr => bfr.BatchID == ts.BatchID)
group ts by ts.BatchID into tsGrouped
orderby tsGrouped.Key
select new
{
BatchId = tsGrouped.Key,
SurveyCount = tsGrouped.Select(x => x.UniqID).Distinct().Count()
};
from fs in context.TeleformStaging
where !ts.IsRescan && !context.BatchesForRescan.Any(bfr=>bfr.BatchTrack == ts.BatchID)
group ts by ts.BatchID into g
select new
{
BatchID=g.Key,
SurveyCount=g.Select (x =>x.UniqID).Distinct().Count()
}
with group by

LINQ to SQL recursive problem

I want to convert the following query (which is working correctly) from query syntax to LINQ syntax, but I can't seem to get it right:
SELECT
[t1].[ID], [t1].[ParentID], [t1].[FolderName],
[t1].[Purpose], [t1].[IsSystem],t1].IsHidden],
[t1].[ChangeSrc], [t1].[SyncGUID], [t1].[CreationDBTimeStamp]
FROM [dbo].[FileStorageFolders] AS [t0]
INNER JOIN [dbo].[FileStorageFolders] AS [t1] ON ([t0].[ID]) = [t1][ParentID]
WHERE ([t1].[Purpose] = #p0)
AND ([t1].[FolderName] = #p1)
AND ([t0].[ParentID] IS NULL)
AND ([t0].[Purpose] = #p2)
I use this LINQ syntax but the result always null value:
private static FileStorageFolder GetCapsuleContentFolder(FileStorageDataContext db)
{ IQueryable<FileStorageFolder> source = (from dbfolder in db.FileStorageFolders
join dbsubfolder in db.FileStorageFolders on
new { ParentID = dbfolder.ID } equals
new { ParentID = Convert.ToInt32(dbsubfolder.ParentID) }
where
dbfolder.ParentID == null &&
dbfolder.Purpose == 'REPORT' &&
dbsubfolder.Purpose == 'LayoutFolder' &&
dbsubfolder.FolderName == 'LayoutReport'
select new
{
dbsubfolder.ID,
ParentID = (System.Int32?)dbsubfolder.ParentID,
dbsubfolder.FolderName,
dbsubfolder.Purpose,
dbsubfolder.IsSystem,
pqrentID2 = dbfolder.ParentID,
Purpose2 = dbfolder.Purpose
}) as IQueryable<FileStorageFolder>;
return source.Single();
}
Do you have a collection of child foleders on parent? You should if your relation is mapped. In such case you can try:
var query = from dbFolder in db.FileStorageFolders
from subFolder in dbFolder.SubFolders // Here I use that mapped relations
where ...
select new { ... };
The problem with your query is that you are creating anonymous type and converting it to your type - it is not possible. Also it is not full hierarchical query (your SQL is not as well). It will select only one level of sub folders.

Categories