多对多和级联:实体删除问题

时间:2010-12-07 17:34:21

标签: nhibernate fluent-nhibernate nhibernate-mapping cascading-deletes

我有以下两个实体及其映射:

public class VideoCategory : BaseEntity<VideoCategory>
{
    private readonly Iesi.Collections.Generic.ISet<VideoFolder> folders = new HashedSet<VideoFolder>();

    public VideoCategory()
    {

    }

    public VideoCategory(string name)
    {
        Name = name;
    }

    public string Name { get; set; }


    public IEnumerable<VideoFolder> Folders { get { return folders; } }

    public void AddFolder(VideoFolder videoFolder)
    {
        Contract.Requires(videoFolder != null);

        if (folders.Contains(videoFolder))
            return;

        folders.Add(videoFolder);
        videoFolder.AddCategory(this);
    }

    public void RemoveFolder(VideoFolder videoFolder)
    {
        folders.Remove(videoFolder);
        videoFolder.RemoveCategory(this);
    }

    public void ClearFolders()
    {
        folders.ForEach(f => f.RemoveCategory(this));

        folders.Clear();
    }
}

public class VideoFolder : BaseEntity<VideoFolder>
{
    private readonly Iesi.Collections.Generic.ISet<VideoCategory> categories = new HashedSet<VideoCategory>();

    public VideoFolder()
    {

    }

    public VideoFolder(string path)
    {
        Path = path;
    }

    public string Path { get; set; }
    public string Name { get; private set; }

    public IEnumerable<VideoCategory> Categories { get { return categories; } }

    protected internal void AddCategory(VideoCategory videoCategory)
    {
        Contract.Requires(videoCategory != null);

        categories.Add(videoCategory);
    }

    protected internal void RemoveCategory(VideoCategory videoCategory)
    {
        categories.Remove(videoCategory);
    }
}

public class VideoCategoryMap : ClassMap<VideoCategory>
{
    public VideoCategoryMap()
    {
        Table("VideoCategories");

        Id(cat => cat.Id)
            .GeneratedBy.Native();

        Map(cat => cat.Name)
            .Unique()
            .Not.Nullable();

        HasManyToMany<VideoFolder>(Reveal.Member<VideoCategory>("folders"))
            .Access.CamelCaseField()
            .AsSet()
            .Inverse()
            .Cascade.AllDeleteOrphan();
    }
}

public class VideoFolderMap : ClassMap<VideoFolder>
{
    public VideoFolderMap()
    {
        Table("VideoFolders");

        Id(folder => folder.Id)
            .GeneratedBy.Native();

        Map(folder => folder.Path)
            .Not.Nullable();

        HasManyToMany<VideoCategory>(Reveal.Member<VideoFolder>("categories"))
            .Access.CamelCaseField()
            .AsSet();
    }
}

我有这两个单元测试:

[Fact]
public void DeletingVideocategory_DeletesVideoFolders()
{
    object id;

    using (ISession session = SessionFactory.OpenSession())
    {
        var categ = new VideoCategory("Foo");
        var folder = new VideoFolder("D:\\Foo");
        categ.AddFolder(folder);

        id = session.Save(categ);

        session.Flush();
    }

    using (ISession session = SessionFactory.OpenSession())
    {
        var category = session.Get<VideoCategory>(id);
        category.ClearFolders();

        session.Delete(category);
        session.Flush();

        Assert.Equal(0, session.QueryOver<VideoFolder>().RowCount());
    }
}

[Fact]
public void DeletingVideocategory_DoesntDeleteVideoFoldersOwned_ByOtherCategories()
{
    object id;
    object id2;

    using (ISession session = SessionFactory.OpenSession())
    {
        var categ = new VideoCategory("Foo");
        var categ2 = new VideoCategory("Bar");
        var folder = new VideoFolder("D:\\Foo");

        categ.AddFolder(folder);
        categ2.AddFolder(folder);

        id = session.Save(categ);
        id2 = session.Save(categ2);

        session.Flush();
    }

    using (ISession session = SessionFactory.OpenSession())
    {
        var category = session.Get<VideoCategory>(id);
        category.ClearFolders();

        session.Delete(category);
        session.Flush();

        Assert.Equal(1, session.QueryOver<VideoFolder>().RowCount());
        Assert.Equal(1, session.Get<VideoCategory>(id2).Folders.Count());
    }
}

第一个成功,但不是第二个的第一个断言,其中视频文件夹被删除,而它仍然与剩余的视频类别相关联。

这是第二个测试的SQL输出:

INSERT INTO VideoCategories (Name) VALUES (@p0); select last_insert_rowid();@p0 = 'Foo' [Type: String (0)]
INSERT INTO VideoFolders (Path) VALUES (@p0); select last_insert_rowid();@p0 = 'D:\Foo' [Type: String (0)]
INSERT INTO VideoCategories (Name) VALUES (@p0); select last_insert_rowid();@p0 = 'Bar' [Type: String (0)]
INSERT INTO CategoriesToFolders (VideoFolder_id, VideoCategory_id) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 1 [Type: Int32 (0)]
INSERT INTO CategoriesToFolders (VideoFolder_id, VideoCategory_id) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 2 [Type: Int32 (0)]
SELECT videocateg0_.Id as Id10_0_, videocateg0_.Name as Name10_0_ FROM VideoCategories videocateg0_ WHERE videocateg0_.Id=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT folders0_.VideoCategory_id as VideoCat1_1_, folders0_.VideoFolder_id as VideoFol2_1_, videofolde1_.Id as Id13_0_, videofolde1_.Path as Path13_0_ FROM CategoriesToFolders folders0_ left outer join VideoFolders videofolde1_ on folders0_.VideoFolder_id=videofolde1_.Id WHERE folders0_.VideoCategory_id=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT categories0_.VideoFolder_id as VideoFol2_1_, categories0_.VideoCategory_id as VideoCat1_1_, videocateg1_.Id as Id10_0_, videocateg1_.Name as Name10_0_ FROM CategoriesToFolders categories0_ left outer join VideoCategories videocateg1_ on categories0_.VideoCategory_id=videocateg1_.Id WHERE categories0_.VideoFolder_id=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT videos0_.VideoFolder_id as VideoFol3_1_, videos0_.Id as Id1_, videos0_.Id as Id12_0_, videos0_.Path as Path12_0_ FROM VideoFiles videos0_ WHERE videos0_.VideoFolder_id=@p0;@p0 = 1 [Type: Int32 (0)]
DELETE FROM CategoriesToFolders WHERE VideoFolder_id = @p0;@p0 = 1 [Type: Int32 (0)]
DELETE FROM VideoFolders WHERE Id = @p0;@p0 = 1 [Type: Int32 (0)]
DELETE FROM VideoCategories WHERE Id = @p0;@p0 = 1 [Type: Int32 (0)]
SELECT count(*) as y0_ FROM VideoFolders this_

如果我更改了映射,并在VideoCategoryMap中修改了Cascade.AllDeleteOrphan,则第二次测试成功,而第一次测试失败,因为孤立的视频文件夹未被删除。

如何让这两项测试成功?

提前致谢

迈克

1 个答案:

答案 0 :(得分:2)

NHibernate中的“删除孤儿”并不意味着“删除没有任何其他父母的实体”。这意味着“删除关系时,也删除引用的实体”。

NHibernate不处理你需要OOTB的情况。可能的解决方案包括域级逻辑,存储库级逻辑,事件监听器和触发器。