views:

379

answers:

4

I've come up against a problem in converting my Fluent NH mapping to Sharp Architecture. I like the platform for it's ease, however it seems to handle entity mappings slightly differently to pure Fluent NH.

I have a Entity 'Category' that is a simple tree structure. I have to override the auto-mapping as there is a M:M property that I need to add in (not included in code below).

When I create tests on the repository, the GetAll method returns all Categories as it should, however the Children property just infinitely loops itself. i.e. the list of children for each category only contains itself, in and unending loop.

/// The Entity ///

public class Category : Entity
{
    public Category()
    {
        InitMembers();
    }

    /// <summary>
    /// Creates valid domain object
    /// </summary>
    public Category(string name)
        : this()
    {
        Name = name;
    }

    /// <summary>
    /// Creates valid domain object
    /// </summary>
    public Category(string name, int depth)
        : this()
    {
        Name = name;
        Depth = depth;
    }

    private void InitMembers()
    {
        Children = new List<Category>();
    }

    [DomainSignature]
    [NotNullNotEmpty]
    public virtual string Name { get; protected set; }

    [DomainSignature]
    public virtual int Depth { get; protected set; }

    public virtual Category Parent { get; set; }

    public virtual IList<Category> Children { get; private set; }

    public virtual void AddChild(Category category)
    {
        category.Parent = this;
        Children.Add(category);
    }
}

/// The Mapping ///

public class CategoryMap : IAutoMappingOverride<Category>
{
    public void Override(AutoMap<Category> mapping)
    {
        mapping.Id(x => x.Id, "CategoryId")
            .WithUnsavedValue(0)
            .GeneratedBy.Identity();

        mapping.Map(x => x.Name).WithLengthOf(50);

        mapping.Map(x => x.Depth);

        mapping.HasMany<Category>(x => x.Children)
            .Inverse()
            .Cascade.All()
            .KeyColumnNames.Add("Parent_id")
            .AsBag();
    }
}

/// The Data Repository Tests ///

[TestFixture]
[Category("DB Tests")]
public class CategoryRepositoryTests : RepositoryTestsBase
{
    private readonly IRepository<Category> _repository = new Repository<Category>();

    protected override void LoadTestData()
    {
        CreatePersistedCategory("Root 1");
        CreatePersistedCategory("Root 2");
        CreatePersistedCategoryWithChildren("Level 1", "Level 2", "Level 3");
    }

    [Test]
    public void CanGetAllCategories()
    {
        var categories = _repository.GetAll();
        categories.ShouldNotBeNull();
        categories.Count.ShouldEqual(5);
    }

    [Test]
    public void CanGetCategoryById()
    {
        var category = _repository.Get(1);
        category.Name.ShouldEqual("Root 1");
        category.Depth.ShouldEqual(1);
    }

    [Test]
    public void CanGetCategoryChildren()
    {
        var category = _repository.Get(3);
        category.Name.ShouldEqual("Level 1");
        category.Depth.ShouldEqual(1);
        category.Children.ShouldNotBeNull();
        category.Children.Count.ShouldEqual(1);
        category.Children[0].Name.ShouldEqual("Level 2");
        category.Children[0].Depth.ShouldEqual(2);
        category.Children[0].Children.ShouldNotBeNull();
        category.Children[0].Children.Count.ShouldEqual(1);
        category.Children[0].Children[0].Name.ShouldEqual("Level 3");
        category.Children[0].Children[0].Depth.ShouldEqual(3);
    }


    private void CreatePersistedCategory(string categoryName)
    {
        var category = new Category(categoryName, 1);
        _repository.SaveOrUpdate(category);
        FlushSessionAndEvict(category);
    }

    private void CreatePersistedCategoryWithChildren(string category1, string category2, string category3)
    {
        var cat1 = new Category(category1, 1);
        var cat2 = new Category(category2, 2) { Parent = cat1 };
        var cat3 = new Category(category3, 3) { Parent = cat2 };
        cat1.AddChild(cat2);
        cat2.AddChild(cat3);
        _repository.SaveOrUpdate(cat1);
        FlushSessionAndEvict(cat1);
    }
}
+1  A: 

Managed to work it out, after much Mapping tweaking. The Auto-mapping stuff although very cool requires some understanding. RTFM for me...

Jordan
A: 

Would have been a good idea to post your answer

Chris Nicola
A: 

Right you are, I hadn't yet discovered or understood the Auto-mapping conventions: TableNameConvention, PrimaryKeyConvention, and specifically HasManyConvention. The default S#arp code likes to pluralise its database tables, and have Id columns with the table name prefixed, i.e. CategoryId.

I don't like to pluralise, and I like consistent Id columns, 'Id' suffices. And my foreign key references were different style to, I like Category_id.

public class HasManyConvention : IHasManyConvention
{
    public bool Accept(IOneToManyCollectionInstance oneToManyPart)
    {
        return true;
    }

    public void Apply(IOneToManyCollectionInstance oneToManyPart)
    {
        oneToManyPart.KeyColumnNames.Add(oneToManyPart.EntityType.Name + "_id");
    }
}

public class PrimaryKeyConvention : IIdConvention
{
    public bool Accept(IIdentityInstance id)
    {
        return true;
    }

    public void Apply(IIdentityInstance id)
    {
        id.Column("Id");
    }
}

However now this all works a treat but I now have a problem with Many-to-many mappings. It seems S#arp doesn't quite support them yet. My mapping overrides don't seem to work, nothing gets inserted into my mapping table in the database.

See: http://stackoverflow.com/questions/1816342/sarp-architecture-many-to-many-mapping-overrides-not-working

Jordan
A: 

I was not able to solve this using fluent conventions and from what I have seen searching around this currently can't be done using conventions. Fluent assumes that a self-referencing tree like this is many-to-many, so in your case I assume you are trying to map a many-to-many relationship and so there should be no problem.

In my case I needed to map it as many-to-one (for a strict hierarchy of nested comments and replies). The only way I could figure out to do this was setting an override for the parent-child class to map that relationship. Fortunately it is very easy.

I would love to know if there is a way to successfully map many-to-one like this with conventions though.

Chris Nicola