12

我的数据库中有 3 个表:

  1. 项目 (ID,名称)
  2. 标签(ID,名称)
  3. ProjectsTagss (id, projectId, tagid)

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

这是我流利的 ​​nhibernate 映射

项目地图.cs:

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

项目TagsMap.cs:

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

标签映射.cs:

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

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

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

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

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

当我简单地更新标签表时,任何人都可以看到我添加的内容有什么问题会导致这个休眠异常。同样,我的目标是能够执行以下操作:

 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; }
}
4

2 回答 2

19

在您的代码中的某处,您应该取消引用 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);
于 2011-05-04T12:56:27.923 回答
7

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

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

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 访问策略进行映射,以避免幽灵更新:

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

反正...

你的联想似乎非常复杂。如果 ProjectsTags 表应该只包含标签的 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();
于 2011-05-04T21:22:26.123 回答