【问题标题】:Many-To-Many and cascades: Problem with entities deletion多对多和级联:实体删除问题
【发布时间】:2011-05-21 18:02:19
【问题描述】:

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

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();
    }
}

我有这 2 个单元测试:

[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,第二次测试成功,而第一次失败,因为没有删除孤立的视频文件夹。

我怎样才能使两个测试都成功?

提前致谢

迈克

【问题讨论】:

  • 第二个单元测试到底有什么问题?这两个断言中的哪一个失败了?
  • 在第二个测试中,这是第一个失败的断言。我正在更新问题

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


【解决方案1】:

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

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

【讨论】:

  • 那么正确的做法是映射实体,只删除多对多表中的数据,手动删除“没有父级的实体”?
  • 是的。如前所述,您可以通过不同的方式实现自动化;触发器不是一个坏主意。
  • 非常感谢,我去看看
  • 要明确一点:触发器在数据库中,对吧?这不是 NHibernate 功能吗?
  • 触发器在数据库中,是的(您可以从 NH 管理它的创建)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多