Generic extension to transform a flat nested list to hierarchy list












4














I have a collection of items in my database with a ParentId property. I'm reading the categories as a flat structure and I would like to convert it to a hierarchy list by populating the ParentCategory and ChildCategories properties.



// Domain class
public class Category
{
public Category()
{
this.ChildCategories = new HashSet<Category>();
}

public int CategoryId { get; set; }
public string Name { get; set; }
public int Position { get; set; }

// ... many more properties

public int? CategoryParentId { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}

// DTO class
public class CategoryDto
{
public CategoryDto()
{
this.ChildCategories = new HashSet<CategoryDto>();
}

public int CategoryId { get; set; }
public string Name { get; set; }
public int? CategoryParentId { get; set; }
public CategoryDto ParentCategory { get; set; }
public IEnumerable<CategoryDto> ChildCategories { get; set; }
}


Since that we cannot do hierarchy query directly with Entity Framework Core, I can only retrieve them in a flat list like so:



var result = myDbContext.Categories
.Select(category => new Dto.CategoryIndexDto()
{
CategoryId = category.CategoryId,
Name = category.Name,
Position = category.Position,
CategoryParentId = category.CategoryParentId,
});


So I needed a way to convert this list of dto objects into hierarchy list where the ParentCategory and ChildCategories are populated for each dto object. So I've made this extension:



public static IEnumerable<TEntity> AsHierarchy<TEntity, TKeyProperty, TChildrenProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Expression<Func<TEntity, TEntity>> parentPropertySelector,
Expression<Func<TEntity, TChildrenProperty>> childrenPropertySelector)
where TChildrenProperty : IEnumerable<TEntity>
{
return items.AsHierarchy(keyProperty, parentKeyProperty, parentPropertySelector, childrenPropertySelector, default(TKeyProperty), default(TEntity));
}

private static IEnumerable<TEntity> AsHierarchy<TEntity, TKeyProperty, TChildrenProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Expression<Func<TEntity, TEntity>> parentPropertySelector,
Expression<Func<TEntity, TChildrenProperty>> childrenPropertySelector,
TKeyProperty parentKeyValue,
TEntity parentValue)
where TChildrenProperty : IEnumerable<TEntity>
{
foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
{
var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

parentProperty.SetValue(item, parentValue, null);

var childrenValues = items.AsHierarchy(keyProperty, parentKeyProperty, parentPropertySelector, childrenPropertySelector, keyProperty(item), item).ToList();
childrenProperty.SetValue(item, childrenValues, null);

yield return item;
}
}


And now I can call my database that way:



var result = myDbContext.Categories
.Select(category => new Dto.CategoryIndexDto()
{
CategoryId = category.CategoryId,
Name = category.Name,
Position = category.Position,
CategoryParentId = category.CategoryParentId,
})
.ToList() // query the database
.AsHierarchy(x => x.CategoryId, x => x.CategoryParentId, x => x.ParentCategory, x => x.ChildCategories)
.ToList();


Now my flat list is fully converted to an hierarchy list and they are all linked together by the navigation properties (ParentCategory and ChildCategories).



I would like to know if the AsHierarchy function is well coded for performance and if there's a way to simplify it?










share|improve this question







New contributor




Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




















  • You might find my question about the same task interesting. It's here.
    – t3chb0t
    Dec 20 at 22:18










  • I think there's a little difference with what I want. I want to move my objects in is own tree, not use another class that will make the tree. But I will read your code carefully and see if I can use some logic in my own implementation. Thank you very much!
    – Alexandre Jobin
    Dec 21 at 13:54
















4














I have a collection of items in my database with a ParentId property. I'm reading the categories as a flat structure and I would like to convert it to a hierarchy list by populating the ParentCategory and ChildCategories properties.



// Domain class
public class Category
{
public Category()
{
this.ChildCategories = new HashSet<Category>();
}

public int CategoryId { get; set; }
public string Name { get; set; }
public int Position { get; set; }

// ... many more properties

public int? CategoryParentId { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}

// DTO class
public class CategoryDto
{
public CategoryDto()
{
this.ChildCategories = new HashSet<CategoryDto>();
}

public int CategoryId { get; set; }
public string Name { get; set; }
public int? CategoryParentId { get; set; }
public CategoryDto ParentCategory { get; set; }
public IEnumerable<CategoryDto> ChildCategories { get; set; }
}


Since that we cannot do hierarchy query directly with Entity Framework Core, I can only retrieve them in a flat list like so:



var result = myDbContext.Categories
.Select(category => new Dto.CategoryIndexDto()
{
CategoryId = category.CategoryId,
Name = category.Name,
Position = category.Position,
CategoryParentId = category.CategoryParentId,
});


So I needed a way to convert this list of dto objects into hierarchy list where the ParentCategory and ChildCategories are populated for each dto object. So I've made this extension:



public static IEnumerable<TEntity> AsHierarchy<TEntity, TKeyProperty, TChildrenProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Expression<Func<TEntity, TEntity>> parentPropertySelector,
Expression<Func<TEntity, TChildrenProperty>> childrenPropertySelector)
where TChildrenProperty : IEnumerable<TEntity>
{
return items.AsHierarchy(keyProperty, parentKeyProperty, parentPropertySelector, childrenPropertySelector, default(TKeyProperty), default(TEntity));
}

private static IEnumerable<TEntity> AsHierarchy<TEntity, TKeyProperty, TChildrenProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Expression<Func<TEntity, TEntity>> parentPropertySelector,
Expression<Func<TEntity, TChildrenProperty>> childrenPropertySelector,
TKeyProperty parentKeyValue,
TEntity parentValue)
where TChildrenProperty : IEnumerable<TEntity>
{
foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
{
var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

parentProperty.SetValue(item, parentValue, null);

var childrenValues = items.AsHierarchy(keyProperty, parentKeyProperty, parentPropertySelector, childrenPropertySelector, keyProperty(item), item).ToList();
childrenProperty.SetValue(item, childrenValues, null);

yield return item;
}
}


And now I can call my database that way:



var result = myDbContext.Categories
.Select(category => new Dto.CategoryIndexDto()
{
CategoryId = category.CategoryId,
Name = category.Name,
Position = category.Position,
CategoryParentId = category.CategoryParentId,
})
.ToList() // query the database
.AsHierarchy(x => x.CategoryId, x => x.CategoryParentId, x => x.ParentCategory, x => x.ChildCategories)
.ToList();


Now my flat list is fully converted to an hierarchy list and they are all linked together by the navigation properties (ParentCategory and ChildCategories).



I would like to know if the AsHierarchy function is well coded for performance and if there's a way to simplify it?










share|improve this question







New contributor




Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




















  • You might find my question about the same task interesting. It's here.
    – t3chb0t
    Dec 20 at 22:18










  • I think there's a little difference with what I want. I want to move my objects in is own tree, not use another class that will make the tree. But I will read your code carefully and see if I can use some logic in my own implementation. Thank you very much!
    – Alexandre Jobin
    Dec 21 at 13:54














4












4








4







I have a collection of items in my database with a ParentId property. I'm reading the categories as a flat structure and I would like to convert it to a hierarchy list by populating the ParentCategory and ChildCategories properties.



// Domain class
public class Category
{
public Category()
{
this.ChildCategories = new HashSet<Category>();
}

public int CategoryId { get; set; }
public string Name { get; set; }
public int Position { get; set; }

// ... many more properties

public int? CategoryParentId { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}

// DTO class
public class CategoryDto
{
public CategoryDto()
{
this.ChildCategories = new HashSet<CategoryDto>();
}

public int CategoryId { get; set; }
public string Name { get; set; }
public int? CategoryParentId { get; set; }
public CategoryDto ParentCategory { get; set; }
public IEnumerable<CategoryDto> ChildCategories { get; set; }
}


Since that we cannot do hierarchy query directly with Entity Framework Core, I can only retrieve them in a flat list like so:



var result = myDbContext.Categories
.Select(category => new Dto.CategoryIndexDto()
{
CategoryId = category.CategoryId,
Name = category.Name,
Position = category.Position,
CategoryParentId = category.CategoryParentId,
});


So I needed a way to convert this list of dto objects into hierarchy list where the ParentCategory and ChildCategories are populated for each dto object. So I've made this extension:



public static IEnumerable<TEntity> AsHierarchy<TEntity, TKeyProperty, TChildrenProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Expression<Func<TEntity, TEntity>> parentPropertySelector,
Expression<Func<TEntity, TChildrenProperty>> childrenPropertySelector)
where TChildrenProperty : IEnumerable<TEntity>
{
return items.AsHierarchy(keyProperty, parentKeyProperty, parentPropertySelector, childrenPropertySelector, default(TKeyProperty), default(TEntity));
}

private static IEnumerable<TEntity> AsHierarchy<TEntity, TKeyProperty, TChildrenProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Expression<Func<TEntity, TEntity>> parentPropertySelector,
Expression<Func<TEntity, TChildrenProperty>> childrenPropertySelector,
TKeyProperty parentKeyValue,
TEntity parentValue)
where TChildrenProperty : IEnumerable<TEntity>
{
foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
{
var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

parentProperty.SetValue(item, parentValue, null);

var childrenValues = items.AsHierarchy(keyProperty, parentKeyProperty, parentPropertySelector, childrenPropertySelector, keyProperty(item), item).ToList();
childrenProperty.SetValue(item, childrenValues, null);

yield return item;
}
}


And now I can call my database that way:



var result = myDbContext.Categories
.Select(category => new Dto.CategoryIndexDto()
{
CategoryId = category.CategoryId,
Name = category.Name,
Position = category.Position,
CategoryParentId = category.CategoryParentId,
})
.ToList() // query the database
.AsHierarchy(x => x.CategoryId, x => x.CategoryParentId, x => x.ParentCategory, x => x.ChildCategories)
.ToList();


Now my flat list is fully converted to an hierarchy list and they are all linked together by the navigation properties (ParentCategory and ChildCategories).



I would like to know if the AsHierarchy function is well coded for performance and if there's a way to simplify it?










share|improve this question







New contributor




Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











I have a collection of items in my database with a ParentId property. I'm reading the categories as a flat structure and I would like to convert it to a hierarchy list by populating the ParentCategory and ChildCategories properties.



// Domain class
public class Category
{
public Category()
{
this.ChildCategories = new HashSet<Category>();
}

public int CategoryId { get; set; }
public string Name { get; set; }
public int Position { get; set; }

// ... many more properties

public int? CategoryParentId { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}

// DTO class
public class CategoryDto
{
public CategoryDto()
{
this.ChildCategories = new HashSet<CategoryDto>();
}

public int CategoryId { get; set; }
public string Name { get; set; }
public int? CategoryParentId { get; set; }
public CategoryDto ParentCategory { get; set; }
public IEnumerable<CategoryDto> ChildCategories { get; set; }
}


Since that we cannot do hierarchy query directly with Entity Framework Core, I can only retrieve them in a flat list like so:



var result = myDbContext.Categories
.Select(category => new Dto.CategoryIndexDto()
{
CategoryId = category.CategoryId,
Name = category.Name,
Position = category.Position,
CategoryParentId = category.CategoryParentId,
});


So I needed a way to convert this list of dto objects into hierarchy list where the ParentCategory and ChildCategories are populated for each dto object. So I've made this extension:



public static IEnumerable<TEntity> AsHierarchy<TEntity, TKeyProperty, TChildrenProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Expression<Func<TEntity, TEntity>> parentPropertySelector,
Expression<Func<TEntity, TChildrenProperty>> childrenPropertySelector)
where TChildrenProperty : IEnumerable<TEntity>
{
return items.AsHierarchy(keyProperty, parentKeyProperty, parentPropertySelector, childrenPropertySelector, default(TKeyProperty), default(TEntity));
}

private static IEnumerable<TEntity> AsHierarchy<TEntity, TKeyProperty, TChildrenProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Expression<Func<TEntity, TEntity>> parentPropertySelector,
Expression<Func<TEntity, TChildrenProperty>> childrenPropertySelector,
TKeyProperty parentKeyValue,
TEntity parentValue)
where TChildrenProperty : IEnumerable<TEntity>
{
foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
{
var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

parentProperty.SetValue(item, parentValue, null);

var childrenValues = items.AsHierarchy(keyProperty, parentKeyProperty, parentPropertySelector, childrenPropertySelector, keyProperty(item), item).ToList();
childrenProperty.SetValue(item, childrenValues, null);

yield return item;
}
}


And now I can call my database that way:



var result = myDbContext.Categories
.Select(category => new Dto.CategoryIndexDto()
{
CategoryId = category.CategoryId,
Name = category.Name,
Position = category.Position,
CategoryParentId = category.CategoryParentId,
})
.ToList() // query the database
.AsHierarchy(x => x.CategoryId, x => x.CategoryParentId, x => x.ParentCategory, x => x.ChildCategories)
.ToList();


Now my flat list is fully converted to an hierarchy list and they are all linked together by the navigation properties (ParentCategory and ChildCategories).



I would like to know if the AsHierarchy function is well coded for performance and if there's a way to simplify it?







c# .net extension-methods






share|improve this question







New contributor




Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











share|improve this question







New contributor




Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









share|improve this question




share|improve this question






New contributor




Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









asked Dec 20 at 21:27









Alexandre Jobin

1211




1211




New contributor




Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





New contributor





Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






Alexandre Jobin is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.












  • You might find my question about the same task interesting. It's here.
    – t3chb0t
    Dec 20 at 22:18










  • I think there's a little difference with what I want. I want to move my objects in is own tree, not use another class that will make the tree. But I will read your code carefully and see if I can use some logic in my own implementation. Thank you very much!
    – Alexandre Jobin
    Dec 21 at 13:54


















  • You might find my question about the same task interesting. It's here.
    – t3chb0t
    Dec 20 at 22:18










  • I think there's a little difference with what I want. I want to move my objects in is own tree, not use another class that will make the tree. But I will read your code carefully and see if I can use some logic in my own implementation. Thank you very much!
    – Alexandre Jobin
    Dec 21 at 13:54
















You might find my question about the same task interesting. It's here.
– t3chb0t
Dec 20 at 22:18




You might find my question about the same task interesting. It's here.
– t3chb0t
Dec 20 at 22:18












I think there's a little difference with what I want. I want to move my objects in is own tree, not use another class that will make the tree. But I will read your code carefully and see if I can use some logic in my own implementation. Thank you very much!
– Alexandre Jobin
Dec 21 at 13:54




I think there's a little difference with what I want. I want to move my objects in is own tree, not use another class that will make the tree. But I will read your code carefully and see if I can use some logic in my own implementation. Thank you very much!
– Alexandre Jobin
Dec 21 at 13:54










1 Answer
1






active

oldest

votes


















0














Your algorithm seems to be working as expected, and for very small data sets it is okay. But for larger data sets it is not optimal and for sets larger than 1000 it is very inefficient.



Some minor problems:




foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
{
var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;



The member information for the child and parent properties are invariant per item, so place their statements before the loop:



var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
{




Personally I try to only use reflection as the last resort and here it is not necessary. You can change your method signatures to something like this instead:



public static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Action<TEntity, TEntity> parentSetter,
Action<TEntity, TEntity> childSetter)
{
return items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, default(TKeyProperty), default(TEntity));
}

private static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
this IEnumerable<TEntity> items,
Func<TEntity, TKeyProperty> keyProperty,
Func<TEntity, TKeyProperty> parentKeyProperty,
Action<TEntity, TEntity> parentSetter,
Action<TEntity, TEntity> childSetter,
TKeyProperty parentKeyValue,
TEntity parentValue)
{
foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
{
parentSetter(item, parentValue);

var childrenValues = items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, keyProperty(item), item).ToList();
foreach (var child in childrenValues)
{
childSetter(child, item);
}

yield return item;
}




But the overall problem with your algorithm is this:




  foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
{
...



Here you requery the entire collection for every item in the collection to find possible child elements to the argument parent and that is too expensive.



If you know that the elements in the data set are given in hierarchical order, the most optimized solution would be to go along with Nikita B's answer in the link suggested by t3chb0t.



If not, you could go along the below path:



public delegate void ParentSetter<T>(T child, T parent);
public delegate void ChildSetter<T>(T child, T parent);

public static IEnumerable<T> AsHierarchy<T, TID>(
this IEnumerable<T> elements,
Func<T, TID> idSelector,
Func<T, TID> parentIdSelector,
ParentSetter<T> parentSetter,
ChildSetter<T> childAdder,
TID rootId)
{
Dictionary<TID, T> lookUp = elements.ToDictionary(e => idSelector(e));

foreach (T element in lookUp.Values)
{
TID parentId = parentIdSelector(element);
if (!lookUp.TryGetValue(parentId, out T parent))
{
if (parentId.Equals(rootId))
{
yield return element;
continue;
}
else
throw new InvalidOperationException($"Parent not found for: {element}");
}

parentSetter(element, parent);
childAdder(element, parent);
}
}


Here a dictionary is created as a look up table for the parent of each element. To create a dictionary is a remarkable inexpensive operation.



When an elements parent is not found it is checked if the parent id is the root id. If true the element is returned otherwise an exception is thrown.



A pair of explicit delegates for setting the parent and child are provided in order to show which argument is supposed to be the child and which the parent.






share|improve this answer























    Your Answer





    StackExchange.ifUsing("editor", function () {
    return StackExchange.using("mathjaxEditing", function () {
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    });
    });
    }, "mathjax-editing");

    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "196"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });






    Alexandre Jobin is a new contributor. Be nice, and check out our Code of Conduct.










    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210076%2fgeneric-extension-to-transform-a-flat-nested-list-to-hierarchy-list%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    0














    Your algorithm seems to be working as expected, and for very small data sets it is okay. But for larger data sets it is not optimal and for sets larger than 1000 it is very inefficient.



    Some minor problems:




    foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
    {
    var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
    var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;



    The member information for the child and parent properties are invariant per item, so place their statements before the loop:



    var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
    var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

    foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
    {




    Personally I try to only use reflection as the last resort and here it is not necessary. You can change your method signatures to something like this instead:



    public static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
    this IEnumerable<TEntity> items,
    Func<TEntity, TKeyProperty> keyProperty,
    Func<TEntity, TKeyProperty> parentKeyProperty,
    Action<TEntity, TEntity> parentSetter,
    Action<TEntity, TEntity> childSetter)
    {
    return items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, default(TKeyProperty), default(TEntity));
    }

    private static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
    this IEnumerable<TEntity> items,
    Func<TEntity, TKeyProperty> keyProperty,
    Func<TEntity, TKeyProperty> parentKeyProperty,
    Action<TEntity, TEntity> parentSetter,
    Action<TEntity, TEntity> childSetter,
    TKeyProperty parentKeyValue,
    TEntity parentValue)
    {
    foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
    {
    parentSetter(item, parentValue);

    var childrenValues = items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, keyProperty(item), item).ToList();
    foreach (var child in childrenValues)
    {
    childSetter(child, item);
    }

    yield return item;
    }




    But the overall problem with your algorithm is this:




      foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
    {
    ...



    Here you requery the entire collection for every item in the collection to find possible child elements to the argument parent and that is too expensive.



    If you know that the elements in the data set are given in hierarchical order, the most optimized solution would be to go along with Nikita B's answer in the link suggested by t3chb0t.



    If not, you could go along the below path:



    public delegate void ParentSetter<T>(T child, T parent);
    public delegate void ChildSetter<T>(T child, T parent);

    public static IEnumerable<T> AsHierarchy<T, TID>(
    this IEnumerable<T> elements,
    Func<T, TID> idSelector,
    Func<T, TID> parentIdSelector,
    ParentSetter<T> parentSetter,
    ChildSetter<T> childAdder,
    TID rootId)
    {
    Dictionary<TID, T> lookUp = elements.ToDictionary(e => idSelector(e));

    foreach (T element in lookUp.Values)
    {
    TID parentId = parentIdSelector(element);
    if (!lookUp.TryGetValue(parentId, out T parent))
    {
    if (parentId.Equals(rootId))
    {
    yield return element;
    continue;
    }
    else
    throw new InvalidOperationException($"Parent not found for: {element}");
    }

    parentSetter(element, parent);
    childAdder(element, parent);
    }
    }


    Here a dictionary is created as a look up table for the parent of each element. To create a dictionary is a remarkable inexpensive operation.



    When an elements parent is not found it is checked if the parent id is the root id. If true the element is returned otherwise an exception is thrown.



    A pair of explicit delegates for setting the parent and child are provided in order to show which argument is supposed to be the child and which the parent.






    share|improve this answer




























      0














      Your algorithm seems to be working as expected, and for very small data sets it is okay. But for larger data sets it is not optimal and for sets larger than 1000 it is very inefficient.



      Some minor problems:




      foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
      {
      var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
      var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;



      The member information for the child and parent properties are invariant per item, so place their statements before the loop:



      var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
      var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

      foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
      {




      Personally I try to only use reflection as the last resort and here it is not necessary. You can change your method signatures to something like this instead:



      public static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
      this IEnumerable<TEntity> items,
      Func<TEntity, TKeyProperty> keyProperty,
      Func<TEntity, TKeyProperty> parentKeyProperty,
      Action<TEntity, TEntity> parentSetter,
      Action<TEntity, TEntity> childSetter)
      {
      return items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, default(TKeyProperty), default(TEntity));
      }

      private static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
      this IEnumerable<TEntity> items,
      Func<TEntity, TKeyProperty> keyProperty,
      Func<TEntity, TKeyProperty> parentKeyProperty,
      Action<TEntity, TEntity> parentSetter,
      Action<TEntity, TEntity> childSetter,
      TKeyProperty parentKeyValue,
      TEntity parentValue)
      {
      foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
      {
      parentSetter(item, parentValue);

      var childrenValues = items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, keyProperty(item), item).ToList();
      foreach (var child in childrenValues)
      {
      childSetter(child, item);
      }

      yield return item;
      }




      But the overall problem with your algorithm is this:




        foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
      {
      ...



      Here you requery the entire collection for every item in the collection to find possible child elements to the argument parent and that is too expensive.



      If you know that the elements in the data set are given in hierarchical order, the most optimized solution would be to go along with Nikita B's answer in the link suggested by t3chb0t.



      If not, you could go along the below path:



      public delegate void ParentSetter<T>(T child, T parent);
      public delegate void ChildSetter<T>(T child, T parent);

      public static IEnumerable<T> AsHierarchy<T, TID>(
      this IEnumerable<T> elements,
      Func<T, TID> idSelector,
      Func<T, TID> parentIdSelector,
      ParentSetter<T> parentSetter,
      ChildSetter<T> childAdder,
      TID rootId)
      {
      Dictionary<TID, T> lookUp = elements.ToDictionary(e => idSelector(e));

      foreach (T element in lookUp.Values)
      {
      TID parentId = parentIdSelector(element);
      if (!lookUp.TryGetValue(parentId, out T parent))
      {
      if (parentId.Equals(rootId))
      {
      yield return element;
      continue;
      }
      else
      throw new InvalidOperationException($"Parent not found for: {element}");
      }

      parentSetter(element, parent);
      childAdder(element, parent);
      }
      }


      Here a dictionary is created as a look up table for the parent of each element. To create a dictionary is a remarkable inexpensive operation.



      When an elements parent is not found it is checked if the parent id is the root id. If true the element is returned otherwise an exception is thrown.



      A pair of explicit delegates for setting the parent and child are provided in order to show which argument is supposed to be the child and which the parent.






      share|improve this answer


























        0












        0








        0






        Your algorithm seems to be working as expected, and for very small data sets it is okay. But for larger data sets it is not optimal and for sets larger than 1000 it is very inefficient.



        Some minor problems:




        foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
        {
        var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
        var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;



        The member information for the child and parent properties are invariant per item, so place their statements before the loop:



        var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
        var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

        foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
        {




        Personally I try to only use reflection as the last resort and here it is not necessary. You can change your method signatures to something like this instead:



        public static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
        this IEnumerable<TEntity> items,
        Func<TEntity, TKeyProperty> keyProperty,
        Func<TEntity, TKeyProperty> parentKeyProperty,
        Action<TEntity, TEntity> parentSetter,
        Action<TEntity, TEntity> childSetter)
        {
        return items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, default(TKeyProperty), default(TEntity));
        }

        private static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
        this IEnumerable<TEntity> items,
        Func<TEntity, TKeyProperty> keyProperty,
        Func<TEntity, TKeyProperty> parentKeyProperty,
        Action<TEntity, TEntity> parentSetter,
        Action<TEntity, TEntity> childSetter,
        TKeyProperty parentKeyValue,
        TEntity parentValue)
        {
        foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
        {
        parentSetter(item, parentValue);

        var childrenValues = items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, keyProperty(item), item).ToList();
        foreach (var child in childrenValues)
        {
        childSetter(child, item);
        }

        yield return item;
        }




        But the overall problem with your algorithm is this:




          foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
        {
        ...



        Here you requery the entire collection for every item in the collection to find possible child elements to the argument parent and that is too expensive.



        If you know that the elements in the data set are given in hierarchical order, the most optimized solution would be to go along with Nikita B's answer in the link suggested by t3chb0t.



        If not, you could go along the below path:



        public delegate void ParentSetter<T>(T child, T parent);
        public delegate void ChildSetter<T>(T child, T parent);

        public static IEnumerable<T> AsHierarchy<T, TID>(
        this IEnumerable<T> elements,
        Func<T, TID> idSelector,
        Func<T, TID> parentIdSelector,
        ParentSetter<T> parentSetter,
        ChildSetter<T> childAdder,
        TID rootId)
        {
        Dictionary<TID, T> lookUp = elements.ToDictionary(e => idSelector(e));

        foreach (T element in lookUp.Values)
        {
        TID parentId = parentIdSelector(element);
        if (!lookUp.TryGetValue(parentId, out T parent))
        {
        if (parentId.Equals(rootId))
        {
        yield return element;
        continue;
        }
        else
        throw new InvalidOperationException($"Parent not found for: {element}");
        }

        parentSetter(element, parent);
        childAdder(element, parent);
        }
        }


        Here a dictionary is created as a look up table for the parent of each element. To create a dictionary is a remarkable inexpensive operation.



        When an elements parent is not found it is checked if the parent id is the root id. If true the element is returned otherwise an exception is thrown.



        A pair of explicit delegates for setting the parent and child are provided in order to show which argument is supposed to be the child and which the parent.






        share|improve this answer














        Your algorithm seems to be working as expected, and for very small data sets it is okay. But for larger data sets it is not optimal and for sets larger than 1000 it is very inefficient.



        Some minor problems:




        foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
        {
        var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
        var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;



        The member information for the child and parent properties are invariant per item, so place their statements before the loop:



        var parentProperty = (parentPropertySelector.Body as MemberExpression).Member as PropertyInfo;
        var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as PropertyInfo;

        foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
        {




        Personally I try to only use reflection as the last resort and here it is not necessary. You can change your method signatures to something like this instead:



        public static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
        this IEnumerable<TEntity> items,
        Func<TEntity, TKeyProperty> keyProperty,
        Func<TEntity, TKeyProperty> parentKeyProperty,
        Action<TEntity, TEntity> parentSetter,
        Action<TEntity, TEntity> childSetter)
        {
        return items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, default(TKeyProperty), default(TEntity));
        }

        private static IEnumerable<TEntity> AsHierarchyReview<TEntity, TKeyProperty>(
        this IEnumerable<TEntity> items,
        Func<TEntity, TKeyProperty> keyProperty,
        Func<TEntity, TKeyProperty> parentKeyProperty,
        Action<TEntity, TEntity> parentSetter,
        Action<TEntity, TEntity> childSetter,
        TKeyProperty parentKeyValue,
        TEntity parentValue)
        {
        foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
        {
        parentSetter(item, parentValue);

        var childrenValues = items.AsHierarchyReview(keyProperty, parentKeyProperty, parentSetter, childSetter, keyProperty(item), item).ToList();
        foreach (var child in childrenValues)
        {
        childSetter(child, item);
        }

        yield return item;
        }




        But the overall problem with your algorithm is this:




          foreach (var item in items.Where(item => parentKeyProperty(item).Equals(parentKeyValue)))
        {
        ...



        Here you requery the entire collection for every item in the collection to find possible child elements to the argument parent and that is too expensive.



        If you know that the elements in the data set are given in hierarchical order, the most optimized solution would be to go along with Nikita B's answer in the link suggested by t3chb0t.



        If not, you could go along the below path:



        public delegate void ParentSetter<T>(T child, T parent);
        public delegate void ChildSetter<T>(T child, T parent);

        public static IEnumerable<T> AsHierarchy<T, TID>(
        this IEnumerable<T> elements,
        Func<T, TID> idSelector,
        Func<T, TID> parentIdSelector,
        ParentSetter<T> parentSetter,
        ChildSetter<T> childAdder,
        TID rootId)
        {
        Dictionary<TID, T> lookUp = elements.ToDictionary(e => idSelector(e));

        foreach (T element in lookUp.Values)
        {
        TID parentId = parentIdSelector(element);
        if (!lookUp.TryGetValue(parentId, out T parent))
        {
        if (parentId.Equals(rootId))
        {
        yield return element;
        continue;
        }
        else
        throw new InvalidOperationException($"Parent not found for: {element}");
        }

        parentSetter(element, parent);
        childAdder(element, parent);
        }
        }


        Here a dictionary is created as a look up table for the parent of each element. To create a dictionary is a remarkable inexpensive operation.



        When an elements parent is not found it is checked if the parent id is the root id. If true the element is returned otherwise an exception is thrown.



        A pair of explicit delegates for setting the parent and child are provided in order to show which argument is supposed to be the child and which the parent.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Dec 22 at 7:46

























        answered Dec 22 at 4:55









        Henrik Hansen

        6,7781824




        6,7781824






















            Alexandre Jobin is a new contributor. Be nice, and check out our Code of Conduct.










            draft saved

            draft discarded


















            Alexandre Jobin is a new contributor. Be nice, and check out our Code of Conduct.













            Alexandre Jobin is a new contributor. Be nice, and check out our Code of Conduct.












            Alexandre Jobin is a new contributor. Be nice, and check out our Code of Conduct.
















            Thanks for contributing an answer to Code Review Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            Use MathJax to format equations. MathJax reference.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210076%2fgeneric-extension-to-transform-a-flat-nested-list-to-hierarchy-list%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Сан-Квентин

            8-я гвардейская общевойсковая армия

            Алькесар