代码之家  ›  专栏  ›  技术社区  ›  HakonB

哪种算法与多对多关系式结合使用

  •  2
  • HakonB  · 技术社区  · 16 年前

    我在照片和标签之间有一种多对多的关系:一张照片可以有多个标签,多张照片可以共享同一个标签。

    我有一个循环扫描目录中的照片,然后将它们添加到nhibernate。在这一过程中,一些标签被添加到照片中,例如2009年拍摄照片时的一个2009标签。

    标记类实现equals和gethashcode,并使用name属性作为唯一的签名属性。照片和标签都有代理键,并且都有版本控制。

    我有一些类似以下的代码:

    public void Import() {
        ...
        foreach (var fileName in fileNames) {
            var photo = new Photo { FileName = fileName };
            AddDefaultTags(_session, photo, fileName);
            _session.Save(photo);
        }
        ...
    }
    
    private void AddDefaultTags(…) {
        ...
        var tag =_session.CreateCriteria(typeof(Tag))
                        .Add(Restriction.Eq(“Name”, year.ToString()))
                        .UniqueResult<Tag>();
    
        if (tag != null) {
            photo.AddTag(tag);
        } else {
            var tag = new Tag { Name = year.ToString()) };
            _session.Save(tag);
            photo.AddTag(tag);
        }
    }
    

    我的问题是当标签不存在时,例如新年的第一张照片。addDefaultTags方法检查数据库中是否存在标记,然后创建它并将其添加到nhibernate。这在添加一张照片时效果很好,但在新年导入多张照片时,如果在同一工作单元中导入多张照片,则会失败,因为它仍然不存在于数据库中,并且会再次添加。完成工作单元时失败,因为它试图在标记表中添加两个同名的条目…

    我的问题是如何确保nhibernate在上述情况下只尝试在数据库中创建单个标记。我需要自己维护一个新添加的标签列表吗,或者我可以以这样一种方式设置映射吗?

    3 回复  |  直到 14 年前
        1
  •  2
  •   Community CDub    8 年前

    你需要跑步 _session.Flush() 如果您的条件不应返回过时的数据。 或者您应该能够通过设置 _session.FlushMode 自动。

    使用flushMode.auto,会话将在执行条件之前自动刷新。

    编辑:很重要!在读取所显示的代码时,它看起来不像是在为工作单元使用事务。我建议在事务中包装您的工作单元-这是FlushMode所必需的。如果您使用NH2.0+,则自动工作!

    请在此处进一步阅读: NHibernate ISession Flush: Where and when to use it, and why?

        2
  •  0
  •   Maggie    16 年前

    如果希望每次检查时新标记都在数据库中,则需要在保存后提交事务以将其放在数据库中。

    另一种方法是在处理照片之前将标签读取到集合中。 然后就像你说的那样,你会在本地搜索并根据需要添加新的标签。完成文件夹后,可以提交会话。

    你应该张贴你的映射,因为我可能没有正确地解释你的问题。

        3
  •  0
  •   Stefan Steinegger    16 年前

    这就是典型的“锁定不存在的东西”问题。我已经面对过好几次了,仍然没有一个简单的解决办法。

    这是迄今为止我知道的选项:

    • 乐观:对名称有一个唯一的约束,并让其中一个会话提交。然后你再试一次。当发生另一个错误时,必须确保不会以无限循环结束。
    • 悲观:当您添加一个新标记时,您使用TSQL锁定整个标记表。
    • .NET锁定:使用.NET锁定同步线程。只有当并行事务处于同一进程中时,这才有效。
    • 使用自己的会话创建标记(见下文)

    例子:

    public static Tag CreateTag(string name)
    {
      try
      {
        using (ISession session = factors.CreateSession())
        {
          session.BeginTransaction();
          Tag existingTag = session.CreateCriteria(typeof(Tag)) /* .... */
          if (existingtag != null) return existingTag;
          {
            session.Save(new Tag(name));
          }
          session.Transaction.Commit();
        }
      }
      // catch the unique constraint exception you get
      catch (WhatEverException ex)
      {
        // try again
        return CreateTag(name);
      }
    }
    

    这看起来很简单,但有一些问题。您总是会得到一个标签,它要么是现有的,要么是已创建的(并立即提交)。但是您得到的标签来自另一个会话,因此它是为您的主会话分离的。您需要使用级联(您可能不想)或更新将其附加到会话。

    创建标记不再与您的主事务耦合,这是目标,但也意味着回滚您的事务会在数据库中留下所有创建的标记。换句话说:创建标记不再是事务的一部分。