Search code examples
c#xmllinqgroup-by

Reading xml into LINQ format to check against log file data - Conditions in list format - linq not picking up reformated xml


I have a project that reads xml of expected build results for each build area, and compares against build logs to see if the build passed. Right now, the linq is showing each condition in a separate line of results, but (since I'm looping thru each linq result line to check for success) I need to be able to check both conditions (if there are two) at the same time, not different times. Otherwise I could be checking the wrong line of the log file. Note that sometimes there are two success criteria but not always. It's just the two conditions I need to check at once. To make this change, I moved my SuccessCriteria tag out of the Conditions tag and below Conditions. With that change, the linq isn't picking up the xml lines. There should be about 69 lines of results_b with my full xml, but it's picking up a couple sections I'm not showing that I left in the old format. I tried changing cName = (string)p.Attribute("name"), to cName = (string)b.Attribute("name"), in the LINQ but have the same results. The cValue was changed similarly at the same time.

My question is, how do I change the LINQ to pick up the full xml file, and the groupBy_b to check both conditions at once? I want to have the conditions together in a list but have the other data around it like build machine, process name, and success criteria. For example, the table I put in xml looks like this for some of the log, ParseISLogStats (so I need to check BuildProject and BaseBuildArea are correct together in the log because they are different columns, but the success criteria is only one for those two conditions):

ParseISLogStats    Desctiption=nightly v17 partb   Projects=8   Status=Success
ParseISLogStats    Description=nightly v17 partb   Projects=8   Status=Success
ParseISLogStats    Description=nightly v17         Projects=33  Status=Success

I've been looking at these examples: linq linq

So far I have this code (with added groupBy to try and get conditions into a list together):

void ReadXml()
{
    int i_results = 0; //0 is successful for all; if it increases we have failed in one
    XmlDocument xml = new XmlDocument();
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string newPath = Path.GetFullPath(Path.Combine(path, @"..\.."));
    string finalPathXmlb = Path.GetFullPath(Path.Combine(newPath, @"BuildVerificationBuildAttributes_b.xml")); //new format

    teamPost = "";

    //linq 
    XDocument xmlDoc2 = XDocument.Load(finalPathXmlb);
    //XElement buildVerificationElement = xmlDoc1.Element("BuildVerification");
    //IEnumerable<XElement> buildElements = buildVerificationElement.Elements("build");
    //IEnumerable<XElement> buildMachines = buildElements.Elements("BuildMachine");

    //get just the times from the xml
    //var codeFreezeTime = xmlDoc1.Descendants("codeFreezeTime").First()?.Value;
    //logToFileAndScreen(codeFreezeTime);

    //get build info from xml, such as process (log name), condition, successCriteria, build machine, start time header, 
    IEnumerable<XElement> processes = buildMachines.Elements("Process");

    var results_b =
        xmlDoc2.Descendants("build")
        .SelectMany(b => b.Descendants("Process")
        .SelectMany(p => p.Descendants("Condition")
        .SelectMany(c => c.Descendants("SuccessCriteria")
        .Select(sc => new
        {
            buildMach = (string)b.Element("BuildMachine"),
            p1 = (string)p.Element("ProcessName"),
            startTimeHeader = (string)p.Element("startTimeHeader"),
            cName = (string)c.Attribute("name"),
            cValue = (string)c.Attribute("value"),
            f1 = (string)sc.Element("field"),
            c1 = (string)sc.Element("comparison"),
            v1 = (string)sc.Element("value")
        })))).ToList();
    
    var groups_b = results_b.GroupBy(x => new { build_machine = x.buildMach, process_name = x.p1, name = x.cName }).ToList();
} //ParseLogFile

I want my groups_b to look like this (but with the linq not working it will never do that, but I don't think I have it correct in my groups_b either if the linq did work):

{buildMach = mach64, p1=ParseISLogStats, startTimeHeader = StartTime, {cName=Description, cValue=nightly v17 partb, cName=Projects, cValue=8}, f1=Status, c1=equal, v1=Succcess}
{buildmach = mach64, p1=ParseISLogStats, startTimeheader = StartTime, {cName=Description, cValue = nightly v17, cName=Projects, cValue=33},f1=status, c1=equal, v1=Success}

This is what my xml looks like:

<?xml version="1.0" encoding="UTF-8"?>
<BuildVerification>
    <codeFreezeTime>19:00</codeFreezeTime>
    <build>
        <BuildMachine>mach31</BuildMachine>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\R\filename1.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\R\filename2.cs">
                        </Condition>
                    </Conditions>
                        <SuccessCriteria>
                            <field>Status</field>
                            <comparison>equal</comparison>
                            <value>Success</value>
                        </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\R\filename3.h">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                            <field>Status</field>
                            <comparison>equal</comparison>
                            <value>Success</value>
                    </SuccessCriteria>
            </Process>

        </build>
        
        <build>
        <BuildMachine>mach46</BuildMachine>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\K\filename4.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\K\fiename5.h">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\K\filename6.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                            <field>Status</field>
                            <comparison>equal</comparison>
                            <value>Success</value>
                    </SuccessCriteria>
            </Process>
            
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\D\filename7.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\K\K\filename8.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\K\K\filename9.h">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            
                        
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\K\K\filename11.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> <!--can't use >= here -->
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\K\filename22.cs">   
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinFileVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='VersionFile' value="\\view\Build_NightlyDeveloper\K\filename33.cs">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            
            
            <Process>
                <ProcessName>SpinISProjectVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='ProductName' value="P1">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinISProjectVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='ProductName' value="P2">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>SpinISProjectVersionStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='ProductName' value="P3">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>GetSWStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='DestinationPath' value="D:\Builds\Retail\nightly">
                        </Condition>
                        <Condition
                            name='DestinationPath' value="D:\Builds\Retail\nightly">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison>
                        <value>Successful</value>
                    </SuccessCriteria>
            </Process>
            
            <Process>
                <ProcessName>ParseISLogStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='Nightly' value="D:\nightly V17">
                            </Condition>
                        <Condition
                            name='Projects' value=33>
                            
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>ParseISLogStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='Nightly' value="D:\nightly V17 partb">
                        </Condition>
                        <Condition
                            name='Projects' value=8>
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>ParseISLogStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='BuildProject' value="DInstallerBuild">
                        </Condition>
                        <Condition
                            name='BaseBuildArea' value="D:\R_Builds\K\v2.1\nightly">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                        <field>Status</field>
                        <comparison>equal</comparison> 
                        <value>Success</value>
                    </SuccessCriteria>
            </Process>
            <Process>
                <ProcessName>ParseISLogStats</ProcessName>
                <startTimeHeader>StartTime</startTimeHeader>
                    <Conditions>
                        <Condition
                            name='BuildProject' value="KInstallerBuild">
                        </Condition>
                        <Condition
                            name='BaseBuildArea' value="D:\R_Builds\K\v2.1\nightly">
                        </Condition>
                    </Conditions>
                    <SuccessCriteria>
                            <field>Status</field>
                            <comparison>equal</comparison> 
                            <value>Success</value>
                    </SuccessCriteria>
            </Process>

        </build>
    </BuildVerification>

Update: I tried changing LINQ to the following, but it's still just returning the data from the xml that I left in the old format, not the one with the SuccessCriteria at the same level/depth as Conditions.

var results_b = (
    from b in xmlDoc2.Descendants("build")
    from p in b.Descendants("Process")
    from c in p.Descendants("Condition")
    from sc in c.Descendants("SuccessCriteria")
    select new
    {
        buildMach = (string)b.Element("BuildMachine"),
        p1 = (string)p.Element("ProcessName"),
        startTimeHeader = (string)p.Element("startTimeHeader"),
        cName = (string)c.Attribute("name"),
        cValue = (string)c.Attribute("value"),
        f1 = (string)sc.Element("field"),
        c1 = (string)sc.Element("comparison"),
        v1 = (string)sc.Element("value")
    })
    .ToList();

Solution

  • To navigate your XML data using your LINQ query, you need to first understand the element hierarchy (schema) and then tailor your query to match.

    From a review of your sample XML, it appears that your current content has a structure like:

    BuildVerification
        codeFreezeTime (text)
        *build
            BuildMachine (text)
            *Process
                ProcessName (text)
                startTimeHeader (text)
                *Conditions
                    Condition
                        @name (text)
                        @value (text)
                *SuccessCriteria
                    field (text)
                    comparison (text)
                    value (text)
    

    (This is not a formal notation, but just my way of laying things out. Indentation reflects nesting, "(text)" indicates elements that have text content, "@" indicates an attribute, and I used "*" to mark elements that may be repeated.)

    I marked SuccessCriteria as repeated, because your earlier questions contained examples where a Process element contained multiple nested SuccessCriteria elements.

    So the main problem with your current query is that you are attempting to select SuccessCriteria elements that are child elements of Condition, but if you review the above layout, you will see that this will never occur. SuccessCriteria is now a child of Process.

    We also need to handle the nested collections.

    Your requested result layout is roughly the following:

    {
        buildMach = value,
        p1 = value,
        startTimeHeader = value,
        {
            cName = value,
            cValue = value,
            cName = value,
            cValue = value
        },
        f1 = value,
        c1 = value,
        v1 = value
    }
    

    To handle multiple conditions per process and (potentially) multiple criteria per process (that match those conditions), I believe you really need a layout something like:

    List of {
        buildMach = value,
        p1 = value,
        startTimeHeader = value,
        Conditions = List of {
            cName = value,
            cValue = value
        },
        SuccessCriteria = List of {
            f1 = value,
            c1 = value,
            v1 = value
        }
    }
    

    This would allow you to define validation rules that each can define multiple match conditions and then apply multiple success criteria checks.

    To get the nested structure above, the Condition and SuccessCriteria portions of your query will need to move to subexpressions of your result object constructor.

    For improved readability, I also recommend dropping the (string) cast syntax and instead use the .Value property to get the element and attribute contents. (If there is a chance that an element or attribute may be missing/null, you can use the null-propagating ?.Value syntax.)

    The resulting code would be something like:

    var results1 =
        xmlDoc2.Descendants("build")
        .SelectMany(b => b.Descendants("Process")
        .Select(p => new
        {
            buildMach = b.Element("BuildMachine").Value,
            p1 = p.Element("ProcessName").Value,
            startTimeHeader = p.Element("startTimeHeader").Value,
            conditions = p.Descendants("Condition")
                .Select(c => new
                {
                    cName = c.Attribute("name").Value,
                    cValue = c.Attribute("value").Value
                })
                .ToList(),
            successCriteria = p.Descendants("SuccessCriteria")
                .Select(sc => new
                {
                    f1 = sc.Element("field").Value,
                    c1 = sc.Element("comparison").Value,
                    v1 = sc.Element("value").Value
                })
                .ToList()
        })).ToList();
    

    The equivalent using LINQ query syntax:

    var results2 = (
        from b in xmlDoc2.Descendants("build")
        from p in b.Descendants("Process")
        select new {
            buildMach = b.Element("BuildMachine").Value,
            p1 = p.Element("ProcessName").Value,
            startTimeHeader = p.Element("startTimeHeader").Value,
            conditions = (
                from c in p.Descendants("Condition")
                select new
                {
                    cName = c.Attribute("name").Value,
                    cValue = c.Attribute("value").Value
                })
                .ToList(),
            successCriteria = (
                from sc in p.Descendants("SuccessCriteria")
                select new
                {
                    f1 = sc.Element("field").Value,
                    c1 = sc.Element("comparison").Value,
                    v1 = sc.Element("value").Value
                })
                .ToList()
        })
        .ToList();
    

    Results (partial):

    ...
    {"buildMach":"mach46","p1":"ParseISLogStats","startTimeHeader":"StartTime","conditions":[{"cName":"BuildProject","cValue":"DInstallerBuild"},{"cName":"BaseBuildArea","cValue":"D:\\R_Builds\\K\\v2.1\\nightly"}],"successCriteria":[{"f1":"Status","c1":"equal","v1":"Success"}]}
    {"buildMach":"mach46","p1":"ParseISLogStats","startTimeHeader":"StartTime","conditions":[{"cName":"BuildProject","cValue":"KInstallerBuild"},{"cName":"BaseBuildArea","cValue":"D:\\R_Builds\\K\\v2.1\\nightly"}],"successCriteria":[{"f1":"Status","c1":"equal","v1":"Success"}]}
    

    See this .NET Fiddle.

    You might find it useful to define named classes to receive the results, instead of using anonymous types.

    If you make further design changes in your data, I suggest that you revise the layout (schema) representation above, or put together an equivalent representation that works for you. You can then reference that updated schema to guide you while making the necessary changes to your code.