Reputation: 23
Can anybody jump in and help me finish my code? I'd like to convert this SQL Query to C# EF. I have attached below my attempt. I should be able to finish the rest if I have a sample for the ReferredCode part.
Please enlighten me if it even is possible to convert. Any help is appreciated. Thanks!
SQL Query
SELECT cd.id,
cd.RunOrder,
cd.ItemId,
code =
case
when cd.IsItemAssembly=0 or cd.IsItemAssembly is null then
(select cast(ma.Code as varchar) as code
from materials.Material ma
inner join materials.MaterialPrice mp
on ma.ID=mp.MaterialID
and mp.HistoryStatusID=2
where cd.ItemID=ma.ID
)
else
(select cast(ass.Code as varchar) as code
from Materials.tblAssemblies ass
where cd.ItemID=ass.Assembly_ID
and ass.HistoryStatusID=2
)
end,
isnull(cd.IsItemAssembly,0) as IsItemAssembly,
cd.ReferredItemID,
ReferredCode =
case
when cd.IsReferredItemAssembly=0 or cd.IsReferredItemAssembly is null then
(select cast(ma.Code as varchar) as code
from materials.Material ma
inner join materials.MaterialPrice mp
on ma.ID=mp.MaterialID
and mp.HistoryStatusID=2
where cd.ReferredItemID=ma.ID
)
else
(select cast(ass.Code as varchar) as code
from Materials.tblAssemblies ass
where cd.ReferredItemID=ass.Assembly_ID
and ass.HistoryStatusID=2
)
end,
isnull(cd.IsReferredItemAssembly,0) as IsReferredItemAssembly,
cd.Factor,
cd.LinkedCalculatorID,
cast(lc.Name as varchar) as LinkedCalcName,
cd.CalculatorRuleID,
cast(cr.Name as varchar) as RuleName,
cast(cr.Code as varchar) as RuleCode
from dbo.Calculator ch
inner join dbo.CalculatorDetail cd
on ch.ID = cd.CalculatorID
and ch.IsActive = 1
inner join dbo.CalculatorRule cr
on cd.CalculatorRuleID=cr.ID
left join dbo.Calculator lc
on lc.ID = cd.LinkedCalculatorID
and lc.IsActive = 1
where ch.ID=@CalculatorID
order by cd.RunOrder
My attempt:
var query = (from ch in dbContext.Calculators
join cd in dbContext.CalculatorDetails on ch.ID equals cd.CalculatorID where ch.IsActive == true
join cr in dbContext.CalculatorRules on cd.CalculatorRuleID equals cr.ID
join lc in dbContext.Calculators on cd.LinkedCalculatorID equals lc.ID where lc.IsActive == true
where ch.ID == calculatorIDParameter
orderby cd.RunOrder
select new GetCalculatorMaterialsModel
{
id = cd.ID,
RunOrder = cd.RunOrder,
ItemId = cd.ItemID,
ReferredItemID = cd.ReferredItemID,
ReferredCode =
IsReferredItemAssembly = cd.IsReferredItemAssembly == null ? false : true,
Factor = cd.Factor,
LinkedCalculatorID = cd.LinkedCalculatorID,
LinkedCalcName = lc.Name,
CalculatorRuleID = cd.CalculatorRuleID,
RuleName = cr.Name,
RuleCode = cr.Code
}).ToList();
Upvotes: 0
Views: 305
Reputation: 35073
Firstly, all of the relationships between the tables should be able to be covered by setting up navigation properties and configuring their associated FKs. This avoids the need to specify Join conditions in EF Linq statements.
public class Calculator
{
[Key]
public int Id { get; set; }
// ...
[ForeignKey("CalculatorDetailsId")]
public virtual CalculatorDetails CalculatorDetails { get; set; }
}
public class CalculatorDetails
{
[Key]
public int Id { get; set; }
// ...
[ForeignKey("LinkedCalculatorId")]
public virtual Calculator LinkedCalculator { get; set; }
[ForeignKey("CalculatorRulesId")]
public virtual Calculator CalculatorRules { get; set; }
public int ItemId { get; set; }
public int ReferredItemId { get; set; }
}
These are by no means complete but should give you some idea about what the navigation properties would look like. I don't recommend adding properties for the FKs, but rather shadow properties to link the FK. This keeps the model with one source of truth for the relationship.
Where it gets ugly is your association between CalculatorDetail and either Materials or Assemblies based on the ReferredItemId. This is quite bad relational DB design as you cannot count on FK constraints to enforce referential integrity. This also means we cannot use navigation properties here either.
I would probably keep the initial query simple then fetch the appropriate referred item separately:
var data = dbContext.Calculators
.Where(x => x.Id == calculatorIdParameter)
.Select(x => new GetCalculatorMaterialsModel
{
id = x.ID, // Is this supposed to be the calculator ID or the CalculatorDetails ID?
RunOrder = x.CalculatorDetails.RunOrder,
ItemId = x.CalculatorDetails.ItemID,
ReferredItemID = x.CalculatorDetails.ReferredItemID,
IsItemAssembly = x.CalculatorDetails.IsItemAssembly ?? false,
IsReferredItemAssembly = x.CalculatorDetails.IsReferredItemAssembly ?? false,
Factor = x.CalculatorDetails.Factor,
LinkedCalculatorID = x.CalculatorDetails.LinkedCalculator.ID,
LinkedCalcName = x.CalculatorDetails.LinkedCalculator.Name,
CalculatorRuleID = x.CalculatorDetails.CalculatorRule.ID,
RuleName = x.CalculatorDetails.CalculatorRule.Name,
RuleCode = x.CalculatorDetails.CalculatorRule.Code
}).Single();
Then find and populate your Item/Referred details based on whichever table it needs to come from. It isn't clear from your SQL why you need to join on MaterialPrice for when all you seem to want is Material.Code, or why you would be checking on IsActive as I would assume that the IDs are unique. If they are pointing at an inactive material/Assembly you would want the code to be #Null?
if (data.IsItemAssembly)
data.ItemCode = _dbContext.Assemblies
.Where(x => x.Id == data.ItemId && x.IsActive)
.Select(x => x.Code)
.SingleOrDefault();
else
data.ItemCode = _dbContext.Materials
.Where(x => x.Id == data.ItemId && x.IsActive)
.Select(x => x.Code)
.SingleOrDefault();
Then the same again for setting the related item code.
It may certainly be possible to find a way to compose this into one Linq expression, but it would arguably be a bit harder to understand, where this would work perfectly fine for loading a single detailed item.
Overall though it would be better to fix the relational model to better handle this relationship. EF is designed to work with valid relational models and can do so quite easily and performantly. As soon as you start introducing deviations to good DB design things start to get uglier all around.
Upvotes: 1