简体   繁体   中英

SQL to C# Entity Framework

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();

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM