I've been using Linq to SQL and have started using Entity Framework for a new project. I use LinqPad to test my code before incorporating it into Visual Studio. While debugging in VS, I noticed my counts differed. When I inspected the SQL created by my code in VS, I noticed that it didn't translate correctly.
My code in VS:
var adviceLineCallTotalViewModelList =
from a in db.AdviceLineCalls
.Include(a => a.Agency)
.Include(a => a.Staff)
.Include(a => a.StatusOfAdviceLineCaller)
.Include(a => a.AdviceLineCallSubjectMatter)
join ag in db.Agencies on a.AgencyNumber equals ag.AgencyNumber
join st in db.StatusOfAdviceLineCallers on a.StatusOfAdviceLineCallerID
equals st.StatusOfAdviceLineCallerID
join s in db.Staffs on a.StaffID equals s.StaffID
join sm in db.AdviceLineCallSubjectMatters on a.AdviceLineCallSubjectMatterID
equals sm.AdviceLineCallSubjectMatterID into grp
from sm in grp.DefaultIfEmpty()
where s.Employed == true
select new AdviceLineCallTotalViewModel()
{
AdviceLineCallID = a.AdviceLineCallID,
AdviceLineCallSubjectMatterID = sm.AdviceLineCallSubjectMatterID,
AdviceLineCallSubjectMatterDesc = sm.AdviceLineCallSubjectMatterDesc,
StatusOfAdviceLineCallerID = st.StatusOfAdviceLineCallerID,
StatusOfAdviceLineCallerDesc = st.StatusOfAdviceLineCallerDesc,
AgencyNumber = a.AgencyNumber,
AgencyNumberNameFacility = ag.AgencyNumberNameFacility,
CallDate = a.CallDate,
CallLength = a.CallLength,
Comments = a.Comments,
StaffID = a.StaffID,
LastName = s.LastName
};
When I debug and look at the SQL generated, I see:
SELECT
[Extent1].[AdviceLineCallID] AS [AdviceLineCallID],
[Extent5].[AdviceLineCallSubjectMatterID] AS [AdviceLineCallSubjectMatterID],
[Extent5].[AdviceLineCallSubjectMatterDesc] AS [AdviceLineCallSubjectMatterDesc],
[Extent3].[StatusOfAdviceLineCallerID] AS [StatusOfAdviceLineCallerID],
[Extent3].[StatusOfAdviceLineCallerDesc] AS [StatusOfAdviceLineCallerDesc],
[Extent1].[AgencyNumber] AS [AgencyNumber],
[Extent2].[AgencyNumberNameFacility] AS [AgencyNumberNameFacility],
[Extent1].[CallDate] AS [CallDate],
[Extent1].[CallLength] AS [CallLength],
[Extent1].[Comments] AS [Comments],
[Extent1].[StaffID] AS [StaffID],
[Extent4].[LastName] AS [LastName]
FROM [dbo].[AdviceLineCall] AS [Extent1]
INNER JOIN [dbo].[Agency] AS [Extent2] ON [Extent1].[AgencyNumber] = [Extent2].[AgencyNumber]
INNER JOIN [dbo].[StatusOfAdviceLineCaller] AS [Extent3] ON [Extent1].[StatusOfAdviceLineCallerID] = [Extent3].[StatusOfAdviceLineCallerID]
INNER JOIN [dbo].[Staff] AS [Extent4] ON [Extent1].[StaffID] = [Extent4].[StaffID]
INNER JOIN [dbo].[AdviceLineCallSubjectMatter] AS [Extent5] ON [Extent1].[AdviceLineCallSubjectMatterID] = [Extent5].[AdviceLineCallSubjectMatterID]
WHERE 1 = [Extent4].[Employed]
The last "INNER JOIN" should be a "LEFT OUTER JOIN" because of the lines:
join sm in db.AdviceLineCallSubjectMatters on a.AdviceLineCallSubjectMatterID equals sm.AdviceLineCallSubjectMatterID into grp
from sm in grp.DefaultIfEmpty()
Right???
NOTE: I included the "Include" statements after reading another post about why the "LEFT OUTER JOIN" is not being included. I get the same results with or without the "Includes."
I've used DefaultIfEmpty() in other, simpler queries before and have not run into this problem.
As an EF newbie, I'm not sure if I'm doing something wrong or if EF in my project got corrupted some how. I'm using EF 6.2.
EDIT:
I created a new Visual Studio Project and used the following code:
var adviceLineCallTotalViewModelList = from a in db.AdviceLineCalls
join ag in db.Agencies on a.AgencyNumber equals ag.AgencyNumber
join st in db.StatusOfAdviceLineCallers on a.StatusOfAdviceLineCallerID equals st.StatusOfAdviceLineCallerID
join s in db.Staffs on a.StaffID equals s.StaffID
join sm in db.AdviceLineCallSubjectMatters on a.AdviceLineCallSubjectMatterID equals sm.AdviceLineCallSubjectMatterID into grp
from sm_left in grp.DefaultIfEmpty()
where s.Employed == true
select new AdviceLineCallTotalViewModel()
{
AdviceLineCallID = a.AdviceLineCallID,
AdviceLineCallSubjectMatterID = sm_left == null ? 0 : sm_left.AdviceLineCallSubjectMatterID,
AdviceLineCallSubjectMatterDesc = sm_left == null ? String.Empty : sm_left.AdviceLineCallSubjectMatterDesc,
StatusOfAdviceLineCallerID = st.StatusOfAdviceLineCallerID,
StatusOfAdviceLineCallerDesc = st.StatusOfAdviceLineCallerDesc,
AgencyNumber = a.AgencyNumber,
AgencyNumberNameFacility = ag.AgencyNumberNameFacility,
CallDate = a.CallDate,
CallLength = a.CallLength,
Comments = a.Comments,
StaffID = a.StaffID,
LastName = s.LastName
};
This retrieves the correct number of rows (5104). It also correctly creates the last join as a LEFT OUTER JOIN:
LEFT OUTER JOIN [dbo].[AdviceLineCallSubjectMatter] AS [Extent5] ON [Extent1].[AdviceLineCallSubjectMatterID] = [Extent5].[AdviceLineCallSubjectMatterID]
However, this same line of code in my current project only returns 5 records, and the last join is incorrectly translated into an INNER JOIN.
Does this mean EF or something got corrupted in my current project? As a newbie to MVC and EF, I'm not sure what to do.
Change
join sm in db.AdviceLineCallSubjectMatters ...
from sm in grp.DefaultIfEmpty() ...
....
select
AdviceLineCallSubjectMatterID = sm.AdviceLineCallSubjectMatterID
into
join sm in db.AdviceLineCallSubjectMatters ...
from sm_left in grp.DefaultIfEmpty() ...
....
select
AdviceLineCallSubjectMatterID = sm_left == null? 0 : sm_left.AdviceLineCallSubjectMatterID
Depending on the .net framework version you could alter the select null checking to a more clean way (check #Jacob Proffitt comment)
If your AdviceLineCall entity already has references declared for the related entities (I.e. a.AdviceLineCallSubjectMatter) then you do not need to declare DbSets in your context and manually join these tables in a query. EF handles all of this for you via the mapped references.
var adviceLineCallTotalViewModelList = db.AdviceLineCalls
.Where( a=> a.Staff.Employed)
.Select(a => new {
a.AdviceLineCallId,
a.SubjectMatter.AdviceLineCallSubjectMatterID,
a.SubjectMatter.AdviceLineCallSubjectMatterDesc,
a.StatusOfAdviceLineCaller.StatusOfAdviceLineCallerID,
a.StatusOfAdviceLineCaller.StatusOfAdviceLineCallerDesc,
a.Agency.AgencyNumber,
a.Agency.AgencyNumberNameFacility,
a.CallDate,
a.CallLength,
a.Comments,
a.Staff.StaffID,
a.Staff.LastName})
.ToList() // I'd recommend using .Take() to limit the # of rows returned.
.Select(x => new AdviceLineCallTotalViewModel()
{
AdviceLineCallID = x.AdviceLineCallID,
AdviceLineCallSubjectMatterID = x.AdviceLineCallSubjectMatterID,
AdviceLineCallSubjectMatterDesc = x.AdviceLineCallSubjectMatterDesc,
StatusOfAdviceLineCallerID = x.StatusOfAdviceLineCallerID,
StatusOfAdviceLineCallerDesc = x.StatusOfAdviceLineCallerDesc,
AgencyNumber = x.AgencyNumber,
AgencyNumberNameFacility = x.AgencyNumberNameFacility,
CallDate = x.CallDate,
CallLength = x.CallLength,
Comments = x.Comments,
StaffID = x.StaffID,
LastName = x.LastName
});
The first .Select() extracts just the fields you want from the object model. Provided your entities are mapped as Optional (null-able FK) then it will compose the suitable joins automatically. In cases where something like the SubjectMatter is #null, those two fields requested from that referenced entity will be null/default. When you .Select() that data into your ViewModel you are dealing with POCO objects post the .ToList() or .Take() so you should handle any logic around what to do with missing optional dependencies here. If you want to filter data out, do it in the Where() clause to ensure only relevant data is returned from SQL.
I found the cause of my problem and why the same C# code was being translated into SQL differently, and it all had to do with the Required Data Annotation.
The table, AdviceLineCallSubjectMatter is a new addition to the DB. So only new AdviceLineCall records will have a AdviceLineCallSubjectMatterID so I made it a nullable int.
There are certain AdviceLineCall fields that are "Required," and the new int? AdviceLineCallSubjectMatterID was added to my AdviceLineCall Model class with the Required data annotation.
public partial class AdviceLineCall
{
.
.
.
[Required(ErrorMessage = "Subject Matter is required")]
[DisplayName("Subject Matter")]
public int? AdviceLineCallSubjectMatterID { get; set; }
.
.
.
}
public partial class AdviceLineCallSubjectMatter
{
public AdviceLineCallSubjectMatter()
{
AdviceLineCalls = new HashSet<AdviceLineCall>();
}
[DisplayName("Subject Matter")]
public int AdviceLineCallSubjectMatterID { get; set; }
[StringLength(3)]
[DisplayName("Subject Matter")]
public string AdviceLineCallSubjectMatterDesc { get; set; }
public virtual ICollection<AdviceLineCall> AdviceLineCalls { get; set; }
}
}
When I comment out the Required data annotation in the AdviceLineCall Model class, my C# is translated to the expected SQL with a LEFT OUTER JOIN on AdviceLineCallSubjectMatter.
I am not sure why the Required data annotation has this effect???
NOTE: In the temporary project I created to test the same query, I created the DB Context and Model classes via Code First From DB and didn't add the Required data annotations.
Related
I have this rather complex query in SQL server 2008:
declare #LanguageID as int = 1
select k.datePublish, k.dateEditing, k.dateTables
from TableAreasLevel1 as areaL1
inner join TableAreasLevel2 as areaL2
on areaL1.LanguageID = areaL2.LanguageID and
areaL1.CodeAreaLevel1 = areaL2.CodeAreaLevel1
inner join TableAreasLink as link
on areaL2.CodeAreaLevel1 = link.CodeAreaLevel1 and
areaL2.CodeAreaLevel2 = link.CodeAreaLevel2 and
inner join TableProducts as tblProds
on tblProds.CodeAreaLevel1 = areaL1.CodeAreaLevel1 and
tblProds.CodeAreaLevel2 = areaL2.CodeAreaLevel2
inner join TableSI_Products as prod
on prod.SiAreaCode = link.SiAreaCode
inner join TableCalendar as k
on k.KodTableSI_Products = tblProds.KodTableSI_Products
where areaL1.LanguageID = #LanguageID and
prod.Code = 'some string' and
k.LanguageID = #LanguageID and
tblProds.LanguageID = #LanguageID;
I am trying to develop the same query in LINQ, but I get error when I try join the table TableProducts, i.e the third consecutive join.
Here is my LINQ query:
List<Tuple<DateTime, DateTime, DateTime>> dates = (from areaL1 in gpe.TableAreasLevel1
join areaL2 in gpe.TableAreasLevel2
on new { areaL1.CodeAreaLevel1, areaL1.LanguageID } equals
new { areaL2.CodeAreaLevel1, areaL2.LanguageID }
join link in gpe.TableAreasLink
on new { areaL2.CodeAreaLevel1, areaL2.CodeAreaLevel2, areaL2.RbrOblastNivo2} equals
new {link.CodeAreaLevel1, link.CodeAreaLevel2}
join tblProds in gpe.TableProducts
on tblProds. // The name tblProds is not in the scope of the left side of 'equals'
);
Is the problem connected with how the tables are designed or, something else I should check for?
Any ideas, why tblProds is not visible in the scope of the LINQ query?
You are using Query as your guide something like:
on k.KodTableSI_Products = tblProds.KodTableSI_Products
but take a look at your linq:
on new { areaL1.CodeAreaLevel1, areaL1.LanguageID } equals
it has two fields. I think its not a good idea.
linq join something like
var dates = (from areaL1 in gpe.TableAreasLevel1
join areaL2 in gpe.TableAreasLevel2
on areaL1.PKFields equals areaL2.PKFields
where areaL1.CodeAreaLevel1== areaL2.CodeAreaLevel1 && areaL1.LanguageID = areaL2.LanguageID
Select new YournewClass{YournewClass.Field1=areaL1.fields1, And so on}
)
You can do to join the other tables with aliases.
Sorry need to go for now.
I'm giving you an idea.
Hope it helps.
I wrote this SQL query
select
acc.DepartmentID,
dept.DepartmentName,
dept.DepartmentDivision,
county.CountyName,
sp.StateProvinceID
from [AccountDepartmentXREF] acc
inner join [Department] dept on dept.DepartmentID = acc.DepartmentID
left join [DepartmentStateCountyXREF] dscx on dscx.DepartmentID = acc.DepartmentID
left join [StateCounty] county on county.StateCountyID = dscx.StateCountyID
inner join [StateProvince] sp on sp.StateProvinceID = dept.StateProvinceID
where acc.AccountID = 1
and want to rewrite it using LINQ, but I always get confused when writing left joins in LINQ, so I decided to use a convertor and went with Linqer and this is what it produced
from acc in db.AccountDepartmentXREF
join dscx in db.DepartmentStateCountyXREF on acc.DepartmentID equals dscx.DepartmentID into dscx_join
from dscx in dscx_join.DefaultIfEmpty()
join county in db.StateCounty on new { StateCountyID = Convert.ToInt32(dscx.StateCountyID) } equals new { StateCountyID = county.StateCountyID } into county_join
from county in county_join.DefaultIfEmpty()
join sp in db.StateProvince on acc.Department.StateProvinceID equals sp.StateProvinceID
where
acc.AccountID == 1
select new {
acc.DepartmentID,
acc.Department.DepartmentName,
acc.Department.DepartmentDivision,
CountyName = county.CountyName,
sp.StateProvinceID
}
so when I put everything together in code
public List<DepartmentList> GetDepartmentsByAccountID(string email)
{
HWC = new HWCEntities();
List<DepartmentList> result = new List<DepartmentList>();
int id = CurrentUserID(email);
List<AccountDepartmentXREF> adx = HWC.AccountDepartmentXREFs.Where(w => w.AccountID == id).ToList();
foreach(var a in adx)
{
var query = from acc in HWC.AccountDepartmentXREFs
join dscx in HWC.DepartmentStateCountyXREFs on acc.DepartmentID equals dscx.DepartmentID into dscx_join
from dscx in dscx_join.DefaultIfEmpty()
join county in HWC.StateCounties on new { StateCountyID = Convert.ToInt32(dscx.StateCountyID) } equals new { StateCountyID = county.StateCountyID } into county_join
from county in county_join.DefaultIfEmpty()
join sp in HWC.StateProvinces on acc.Department.StateProvinceID equals sp.StateProvinceID
where
acc.AccountID == a.AccountID
select new
{
acc.DepartmentID,
acc.Department.DepartmentName,
acc.Department.DepartmentDivision,
CountyName = county.CountyName,
sp.StateProvinceID
};
foreach(var b in query)
{
result.Add(new DepartmentList
{
DepartmentID = b.DepartmentID,
DepartmentName = b.DepartmentName,
StateProvinceID = b.StateProvinceID,
DivisionName = b.DepartmentDivision,
CountyName = b.CountyName
});
}
}
return result;
}
I get the error
LINQ to Entities does not recognize the method 'Int32 ToInt32(System.Object)' method, and this method cannot be translated into a store expression.
at the
foreach(var b in query)
Any idea's on how to fix this? I have looked around but other solutions I found aren't dealing with joins
Linq doesn't support the Convert.ToInt32(dscx.StateCountyID) method inside the query because this will need to be converted to sql for execution on the db.
I am not sure what datatype StateCountyID is in both tables but you will need to use some sql compatible conversion method like SqlFunctions.StringConvert() to get them to the same datatype.
Convert them to Strings
from acc in HWC.AccountDepartmentXREFs
join dscx in HWC.DepartmentStateCountyXREFs on acc.DepartmentID equals dscx.DepartmentID into dscx_join
from dscx in dscx_join.DefaultIfEmpty()
join county in HWC.StateCounties on new { StateCountyID = dscx.StateCountyID.ToString() } equals new { StateCountyID = county.StateCountyID.ToString() }
or use the below:
dscx.StateCountyID equals SqlFunctions.Convert(county.StateCountyID)
I figured out why I was getting that error. It was because I had StateCountyID as nullable in my DepartmentStateCountyXREF table. Once I changed the StateCountyID column from a nullable and then removed the Convert.ToInt32, everything worked.
How to convert an SQL to LINQ? Basically, I have a project_mstr table that has a PR_CLASS, PR_TYP and PR_GRP columns. Those 3 columns are values in the params_mstr under param_cd. For example, there's a record that has a PR_TYP value in param_cd with a Regular as its corresponding param_val value.
I installed Linquer but I'm not comfortable using it since I still have to create a connection to my database. I can't also find an online SQL to LINQ converter. So I'm asking the good guys here to please help me with the conversion.
SELECT
c.pr_id, c.pr_class, c.pr_typ, c.pr_grp, cp.pr_price,
c.gl_acct_id, c.pr_DESC "Project",
pm.param_val "Project Class", pm2.param_val "Project Type", pm3.param_val "Project Group"
FROM project_mstr c
JOIN
params_mstr pm ON c.pr_class = pm.param_id AND pm.param_cd = 'PR_CLASS'
JOIN
params_mstr pm2 ON c.pr_typ = pm2.param_id AND pm2.param_cd = 'PR_TYP'
JOIN
params_mstr pm3 ON c.pr_grp = pm3.param_id AND pm3.param_cd = 'PR_GRP'
JOIN
pr_price_mstr cp ON c.pr_id = cp.pr_id
JOIN
gl_acct_mstr gl ON c.gl_acct_id = gl.gl_acct_id
ORDER BY
c.crea_dt DESC;
LINQ-to-SQL only support equijoins, so if you need to introduce multiple values into the join, you can create an anonymous class to represent all of the values being joined on (note that the anonymous classes need to be the same type, which means that they need to have (1) exactly the same names of fields (2) of exactly the same type (3) in exactly the same order).
from c in ProjectMstr
join pm in ParamsMstr on new { ParamId = c.ChClass, ParamCd = "CH_CLASS" } equals new { pm.ParamId, pm.ParamCd }
join pm2 in ParamsMstr on new { ParamId = c.ChClass, ParamCd = "CH_TYP" } equals new { pm2.ParamId, pm2.ParamCd }
join pm3 in ParamsMstr on new { ParamId = c.ChClass, ParamCd = "CH_GRP" } equals new { pm3.ParamId, pm3.ParamCd }
// …
orderby c.CreaDt descending
select new {
c.ChId,
// …
ProjectClass = pm.ParamVal,
ProjectType = pm2.ParamVal,
ProjectGroup = pm3.ParamVal,
}
Alternatively, if it doesn't change the logic of the query, you can pull out the constant value from the join into a where.
from c in ProjectMstr
join pm in ParamsMstr on c.ChClass equals pm.ParamId
join pm2 in ParamsMstr on c.ChClass equals pm2.ParamId
join pm3 in ParamsMstr on c.ChClass equals pm3.ParamId
// …
where pm.ParamCd == "CH_CLASS"
where pm2.ParamCd == "CH_TYP"
where pm3.ParamCd == "CH_GRP"
orderby c.CreaDt descending
select new {
c.ChId,
// …
ProjectClass = pm.ParamVal,
ProjectType = pm2.ParamVal,
ProjectGroup = pm3.ParamVal,
}
I have the following code inside an MVC 6 (beta8) controller:
public IActionResult Get()
{
var districtsdetails = from districts in _ctx.District
select new
{
Id = districts.Id,
CountyFP = districts.County.FIPSCode,
DirectorName = districts.DirectorName,
Email = districts.Email,
EstStudentPop = districts.EstStudentPop,
Name = districts.Name,
Phone = districts.Phone,
Ranking = districts.Ranking,
RANumber = districts.RANumber,
SchoolCount = districts.SchoolCount,
Coop = districts.Coop.Name,
County = districts.County.Name,
Distributors = (from district in _ctx.District
join districtdistributor in _ctx.DistrictDistributor on district.Id equals districtdistributor.DistrictId
into group1
from g1 in group1.DefaultIfEmpty()
join distributor in _ctx.Distributor on g1.DistributorId equals distributor.Id
into group2
from g2 in group2.DefaultIfEmpty()
where district.Id == districts.Id
select new { g2.Id, g2.Name })
};
if (districtsdetails == null)
return HttpNotFound();
return new JsonResult(districtsdetails);
}
The problem is in the Distributors property setter.
I have District, DistrictDistributor, and Distributor entities in my context (and matching tables in my db). There is a many to many relationship between District and Distributor, with DistrictDistributor mapping the many to many relationship between the two. In my DistrictDetailsDTO I'm attempting to bridge the DistrictDistributor gap so I can just do DistrictDetailsDTO.Distributors ... All this is being serialized to Json as you can see by the JsonResult().
In the Distributor = (...) I am trying to effectively reproduce this SQL:
select (...)
from [District] D
left join [DistrictDistributor] DD on
DD.DistrictId = D.Id
left join [Distributor] Db on
Db.Id = DD.DistributorId
where id = 57
However, in my linq 57 would be districts.Id since I'm returning all Districts.
Please HELP I'm going CRAZY! No matter what I try along these lines produces a:
HTTP Error 502.3 - Bad Gateway
The specified CGI application encountered an error and the server terminated the process.
Here is how I think it could be resolved.
First, your query - the hard way. You don't need left joins here at all. They would be needed if you were producing a joined result set (SelectMany), but since that's not the case, you can use the following and let EF do it's magic to make it work:
var query =
from district in _ctx.District.AsNoTracking()
select new
{
Id = district.Id,
Name = district.Name,
// the rest of the district related fields
// ...
Distributors =
from dd in _cxt.DistrictDistributor
where dd.DistrictId == district.Id
join d in _ctx.Distributor on dd.DistributorId equals d.Id
select new { d.Id, d.Name }
};
Second - the easy way. One of the cool things of EF is to describe your model with navigation properties and properly configured relationships. This way you can almost forget about manual joins and let EF do whatever is necessary to satisfy your queries. In your case, the proper model would have District.Distributors and Distributor.Districts navigation properties, and the same result could be achieved with the following simple query:
var query =
from district in _ctx.District.AsNoTracking()
select new
{
Id = district.Id,
Name = district.Name,
// the rest of the district related fields
// ...
Distributors = district.Distributors.Select(d => new { d.Id, d.Name })
};
I need help on the LINQ query below, the join on multiple fields seems to work, but i get
"Invalid 'join' condition. An entity member is invoking an invalid
property or method."
I've already tried to swap the conditions on the join to no luck
(from o in orderSet
join opr in OrderProductSet on o.Id equals opr.OrderId.Id
join pri in ProductPricingSet on
new {BusinessUnitId = o.BusinessUnitId.Id, AccountId = o.Accountid.Id, ProductNameId = opr.ProductNameId.Id} equals
new {BusinessUnitId = pri.BusinessUnitId.Id, AccountId = pri.AccountId.Id, ProductNameId = pri.ProductId.Id}
where o.name.Equals("OE-000701")
select new {
o.name,
o.orderId,
opr.ProductNameId.Name,
opr.Quantity,
pri.Discount,
pri.FinalPrice
})