I'm trying to convert a sql stored proc to linq. I'm having issues with the groupby and inner joins.
Here is what I've tried:
var r = _context.Table1
.GroupBy(x => new { x.OptionId, x.Years, x.Strike })
.Join(_context.Table2,
oc => oc.OptionId, o => o.OptionId, (oc, o) => new
{
OptionsCosts = oc,
Options = o
}).Where(x => x.Options.OptionType == 1
&& x.Options.QualifierId != null
&& x.Options.CreditingMethod != "xxx")
.Select(y => new DataModel.Table1()
{
Years = y.Select(a => a.OptionsCosts.Years).FirstOrDefault(),
Strike = y.Select(a => a.OptionsCosts.Strike).FirstOrDefault(),
Value = y.Select(a => a.OptionsCosts.Value).FirstOrDefault(),
ChangeUser = y.Select(a => a.OptionsCosts.ChangeUser).FirstOrDefault(),
ChangeDate = DateTime.Now,
OptionId = y.Select(a => a.OptionsCosts.OptionId).FirstOrDefault()
});
Here is the SQL that I'm trying to convert:
SELECT o2.OptionId, o2.Years, o2.Strike, SUM(d2.Weights) as 'TotalWeight', COUNT(*) as 'Counts'
FROM Table1 o2
INNER JOIN #Dates d2 --this is a temp table that just holds dates. I was thinking just a where statement could do it???
ON d2.EffectiveDate = o2.EffectiveDate
INNER JOIN Table2 od2
ON od2.OptionId = o2.OptionId
AND od2.OptionType = 1
AND od2.qualifierid is null
AND od2.CreditingMethod <> 'xxx' --28095
GROUP BY o2.OptionId,o2.Years, o2.Strike
My data is way off so I'm sure I'm doing something wrong.
var table1=_context.Table1
.groupBy(o2=> new{
o2.OptionId
, o2.Years
, o2.Strike
})
.select(s=> new{
s.key.OptionId
, s.key.Years
, s.key.Strike
,TotalWeight=s.sum(x=>x.Weights)
,Counts=o2.count(c=>c.OptionId)
}).tolist();
var result=table1
.Join(_context.Table2,oc => oc.OptionId, o => o.OptionId, (oc, o) => new{ OptionsCosts = oc, Options = o })
.Where(x => x.Options.OptionType == 1
&& x.Options.QualifierId != null
&& x.Options.CreditingMethod != "xxx")
.select(x=> new {
x.oc.OptionId, x.oc.Years, x.oc.Strike, x.oc.TotalWeight, x.oc.Counts
}).tolist();
Small advise, when you rewriting SQL queries, use LINQ Query syntax which is close to SQL and more effective to avoid errors.
var dates = new List<DateTime>() { DateTime.Now }; // fill list
var query =
from o2 in _context.Table1
where dates.Contains(o2.EffectiveDate)
from od2 in _context.Table1.Where(od2 => // another way to join
od2.OptionId == o2.OptionId
&& od2.OptionType == 1
&& od2.qualifierid == null
&& od2.CreditingMethod != "xxx")
group o2 by new { o2.OptionId, o2.Years, o2.Strike } into g
select new
{
g.Key.OptionId,
g.Key.Years,
g.Key.Strike,
Counts = g.Count()
// SUM(d2.Weights) as 'TotalWeight', -this one is not available because dates in memory
};
If you are on start and trying to rewrite procedures on LINQ - EF Core is bad idea. Too limited IQueryable support and usually you will fight for each complex LINQ query.
Try linq2db which has temporary tables support and your stored proc can be rewritten into identical LINQ queries. Or you can use linq2db.EntityFrameworkCore to extend EF Core functionality.
Disclaimer. I’m creator of this extension and one from linq2db creators.
Related
I have a very basic parent children relationship. For my main page, I just wanted to get the counts from the children table.
var assignmenTotal = new AssignmentUser
{
IsSupervisor = supervisor,
AssignmentTotals = (
from a in db.Assignments
where (StartDate.HasValue)
? DbFunctions.TruncateTime(a.CreatedDate) == StartDate
: a.IsArchived == false
orderby a.ID ascending
join b in db.Adjustments on a.ID equals b.AssignmentID
group b by new {a.ID,a.UserName,a.Status,a.CreatedDate,a.IsArchived}
into g
select new AssignmentTotals
{
ID = g.Key.ID,
UserName = g.Key.UserName,
Status = g.Key.Status,
ImportedDate = DbFunctions.TruncateTime(g.Key.CreatedDate),
StartingLocation = (db.Adjustments
.Where(x => x.AssignmentID == g.Key.ID)
.OrderBy(x => x.LocationID)
.Select(x => x.LocationID)
.FirstOrDefault()),
EndingLocation = (db.Adjustments.
Where(x => x.AssignmentID == g.Key.ID)
.OrderByDescending(x => x.LocationID)
.Select(x => x.LocationID)
.FirstOrDefault()),
TotalLocations = g.Count(x => x.LocationID != null),
TotalLicensePlates = g.Count(x => x.ExpectedLicensePlateID != null),
TotalAdjCompleted = g.Count(x => x.Status == "C"),
IsSameUser = (currUser == g.Key.UserName ? true : false),
IsArchived = g.Key.IsArchived
})
.OrderBy(x => x.ID)
.ToList()
};
Now total flatten rows are about 1000 and this is taking about 10 seconds to complete.
If I write a SQL Query
SELECT ID, UserName, Status, b.StartLocation, b.EndLocation, b.TotalLocations,
b.TotalLicensePlates, b.TotalLocations
FROM Assignments a
INNER JOIN(
SELECT AssignmentID,
min(LocationID) as StartLocation, max(LocationID) as EndLocation,
COUNT(CASE WHEN LocationID is NOT NULL THEN 1 ELSE 0 end) AS TotalLocations,
SUM(CASE WHEN ExpectedLicensePlateID IS NOT NULL THEN 1 ELSE 0 END )TotalLicensePlates,
SUM(CASE WHEN Status = 'C' THEN 1 ELSE 0 END )TotalAdjCompleted
FROM dbo.Adjustments
group by AssignmentID
) b on (a.ID = b.AssignmentID)
WHERE convert(date,a.CreatedDate) ='04/23/2021'
This takes less than a second to complete.
I think my problem is in the linq COUNT part. I have tried doing a subquery but is still slow. I think the problem is that the linq query is bringing all the data to client and doing all the work in the client instead of having the server doing all the work?
Is there a better way to do this?
Edit: I'm using Entity Framework and when I checked the SQL profiler, the SQL send is very long and complicated.
Entity Framework (just like any other programmatic Object Relational Mapper) get worse performance and efficient the more complex your request is.
So, options:
Deal with it. Eh, it's not the best. Clearly it's going to be slow, but if it work, it works.
Use Raw SQL Queries in EF EF6 / core.
Use a different ORM, but you'd need to evaluate those yourself to find the pros and cons.
Forgo SQL in c# entirely and use Stored Procedures, View, Functions, and other SQL objects to keep SQL queries in the database. (thanks Antonín Lejsek)
Problem here, that you have written non equivalent LiNQ Query. There is no optimal prediction what to do with FirstOrDefault in projection. It creates additional OUTER APPLY joins which is slow.
Rewrite your query to be closer to the SQL as possible:
var query =
from a in db.Assignments
where (StartDate.HasValue)
? DbFunctions.TruncateTime(a.CreatedDate) == StartDate
: a.IsArchived == false
orderby a.ID ascending
join b in db.Adjustments on a.ID equals b.AssignmentID
group b by new {a.ID,a.UserName,a.Status,a.CreatedDate,a.IsArchived}
into g
select new AssignmentTotals
{
ID = g.Key.ID,
UserName = g.Key.UserName,
Status = g.Key.Status,
ImportedDate = DbFunctions.TruncateTime(g.Key.CreatedDate),
StartingLocation = g.Min(x => x.LocationID)
EndingLocation = g.Max(x => x.LocationID),
TotalLocations = g.Count(x => x.LocationID != null),
TotalLicensePlates = g.Count(x => x.ExpectedLicensePlateID != null),
TotalAdjCompleted = g.Count(x => x.Status == "C"),
IsSameUser = (currUser == g.Key.UserName ? true : false),
IsArchived = g.Key.IsArchived
};
var totals = query
.OrderBy(x => x.ID)
.ToList();
var assignmenTotal = new AssignmentUser
{
IsSupervisor = supervisor,
AssignmentTotals = totals
};
I have this query :
var ftr_dist = db.DIST_VIEW.Where(x => x.CITY == "Los Angeles");
var mst2 = db.TB_SERVICE.Where(x => x.ID == x.ID);
var trf2 = db.TYPE.Where(x => x.ID == x.ID);
var Data = (from ftr in ftr_dist
join mst in mst2 on ftr.CUSTOMER_ID equals mst.CUSTOMER_ID
join trf in trf2 on mst.TYPE_ID equals trf.ID
select new TypeObj { City = ftr.CITY, County = ftr.COUNTY, Type = trf.Type }
).OrderBy(i => i.City).ThenBy(i => i.County).ToList();
ftr_dist has about 72000 rows. mst2 has 1100000 rows and trf2 has 340 rows. But it takes too long to get Data. How can I make this query faster? Thanks.
you are essentially performing 4 different queries. Linq does take longer to query than sql strings... try this staying with Linq. Combine all of the queries into one.
I am a little confused in your lambda queries the (x => x.ID == x.ID). You may need to add this to the below
var data = from ftr in db.DIST_VIEW
join mst in db.TB_SERVICE on ftr.CUSTOMER_ID equals
mst.CUSTOMER_ID
join trf in db.TYPE on trf.TYPE_ID equals ftr.ID
where ftr.CITY == "Los Angeles"
select new TypeObj
{
City = ftr.City,
Country = ftr.County,
Type - trf.Type
}.OrderBy(i => i.City).ThenBy(i => i.County).ToList();
I am trying to convert the following SQL:
select * from business
left outer join permissions on permissions.permid = busid
and anotherid = 17
into a C# lambda expression, then convert it to an enumerable and do another filter on it. I have tried the following code:
IEnumerable<DTO_business> business= db.business
.Join(db.permissions,
bus => bus.busid,
perm => perm.perm_busid,
(bus, perm) => new { bus, perm })
.Where(e => e.perm.anotherid == 17).DefaultIfEmpty()
.AsEnumerable()
.Where(User.IsInRole("Administrator")
.Select(bus =>
new DTO_business()
{
BusinessID = bus.bus.busid.Convert(),
BusinessName = bus.bus.busname.Convert()
});
But I belive it's not working as the where is outside the join. I'm not sure how to actually get that where within the join and then run DefaultIfEmpty() on the join which should give me the left join.
EDIT: The bit I cannot get working is: and anotherid = 17. If I put it in a .Where in linq it filters it completely and is not part of the left join.
For me LINQ join clause are always simple with query syntax. If you prefer it, this is how you can do:-
var result = from permission in db.permissions
join business in db.business
on permission.permid equals business.busid into b
from bus in b.DefaultIfEmpty()
where permission.anotherid == 17
select new DTO_business()
{
BusinessID = bus != null ? bus.busid : 0,
BusinessName = bus != null ? bus.busname : String.Empty
};
Query syntax would be easier imho but you could do it using GroupJoin like this:
var business= db.business
.GroupJoin(
db.permissions,
bus => bus.busid,
perm => perm.perm_busid,
(bus, perm) => new { bus, perm })
.SelectMany(
z => z.permissions.DefaultIfEmpty(),
(x, y) => new { Business = x.Bus, Permission = y })
.Where(z => z.Permission.anotherid == 17)
.Select(s => new DTO_business
{
BusinessID = s.Business.busid.Convert(),
BusinessName = s.Business.busname.Convert()
});
Reference: LEFT OUTER JOIN in LINQ
You can use composite key in the join condition.
from p in db.permissions
join b in db.business
on new { BusId = b.busid, AnotherId= b.anotherId}
equals new { BusId = p.perm_busid, AnotherId = 17 } into all
from b in all.DefaultIfEmpty()
select new DTO_business
{
BusinessID = b.busid, // check for null reference
BusinessName = b.busname // check for null reference
};
You can use composite keys similarly in extension syntax also.
I see in your code you are using Convert in the select function. This is not allowed in Linq that convert to Sql.
i have 4 table in SQL: DocumentType,ClearanceDocument,Request, RequestDocument.
i want when page load and user select one request, show all Document Based on clearanceType in RequestTable and check in RequestDocument and when exist set is_exist=true
I have written this query with SqlServer Query Editor for get result this Scenario but i can't convert this Query to Linq
select *,
is_Orginal=
(select is_orginal from CLEARANCE_REQUEST_DOCUMENT
where
DOCUMENT_ID=a.DOCUMENT_ID and REQUEST_ID=3)
from
DOCUMENT_TYPES a
where
DOCUMENT_ID in
(select DOCUMENT_ID from CLEARANCE_DOCUMENTS dt
where
dt.CLEARANCE_ID=
(SELECT R.CLEARANCE_TYPE FROM CLEARANCE_REQUEST R
WHERE
R.REQUEST_ID=3))
i write this Query in linq but not work
var list = (from r in context.CLEARANCE_REQUEST
where r.REQUEST_ID == 3
join cd in context.CLEARANCE_DOCUMENTS on r.CLEARANCE_TYPE equals cd.CLEARANCE_ID
join dt in context.DOCUMENT_TYPES on cd.DOCUMENT_ID equals dt.DOCUMENT_ID into outer
from t in outer.DefaultIfEmpty()
select new
{
r.REQUEST_ID,
cd.CLEARANCE_ID,
t.DOCUMENT_ID,
t.DOCUMENT_NAME,
is_set=(from b in context.CLEARANCE_REQUEST_DOCUMENT where
b.REQUEST_ID==r.REQUEST_ID && b.DOCUMENT_ID==t.DOCUMENT_ID
select new{b.IS_ORGINAL})
}
).ToList();
I want convert this Query to LINQ. Please help me. Thanks.
There is no need to manually join objects returned from an Entity Framework context.
See Why use LINQ Join on a simple one-many relationship?
If you use the framework as intended your job will be much easier.
var result = var clearanceTypes = context.CLEARANCE_REQUEST
.Single(r => r.REQUEST_ID == 3)
.CLEARANCE_DOCUMENTS
.SelectMany(dt => dt.DOCUMENT_TYPES)
.Select(a => new
{
DocumentType = a,
IsOriginal = a.CLEARANCE_REQUEST_DOCUMENT.is_original
});
Since your query won't be executed untill you iterate over the data, you can split your query in several subqueries to help you obtain the results like this:
var clearanceIds = context.CLEARANCE_REQUEST
.Where(r => r.REQUEST_ID == 3)
.Select(r => r.CLEARANCE_TYPE);
var documentIds = context.CLEARANCE_DOCUMENTS
.Where(dt => clearanceIds.Contains(dt.CLEARANCE_ID))
.Select(dt => dt.DOCUMENT_ID);
var result = context.DOCUMENT_TYPES
.Where(a => documentIds.Contains(a.DOCUMENT_ID))
.Select(a => new
{
// Populate properties here
IsOriginal = context.CLEARANCE_REQUEST_DOCUMENT
.Single(item => item.DOCUMENT_ID == a.DOCUMENT_ID &&
item.REQUEST_ID == 3)
.IS_ORIGINAL
})
.ToList();
I have a simple LINQ lambda join query but I want to add a 3rd join with a where clause. How do I go about doing that?
Here's my single join query:
var myList = Companies
.Join(
Sectors,
comp => comp.Sector_code,
sect => sect.Sector_code,
(comp, sect) => new {Company = comp, Sector = sect} )
.Select( c => new {
c.Company.Equity_cusip,
c.Company.Company_name,
c.Company.Primary_exchange,
c.Company.Sector_code,
c.Sector.Description
});
I want to add the following SQL command to the above LINQ query and still maintain the projections:
SELECT
sector_code, industry_code
FROM
distribution_sector_industry
WHERE
service = 'numerical'
The 3rd join would be made with Sector table & Distribution_sector_industry on sector_code.
Thanks in advance.
Just a guess:
var myList = Companies
.Join(
Sectors,
comp => comp.Sector_code,
sect => sect.Sector_code,
(comp, sect) => new { Company = comp, Sector = sect })
.Join(
DistributionSectorIndustry.Where(dsi => dsi.Service == "numerical"),
cs => cs.Sector.Sector_code,
dsi => dsi.Sector_code,
(cs, dsi) => new { cs.Company, cs.Sector, IndustryCode = dsi.Industry_code })
.Select(c => new {
c.Company.Equity_cusip,
c.Company.Company_name,
c.Company.Primary_exchange,
c.Company.Sector_code,
c.Sector.Description,
c.IndustryCode
});
Okay, I can't see why you'd want to select sector_code when you already know it, but I think you want this:
var query = from company in Companies
join sector in Sectors
on company.SectorCode equals sector.SectorCode
join industry in DistributionSectorIndustry
on sector.SectorCode equals industry.SectorCode
where industry.Service == "numerical"
select new {
company.EquityCusip,
company.CompanyName,
company.PrimaryExchange,
company.SectorCode,
sector.Description,
industry.IndustryCode
};
Notes:
I've changed it into a query expression as that's a much more readable way of expressing a query like this.
Although the "where" clause comes after the join, assuming this is a LINQ to SQL or Entity Framework query, it shouldn't make any difference
I've lengthened the range variable names for clarity
I've converted your other names into conventional .NET names; you can do this too in your model
For 4 Tables
var query = CurrencyDeposits
.Join(Customers, cd => cd.CustomerId, cus => cus.Id, (cd, cus)
=> new { CurrencyDeposit = cd, Customer = cus })
.Join(Currencies, x => x.CurrencyDeposit.CurrencyId, cr => cr.Id, (x, cr)
=> new { x.CurrencyDeposit, x.Customer, Currency = cr })
.Join(Banks, x => x.CurrencyDeposit.BankId, bn => bn.Id, (x, bn)
=> new { x.CurrencyDeposit, x.Customer, x.Currency, Bank = bn})
.Select(s => new {
s.CurrencyDeposit.Id,
s.Customer.NameSurname,
s.Currency.Code,
s.Bank.BankName,
s.CurrencyDeposit.RequesCode
});
Try something like this...
var myList = ({from a in Companies
join b in Sectors on a.Sector_code equals b.Sector_code
join c in Distribution on b.distribution_code equals a.distribution_code
select new {...});