题 nhibernate“cascade =”all-delete-orphan“错误


我的数据库中有3个表:

  1. 项目  (id,name)
  2. 标签 (id,name)
  3. ProjectsTagss (id,projectId,tagid)

如您所见,ProjectsTags表是一个桥表

这是我流利的nhibernate映射

ProjectMap.cs:

 Map(x => x.Name).Not.Nullable();
 HasMany(x => x.ProjectsTags).AsBag().Inverse()
    .Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);

ProjectsTagsMap.cs:

 References(x => x.Project).Not.Nullable();
 References(x => x.Tag).Not.Nullable();

TagMap.cs:

  Map(x => x.Name).Not.Nullable();

正如您所看到的,我历史上没有将Tag表链接到其他任何内容。我现在需要生成一个报告来显示Tag以及该标签的使用频率,因此我需要从Tag加入ProjectsTag。我尝试将此行添加到tagsmap中:

 HasMany(x => x.ProjectsTags).AsBag().Inverse()
    .Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);

但是当我去更新标签对象上的名称并提交时,我收到此错误:

拥有实体实例不再引用具有cascade =“all-delete-orphan”的集合

任何人都可以看到我添加的内容有什么问题,当我只是更新Tag表时会导致这个nhibernate异常。我的目标再次是能够做到这样的事情:

 Tag.ProjectTags.Count();

以下是一些额外的代码:

我的标签类:

 public class Tag
{
    public virtual IList<ProjectTag> ProjectTags { get; set; }
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
}

11
2018-04-27 11:17


起源


向我们展示更新标签的代码。请写一个表达问题的有意义的标题。 - Stefan Steinegger
@Stefan Steinegger - 更新标题以反映错误 - leora
从列表中删除项目并在其他列表中重复使用时会发生错误。所以请向我们展示您为更新数据而编写的代码。 - Stefan Steinegger
我同意斯特凡的观点。您应该发布用于修改对象的代码以及如何持久保存更改。除此之外,查看Tag类的实现方式可能会有所帮助。专门添加/删除/编辑ProjectTags集合中的任何项目。 - Cole W
@Cole W - 我从未明确地从Tag对象中删除ProjecTag对象。 (也许这就是问题)。我只是从数据库中获取标记对象,更新Name属性并执行Repository.Save()和Repository.Committ() - leora


答案:


虽然没有修改集合,但NH仍然可以认为它是。这样的事情可能是由鬼更新引起的。来自NHibernate 3.0 Cookbook,Jason Dentler(第184页):“作为自动脏检查的一部分,NHibernate将实体的原始状态与 它目前的状态。否则未更改的实体可能会被不必要地更新,因为a 类型转换导致此比较失败“。

集合的Ghost更新可能是由如下代码引起的:

public class Tag
{
    private IList<ProjectTag> projectsTags;

    public virtual IEnumerable<ProjectTag> ProjectsTags
    {
        get
        {
            return new ReadOnlyCollection<ProjectTag>(projectsTags);
        }

        set
        {
            projectsTags = (IList<ProjectTag>)value;
        }
    }
}

ProjectsTags属性以只读包装形式返回集合,因此客户端代码无法向集合添加元素或从集合中删除元素。

即使标签名称未更改,也会出现错误:

private void GhostTagUpdate(int id)
{
    using (var session = OpenSession())
    {
        using (var transaction = session.BeginTransaction())
        {
            var tag = session.Get<Tag>(id);

            transaction.Commit();
        }
    }
}

ProjectsTags集合应与CamelCaseField访问策略一起映射,以避免ghost更新:

HasMany(x => x.ProjectsTags)
    .Access.CamelCaseField()
    .AsBag().Inverse().Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);

无论如何...

你的关联似乎是恶魔般的复杂。如果ProjectsTags表应该只包含tag的id和项目的id,那么使用FNH多对多双向映射会更简单:

public class Tag2Map : ClassMap<Tag2>
{
    public Tag2Map()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        HasManyToMany(x => x.Projects)
            .AsBag()
            .Cascade.None()
            .Table("ProjectsTags")
            .ParentKeyColumn("TagId")
            .ChildKeyColumn("ProjectId");
    }
}

public class Project2Map : ClassMap<Project2>
{
    public Project2Map()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        HasManyToMany(x => x.Tags)
            .AsBag()
            .Cascade.None()
            .Inverse()
            .Table("ProjectsTags")
            .ParentKeyColumn("ProjectId")
            .ChildKeyColumn("TagId");
    }
}

现在模型中不需要ProjectTag实体。可以通过两种方式检索给定标签使用次数的计数:

直接方式: tag.Projects.Count()  - 但它从数据库中检索所有项目。

查询方式:

var tag = session.Get<Tag2>(tagId);
var count = session.Query<Project2>().Where(x => x.Tags.Contains(tag)).Count();

7
2018-05-04 21:22





在代码的某处,您应该取消引用Project域上的原始集合。我怀疑你的代码是这样的:

var project = Session.Get<Project>();
project.ProjectsTags = new List<ProjectsTags> { someProjectsTagsInstance };
Session.Save(project);

如果是这种情况,您应该这样做:

var project = Session.Get<Project>();
project.ProjectsTags.Clear();
project.ProjectsTags.Add(someProjectsTagsInstance);
Session.Save(project);

16
2018-05-04 12:56



是的,结果证明这是我们的问题。有什么可能导致它的任何见解? - VoodooChild
导致它的原因是NHibernate“接管”了 ProjectTags 列出并跟踪其中的所有内容。通过替换拥有实体上的整个列表,您可以断开此跟踪列表,但仍会跟踪它并在保存时NHibernate不知道如何处理内部的所有对象。 - codekaizen