Is Kigg MVC application DRY? Can we tweak the Repo

2019-07-13 01:03发布

问题:

I was recently taking a look at the Kazi Manzur Kigg MVC implementation (Kazi rocks) and noticed some code that seemed to defeat the DRY/SOC principle. I'd love to have everyone's thoughts on a possible refactor to separate concerns.

Kigg implements both an Add and Remove method on each repository class ( Note: BaseRepository has virtual methods than can be overloaded by each concrete implementation.)

The implementations for the Kigg.Repository.LinqToSql.CategoryRepository and the Kigg.Repository.LinqToSql.StoryRepository both cascade deletes via their Remove methods in order to delete child entities. ( Note: Category has a parent relationship (one-to-many) to Story, so they share the same child relationships from Story down through the object graph) see diagram . The offending code is the way both repositories delete each others child entities:

CategoryRepository

namespace Kigg.Repository.LinqToSql.CategoryRepository{
    //using statements omitted
    public class CategoryRepository : BaseRepository<ICategory, Category>, ICategoryRepository
    {
    //code omitted

    public override void Remove(ICategory entity)
    {
        Check.Argument.IsNotNull(entity, "entity");

        Category category = (Category) entity;

        Database.DeleteAll(Database.StoryViewDataSource.Where(v => v.Story.CategoryId == category.Id));
        Database.DeleteAll(Database.CommentSubscribtionDataSource.Where(cs => cs.Story.CategoryId == category.Id));
        Database.DeleteAll(Database.CommentDataSource.Where(c => c.Story.CategoryId == category.Id));
        Database.DeleteAll(Database.VoteDataSource.Where(v => v.Story.CategoryId == category.Id));
        Database.DeleteAll(Database.MarkAsSpamDataSource.Where(sp => sp.Story.CategoryId == category.Id));
        Database.DeleteAll(Database.StoryTagDataSource.Where(st => st.Story.CategoryId == category.Id));
        Database.DeleteAll(Database.StoryDataSource.Where(s => s.CategoryId == category.Id));

        base.Remove(category);
    }
}
}

StoryRepository

namespace Kigg.Repository.LinqToSql
{

    //using statements omitted


    public class StoryRepository : BaseRepository<IStory, Story>, IStoryRepository
    {

    //code omitted


    public override void Remove(IStory entity)
    {
        Check.Argument.IsNotNull(entity, "entity");

        Story story = (Story) entity;

        Database.DeleteAll(Database.StoryViewDataSource.Where(sv => sv.StoryId == story.Id));
        Database.DeleteAll(Database.CommentSubscribtionDataSource.Where(cs => cs.StoryId == story.Id));
        Database.DeleteAll(Database.CommentDataSource.Where(c => c.StoryId == story.Id));
        Database.DeleteAll(Database.VoteDataSource.Where(v => v.StoryId == story.Id));
        Database.DeleteAll(Database.MarkAsSpamDataSource.Where(sp => sp.StoryId == story.Id));
        Database.DeleteAll(Database.StoryTagDataSource.Where(st => st.StoryId == story.Id));

        base.Remove(story);
    }
    }

}

Would I be correct in assuming that a better design would have the CategoryRepository calling the Remove method on the StoryRepository, thereby delegating the concern for the Story's child object removal to the StoryRepository where it belongs? From a maintenance point-of-view any additions to the Story's children would require DeleteAll calls to be added to both the CategoryRepository and the StoryRepository.

What would be a better implementation?

Should the CategoryRepository be refactored to use the StoryRepository directly?: CategoryRepository (refactor)

namespace Kigg.Repository.LinqToSql.CategoryRepository{
    //using statements omitted
    public class CategoryRepository : BaseRepository<ICategory, Category>, ICategoryRepository
    {
    //code omitted

    public override void Remove(ICategory entity)
    {
        Check.Argument.IsNotNull(entity, "entity");

        Category category = (Category) entity;

        // refactor - start
        StoryRepository _storyRepository = new StoryRepository( Database );
        category.Stories.ForEach( story => _storyRepository.Remove( story ) );
        // refactor - end

        base.Remove(category);
    }
}
}

This refactor would allow the CategoryRepository to reuse the removal logic in the StoryRepository and should also reuse the same LinqToSql DataContext that is referenced by the Database argument given to the StoryRepository constructor. But when it comes to unit testing it starts to have an odor.

Would a better refactor include using IoC (Kigg uses Unity as its Ioc container) to inject the PerWebRequest scoped instance of IStoryRepository into the CategoryRepository's constructor?

CategoryRepository (refactor take 2)

namespace Kigg.Repository.LinqToSql.CategoryRepository{
    //using statements omitted
    public class CategoryRepository : BaseRepository<ICategory, Category>, ICategoryRepository
    {


    private readonly IStoryRepository _storyRepository;

    public CategoryRepository(IDatabase database, IStoryRepository storyRepository) : base(database)
    {
        Check.Argument.IsNotNull(storyRepository, "storyRepository");
        _storyRepository = storyRepository;
    }

    public CategoryRepository(IDatabaseFactory factory, IStoryRepository storyRepository) : base(factory)
    {
        Check.Argument.IsNotNull(storyRepository, "storyRepository");
        _storyRepository = storyRepository;
    }

    //code omitted

    public override void Remove(ICategory entity)
    {
        {
        Check.Argument.IsNotNull(entity, "entity");

        Category category = (Category) entity;

        // refactor - start
        category.Stories.ForEach( story => _storyRepository.Remove( story ) );
        // refactor - end

        base.Remove(category);
    }
}
}

With this second refactor we can now inject an instance of IStoryRepository into CategoryRepository during unit testing and via Unity Ioc. Of course we would have to extend this refactor to each repository class so they could tend to their own children's responsibilites.

What are everyone's thoughts?

回答1:

Excuse my ignorance on L2S (i use NHibernate), but does it not handle cascades automatically ?



回答2:

JBland, LingToSql will handle cascades as defined in the database. Although, SQL Server does not allow multiple cascade paths.

The Kigg database doesn't have cascading Delete or Update rules defined, but they aren't needed if the repository implementation performs the necessary deletes.