Saturday, June 17, 2023

StackOverflowException when use hierarchical object mapping with AutoMapper and Fluent NHibernate lazy loading

Suppose that we have the following class Category with self-reference (regular parent-child hierarchy):

public class Category
{
    public virtual T Id { get; set; }
    public virtual string Name { get; set; }
    public virtual Category ParentCategory { get; set; }
    public virtual IList<Category> ChildCategories { get; set; }
}

(properties are virtual which is required by NHibernate). Regular Fluent NHibernate mapping for this class looks like this:

public class CategoryMap : ClassMap<Category>
{
    public CategoryMap()
    {
        Table("[Category]");
        Id(x => x.Id);
        
        Map(x => x.Name);

        References(x => x.ParentCategory)
            .Column("ParentCategoryId");
        
        HasMany(x => x.ChildCategories)
            .KeyColumn("ParentCategoryId")
            .Inverse()
            .AsBag();
    }
}

Then we want to show category in the view of ASP.Net Core MVC app and use the following view model for that:

public class CategoryModel
{
    public int? Id { get; set; }

    public string Name { get; set; }

    public string ParentCategoryName { get; set; }
}

For mapping Category to CategoryModel the following AutoMapper mapping is used:

public class ViewModelProfile : Profile
{
    public ViewModelProfile()
    {
        CreateMap<Category, CategoryModel>()
            .ForMember(x => x.ParentCategoryName, o => o.MapFrom(c => c.ParentCategory == null ? "" : c.ParentCategory.Name))
    }
}

i.e. when ParentCategory is null then ParentCategoryName property of view model is empty string, otherwise ParentCategoryName will return name of parent category. Until now everything looks good. Problems will start when we will try to create category of nesting level which is greater than 1, i.e. this will work:

child category -> parent category

but with deeper hierarchy:

child category1 -> child category2 -> parent category

we will get StackOverflowException when will try to pass view model (created by AutoMapper) to the view. Googling this problem suggests to use MaxDepth(2) call when create AotuMapper map profile to limit number of processed hierarchy levels but it didn't help because of some reason.

Solution which worked was to specify to not use lazy loading for ParentCategory property in Fluent NHibernate mapping (NHibernate uses lazy loading by default):

public class CategoryMap : ClassMap<Category>
{
    public CategoryMap()
    {
        Table("[Category]");
        Id(x => x.Id);
        
        Map(x => x.Name);

        References(x => x.ParentCategory)
            .Column("ParentCategoryId")
            .Not.LazyLoad();
        
        HasMany(x => x.ChildCategories)
            .KeyColumn("ParentCategoryId")
            .Inverse()
            .AsBag();
    }
}

In theory it may cause performance problems with large amount of data and deep hierarchy levels but with reasonable amount of data should work quite well.

No comments:

Post a Comment