ndrqu
ndrqu

Reputation: 1

Querying a chain of list of lists with LINQ

I am working with an XML standard called SDMX. It's fairly complicated but I'll make it as short as possible. I am receiving an object called CategoryScheme. This object can contain a number of Category, and each Category can contain more Category, and so on, the chain can be infinite. Every Category has an unique ID.

Usually each Category contains a lot of Categories. Together with this object I am receiving an Array, that contains the list of IDs that indicates where a specific Category is nested, and then I am receiving the ID of that category. What I need to do is to create an object that maintains the hierarchy of the Category objects, but each Category must have only one child and that child has to be the one of the tree that leads to the specific Category. So I had an idea, but in order to do this I should generate LINQ queries inside a cycle, and I have no clue how to do this. More information of what I wanted to try is commented inside the code

Let's go to the code:

public void RemoveCategory(ArtefactIdentity ArtIdentity, string CategoryID, string CategoryTree)
{
    try
    {
        WSModel wsModel = new WSModel();

        // Prepare Art Identity and Array

        ArtIdentity.Version = ArtIdentity.Version.Replace("_", ".");
        var CatTree = JArray.Parse(CategoryTree).Reverse();

        // Get Category Scheme

        ISdmxObjects SdmxObj = wsModel.GetCategoryScheme(ArtIdentity, false, false);

        ICategorySchemeMutableObject CatSchemeObj = SdmxObj.CategorySchemes.FirstOrDefault().MutableInstance;

        foreach (var Cat in CatTree)
        {
            // The cycle should work like this.
            // At every iteration it must delete all the elements except the correct one
            // and on the next iteration it must delete all the elements of the previously selected element
            // At the end, I need to have the CatSchemeObj full of the all chains of categories.

            // Iteration 1...
            //CatSchemeObj.Items.ToList().RemoveAll(x => x.Id != Cat.ToString());

            // Iteration 2...
            //CatSchemeObj.Items.ToList().SingleOrDefault().Items.ToList().RemoveAll(x => x.Id != Cat.ToString());

            // Iteration 3...
            //CatSchemeObj.Items.ToList().SingleOrDefault().Items.ToList().SingleOrDefault().Items.ToList().RemoveAll(x => x.Id != Cat.ToString());

            // Etc...
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

Thank you for your help.

Upvotes: 0

Views: 749

Answers (3)

ndrqu
ndrqu

Reputation: 1

Thank you to whoever tried to help but I solved it by myself in a much easier way.

I just sent the full CategoryScheme object to the method that converted it in the XML format, then just one line did the trick:

XmlDocument.Descendants("Category").Where(x => !CatList.Contains(x.Attribute("id").Value)).RemoveIfExists();

Upvotes: 0

Botond Bertalan
Botond Bertalan

Reputation: 370

Edited:

So here is another possible way of going after the discussion below. Not sure what do you really need so just try it out.

    int counter = 0;
    var list = CatSchemeObj.Items.ToList();
    //check before you call it or  you will get an error
    if(!list.Equals(default(list)))
    {
        while(true)
        {
            var temp = list.Where(x => CatTree[counter++] == x.Id); // or != ? play with it .
            list = temp.Items.ToList().SingleOrDefault();

            if(list.Equals(default(list))
            {
                break;
            }
        }
    }

I just translated you problem to 2 solutions, but I am not sure if you won't lose data because of the SingleOrDefault call. It means 'Grab the first item regardless of everything'. I know you said you have only 1 Item that is ok, but still... :)

Let me know in comment if this worked for you or not.

//solution 1
// inside of this loop check each child list if empty or not
foreach (var Cat in CatTree)
{
    var list = CatSchemeObj.Items.ToList();
    //check before you call it or  you will get an error
    if(!list.Equals(default(list)))
    {
        while(true)
        {
            list.RemoveAll(x => x.Id != Cat.ToString());
            list = list.ToList().SingleOrDefault();

            if(list.Equals(default(list))
            {
                break;
            }
        }
    }
}

//solution 2

foreach (var Cat in CatTree)
{
    var list = CatSchemeObj.Items.ToList();
    //check before you call it or  you will get an error
    if(!list.Equals(default(list)))
    {
        CleanTheCat(cat, list);
    }
}

//use this recursive function outside of loop because it will cat itself
void CleanTheCat(string cat, List<typeof(ICategorySchemeMutableObject.Items) /*Place here whatever type you have*/> CatSchemeObj)
{
    CatSchemeObj.RemoveAll(x => x.Id != cat);

    var catObj = CatSchemeObj.Items.ToList().SingleOrDefault();

    if (!catObj.Equals(default(catObj)){
        CleanTheCat(cat, catObj);
    }
}

Upvotes: 0

Chris Schmitz
Chris Schmitz

Reputation: 390

So, as i already said in my comment, building a recursive function should fix the issue. If you're new to it, you can find some basic information about recursion in C# here.

The method could look something like this:

private void DeleteRecursively(int currentRecursionLevel, string[] catTree, ICategorySchemeMutableObject catSchemeObj) 
{
    catSchemeObj.Items.ToList().RemoveAll(x => x.Id != catTree[currentRecursionLevel].ToString());
    var leftoverObject = catSchemeObj.Items.ToList().SingleOrDefault();
    if(leftoverObject != null) DeleteRecursively(++currentRecursionLevel, catTree, leftoverObject);
}

Afterwards you can call this method in your main method, instead of the loop:

    DeleteRecursively(0, CatTree, CatSchemeObject);

But as i also said, keep in mind, that calling the method in the loop, seems senseless to me, because you already cleared the tree, besides the one leftover path, so calling the method with the same tree, but another category, will result in an empty tree (in CatSchemeObject).

CAUTION! Another thing to mention i noticed right now: Calling to list on your Items property and afterwards deleting entries, will NOT affect your source object, as ToList is generating a new object. It IS keeping the referenced original objects, but a deletion only affects the list. So you must write back the resulting list to your Items property, or find a way to directly delete in the Items object. (Assuming it's an IEnumerable and not a concrete collection type you should write it back).

Just try it out with this simple example, and you will see that the original list is not modified.

IEnumerable<int> test = new List<int>() { 1, 2, 3, 4 , 1 };

test.ToList().RemoveAll(a => a != 1);

Upvotes: 1

Related Questions