0

I want to understand the complex filtering with LINQ XML. I have created a simple XML example (DataBaseCities.xml):

<?xml version="1.0" encoding="utf-8"?>
<DataBase>
    <DocumentInfo version="1.0" schemaVersion="1.0"/>
    <ListOfMegaCities>
        <MegaCities city="Moscow" continent="Europe">
            <VariantConstraint>
                <LanguageRef LanguageId="russian">
                    <LanguageDialectsRef DialectsId="north"/>
                </LanguageRef>
            </VariantConstraint>
            <Districts>                
                <CityDistrict District="Arbat"/>
                <CityDistrict District="Basmanny"/>
            </Districts>
        </MegaCities>
        <MegaCities city="New York" continent="North America">
            <VariantConstraint>
                <LanguageRef LanguageId="english">
                    <LanguageDialectsRef DialectsId="west"/>
                </LanguageRef>
                <LanguageRef LanguageId="spanish">
                    <LanguageDialectsRef DialectsId="cental"/>
                </LanguageRef>              
            </VariantConstraint>
            <Districts>                
                <CityDistrict District="Queens"/>
                <CityDistrict District="Bronx"/>
            </Districts>
        </MegaCities>
        <MegaCities city="London" continent="Europe">
            <VariantConstraint>
                <LanguageRef LanguageId="english">
                    <LanguageDialectsRef DialectsId="west"/>
                </LanguageRef>
                <LanguageRef LanguageId="spanish">
                    <LanguageDialectsRef DialectsId="central"/>
                </LanguageRef>  
                <LanguageRef LanguageId="french">
                    <LanguageDialectsRef DialectsId="central"/>
                </LanguageRef>              
            </VariantConstraint>
            <Districts>                
                <CityDistrict District="Greenwich"/>
                <CityDistrict District="Westminster"/>
            </Districts>
        </MegaCities>       
    </ListOfMegaCities>
</DataBase>

And I try to filter like:

        XElement root = XElement.Load(@"DataBaseCities.xml");
        IEnumerable<XElement> ListOfMegaCities =
            from el in root.Descendants("MegaCities")
            where
                (from add in el.Descendants("LanguageRef")
                 where
                      (string)add.Attribute("LanguageId") == "english"
                 select add)
            .Any()
            select el;

        foreach (XElement el in ListOfMegaCities)
        {
            Console.WriteLine((string)el.Attribute("city"));
        }

So the Output is:

New York
London

But I want to filter more then one Attribute.

  1. If I try to filter with these lines:

    (string)add.Attribute("LanguageId") == "english" && (string)add.Attribute("LanguageId") == "spanish"

Why it dont work?

  1. How I can filter "DialectsId" too?

Example: I want to get "New York" with exactly this filtering:

LanguageId="english"
DialectsId="west"
LanguageId="spanish"
DialectsId="cental"

Maybe someone has a good source for complex filtering? I found this one https://learn.microsoft.com/de-de/dotnet/standard/linq/write-queries-complex-filtering but that helped me only partially...

Aleksej
  • 49
  • 7
  • 1
    IMO create classes that represent the structure of the xml, then deserialize to the new classes and finally use linq to filter etc. – Trevor Jun 03 '21 at 12:32
  • make OR condition. its same element. (string)add.Attribute("LanguageId") == "english" || (string)add.Attribute("LanguageId") == "spanish". your requested logic qould be: ((string)add.Attribute("LanguageId") == "english" || (string)add.Attribute("LanguageId") == "spanish") || ((string)add.Attribute("DialectsId") == "west" || (string)add.Attribute("DialectsId") == "cental") – Power Mouse Jun 03 '21 at 12:55
  • @Aleksej, to deserialize xml to CLR object, see this other question : https://stackoverflow.com/questions/364253/how-to-deserialize-xml-document – vernou Jun 03 '21 at 13:09
  • Ok Thanks. I will try. If I find a solution, I will publish it here. – Aleksej Jun 03 '21 at 13:17
  • 1
    The dialectsId for spanish is 'cental' to New York and 'central' to London. This difference is wanted? – vernou Jun 03 '21 at 13:27

2 Answers2

2

I have used All instead of Any so that it filters only those cities which satisfy all the conditions. I am used to writing LINQ Expressions instead of LINQ Query.

List<XElement> ListOfMegaCities = root.Descendants("MegaCities")
                                 .Where(lr => lr.Descendants("LanguageRef")
                                 .All(
                                      li => (li.Attribute("LanguageId").Value == "english" 
                                          && li.Element("LanguageDialectsRef").Attribute("DialectsId").Value == "west")
                                          || (li.Attribute("LanguageId").Value == "spanish" 
                                          && li.Element("LanguageDialectsRef").Attribute("DialectsId").Value == "cental")
                                            )                                     
                                 ).ToList();
Akshay G
  • 2,070
  • 1
  • 15
  • 33
  • I have not forgotten the request. :) This looks very good. I will analyze the syntax to learn more. If I have some other solution I will let everyone know. – Aleksej Jun 12 '21 at 07:03
1

First, see @zaggler comment. Deserialize and manipulate object will be more easily.

Second, if you want the cities where only english and spanish is spoken, see @AkshayGaonkar answer. If you want cities where english and spanish (and maybe other languages) is spoken, see my answer.


To write complex Linq query, I begin to write simulacrum SQL :

-- I want all cities
select * from MegaCities as el
where
    -- that speak english with west dialect
    exists (
        select * from el.LanguageRef as add
        where add.LanguageId = 'english' and
            add.LanguageDialectsRef.DialectsId = 'west'
    )
    -- and also speak spanish with central dialect
    and exists (
        select * from el.LanguageRef as add
        where add.LanguageId == 'spanish' and
            add.LanguageDialectsRef.DialectsId = 'cental'
    )

Then I can transpose to Linq :

IEnumerable<XElement> ListOfMegaCities =
    from el in root.Descendants("MegaCities")
    where
        (from add in el.Descendants("LanguageRef")
         where add.Attribute("LanguageId").Value == "english" &&
            add.Element("LanguageDialectsRef").Attribute("DialectsId").Value == "west"
         select add)
        .Any()
        &&
        (from add in el.Descendants("LanguageRef")
         where add.Attribute("LanguageId").Value == "spanish" &&
            add.Element("LanguageDialectsRef").Attribute("DialectsId").Value == "cental"
         select add)
        .Any()
    select el;

Maybe not the best trick, but can help sometimes.

vernou
  • 6,818
  • 5
  • 30
  • 58
  • @AkshayGaonkar, thank. `central` or `cental`, this sound like a error in the question. – vernou Jun 03 '21 at 13:41
  • Well, that changes the whole scenario. I had used `All` to specifically have only `New York` as output. If London is also a valid output then I have to change to `Any` – Akshay G Jun 03 '21 at 13:59