Showing posts with label Automapper. Show all posts
Showing posts with label Automapper. Show all posts

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.

Monday, April 22, 2013

Map open generic class to open generic interface in StructureMap

Suppose that you have the following generic interface:

   1: public interface IMapper<T, U>
   2: {
   3:     U Map(T source);
   4: }

And you use Automapper in implementation:

   1: public class Mapper<T, U> : IMapper<T, U>
   2: {
   3:     public U Map(T source)
   4:     {
   5:         return Mapper.Map<T, U>(source);
   6:     }
   7: }

I.e. in your code you will have bunch of mapping interfaces like IMapper<Foo1, Bar1>, IMapper<Foo2, Bar2>, …, which will be implemented by Mapper<Foo1, Bar1>, Mapper<Foo2, Bar2>, …, i.e. by the same class with the same generic parameters. How to configure mapping for these interfaces in StructureMap? First way is to write all possible mappings in the custom registry:

   1: public class MyRegistry : Registry
   2: {
   3:     public MyRegistry()
   4:     {
   5:         Scan(x =>
   6:                  {
   7:                      x.Assembly("MyAssembly");
   8:                  });
   9:         For(typeof(IMapper<Foo1,Bar1>)).Use(typeof(Mapper<Foo1,Bar1>));
  10:         For(typeof(IMapper<Foo2,Bar2>)).Use(typeof(Mapper<Foo2,Bar2>));
  11:         ...
  12:         For(typeof(IMapper<Foo_n,Bar_n>)).Use(typeof(Mapper<Foo_n,Bar_n>));
  13:     }
  14: }

But with this approach you will need to add new mapping for each new pair of classes which you will add in the future. Is there an easier way to define mapping for open generic interface IMapper<T,U> to open generic class Mapper<T,U>? If you would have own class for each mapping (like Foo1Bar1Mapper, Foo2Bar2Mapper, …), then you would be able to use ConnectImplementationsToTypesClosing method, but not in this example, because we have the same class for all mappings. However, it is also possible for our case:

   1: public class MyRegistry : Registry
   2: {
   3:     public MyRegistry()
   4:     {
   5:         Scan(x =>
   6:                  {
   7:                      x.Assembly("MyProcessor");
   8:                  });
   9:         For(typeof(IMapper<,>)).Use(typeof(Mapper<,>));
  10:     }
  11: }

Mapping is defined in line 9. With this configuration Mapper<,> class will be used for all new mappings which you will use in the project.