clogue clogue - 19 days ago 7
C# Question

Creating a generic xml linq search based on attribute name/value pairs

I have a set of functions I'm working on, and one of them is to parse an XML file, and return the elements that match to the attribute-based conditions that I have.

This is a sample of the XML I have to use:

<rs:insert>
<z:row Inst='AM5001' Event='EventA' HostName='HostA' EventType='NORMAL' TXID='0000003327'/>
<z:row Inst='AM5001' Event='EventB' HostName='HostB' EventType='NORMAL' TXID='0000011173'/>
<z:row Inst='AM4067' Event='EventA' HostName='HostA' EventType='NORMAL' TXID='0000011175'/>
<z:row Inst='AM5546' Event='EventC' HostName='HostC' EventType='NORMAL' TXID='0000011177'/>
<z:row Inst='AM4567' Event='EventQ' HostName='HostD' EventType='NORMAL' TXID='0000011593'/>


and this is a snippet of what I've got so far:

internal protected IEnumerable<XElement> GetElement(XDocument oXMLDoc
, List<KeyValuePair<string, string>> SearchCriteria)
{
var vElementQuery = oXMLDoc.Elements()
.Where(e => SearchCriteria.ForEach(sc => e.Attribute(sc.Key).Value.ToUpper() == sc.Value.ToUpper()))
.Select(e => e);

foreach (var xElement in vElementQuery)
{
yield return xElement;
}
}


I'm getting an error in my where clause of the vElementQuery :


Only assignment, call, increment, decrement, await, and new object
expressions can be used as a statement


In simplicity, I would be querying based on Inst and Event, but occasionally I would need to based on hostname and Eventtype also.

Basically, I don't know how many attribute conditions I will need to match on, so I could like to add them in as a list or an array, thinking a
KeyValuePair
would be useful as it gives me the name and the comparison (value) that I need.

Thankful for any help.

Answer

Where requires a predicate: A function that takes an e (XElement in this case) and returns a bool.

Instead, you're trying to return the result of SearchCriteria.ForEach(), which returns void. All it does is iterate over the collection and call a void lambda for each item. So you can't use it as the body of a predicate. That would be your next problem after you fix this one, but the same fix takes care of both.

.Where(e => 
    SearchCriteria.ForEach(sc => 
        e.Attribute(sc.Key).Value.ToUpper() == sc.Value.ToUpper()
    )
)

Because ForEach returns void, it takes a lambda that returns void. The body of a function must be a statement. In a lambda, if the return type isn't void, and the body is an expression, the result of that expression is returned -- call it an implicit return statement. But here the return value is void -- that's established by the parameter type of ForEach:

public void ForEach(
    Action<T> action
)

Action<T> has one parameter and no return type. And whatever inference rules the compiler uses result in it assuming that you're trying to write a void lambda, rather than that you're trying to give ForEach a predicate.

Here's the body of the lambda you're giving it:

e.Attribute(sc.Key).Value.ToUpper() == sc.Value.ToUpper()

Without the compiler supplying an implicit return in front of it, that's not a statement. It's an expression. As a statement, it's not legal C#. Try compiling this:

1 != 2;

Same error. C or Perl wouldn't care but C# does.

I think you might want Any instead of ForEach:

.Where(e => 
    SearchCriteria.Any(sc => 
        e.Attribute(sc.Key).Value.ToUpper() == sc.Value.ToUpper()
    )
)

That is to say, return any e where any of the search criteria matches any of its attributes. Any takes a predicate and returns bool, fixing both errors.