I have a many-to-many relationship between photos and tags: A photo can have multiple tags and several photos can share the same tags.
我在照片和标签之间存在多对多的关系:照片可以有多个标签,而且几张照片可以共享相同的标签。
I have a loop that scans the photos in a directory and then adds them to NHibernate. Some tags are added to the photos during that process, e.g. a 2009-tag when the photo is taken in 2009.
我有一个循环扫描目录中的照片,然后将它们添加到NHibernate。在此过程中,有些标签会添加到照片中,例如这张照片是在2009年拍摄的2009年标签。
The Tag class implements Equals and GetHashCode and uses the Name property as the only signature property. Both Photo and Tag have surrogate keys and are versioned.
Tag类实现Equals和GetHashCode,并使用Name属性作为唯一的签名属性。 Photo和Tag都有代理键和版本。
I have some code similar to the following:
我有一些类似于以下代码:
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);
}
}
My problem is when the tag does not exist, e.g. the first photo of a new year. The AddDefaultTags method checks to see if the tag exists in the database and then creates it and adds it to NHibernate. That works great when adding a single photo but when importing multiple photos in the new year and within the same unit of work it fails since it still doesn’t exist in the database and is added again. When completing the unit of work it fails since it tries to add two entries in the Tags table with the same name...
我的问题是当标签不存在时,例如新年的第一张照片。 AddDefaultTags方法检查标记是否存在于数据库中,然后创建它并将其添加到NHibernate。这在添加单张照片时非常有用,但在新的一年中导入多张照片并在同一工作单元内导致失败,因为它仍然不存在于数据库中并再次添加。完成工作单元时,它会失败,因为它试图在Tags表中添加两个具有相同名称的条目...
My question is how to make sure that NHibernate only tries to create a single tag in the database in the above situation. Do I need to maintain a list of newly added tags myself or can I set up the mapping in such a way that it works?
我的问题是如何确保NHibernate在上述情况下只尝试在数据库中创建单个标记。我是否需要自己维护一个新添加的标签列表,还是可以以这样的方式设置映射?
3 个解决方案
#1
You need to run _session.Flush()
if your criteria should not return stale data. Or you should be able to do it correctly by setting the _session.FlushMode
to Auto.
如果您的条件不应返回过时数据,则需要运行_session.Flush()。或者您应该能够通过将_session.FlushMode设置为Auto来正确执行此操作。
With FlushMode.Auto, the session will automatically be flushed before the criteria is executed.
使用FlushMode.Auto,会话将在执行条件之前自动刷新。
EDIT: And important! When reading the code you've shown, it does not look like you're using a transaction for your unit of work. I would recommend wrapping your unit of work in a transaction - that is required for FlushMode.Auto to work if you're using NH2.0+ !
编辑:非常重要!在阅读您显示的代码时,您的工作单元看起来并不像是在使用交易。我建议在事务中包装你的工作单元 - 如果你使用的是NH2.0 +,那么FlushMode.Auto需要工作!
Read further here: NHibernate ISession Flush: Where and when to use it, and why?
在这里进一步阅读:NHibernate ISession Flush:何时何地使用它,为什么?
#2
If you want the new tag to be in the database when you check it each time you need to commit the transaction after you save to put it there.
如果您想在每次需要提交事务后检查新标记时将新标记放在数据库中,并将其保存到该数据库中。
Another approach would be to read the tags into a collection before you process the photos. Then like you said you would search local and add new tags as needed. When you are done with the folder you can commit the session.
另一种方法是在处理照片之前将标签读入集合。然后就像你说你会搜索本地并根据需要添加新标签。完成文件夹后,您可以提交会话。
You should post your mappings as i may not have interpreted your question correctly.
您应该发布您的映射,因为我可能没有正确解释您的问题。
#3
This is that typical "lock something that is not there" problem. I faced it already several times and still do not have a simple solution for it.
这就是典型的“锁定不存在的东西”的问题。我已经多次面对它,但仍然没有一个简单的解决方案。
This are the options I know until now:
这是我直到现在才知道的选项:
- Optimistic: have a unique constraint on the name and let one of the sessions throw on commit. Then you try it again. You have to make sure that you don't end in a infinite loop when another error occurs.
- Pessimistic: When you add a new Tag, you lock the whole Tag table using TSQL.
- .NET Locking: you synchronize the threads using .NET locks. This only works if you parallel transactions are in the same process.
- Create Tags using a own session (see bellow)
乐观:对名称有一个唯一约束,让其中一个会话抛出提交。然后你再试一次。当发生另一个错误时,您必须确保不会以无限循环结束。
悲观:当您添加新标记时,使用TSQL锁定整个标记表。
.NET锁定:使用.NET锁同步线程。这仅适用于并行事务处于同一进程中的情况。
使用自己的会话创建标签(见下文)
Example:
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);
}
}
This looks simple, but has some problems. You get always a tag, that is either existing or created (and committed immediately). But the tag you get is from another session, so it is detached for your main session. You need to attach it to your session using cascades (which you probably don't want to) or update.
这看起来很简单,但有一些问题。您始终会获得一个标记,即现有标记或已创建标记(并立即提交)。但是你得到的标签来自另一个会话,所以它是为你的主会话分离的。您需要使用级联(您可能不希望)或更新将其附加到会话。
Creating tags is not coupled to your main transaction anymore, this was the goal but also means that rolling back your transaction leaves all created tags in the database. In other words: creating tags is not part of your transaction anymore.
创建标记不再与您的主事务耦合,这是目标,但也意味着回滚您的事务会在数据库中保留所有创建的标记。换句话说:创建标签不再是您交易的一部分。
#1
You need to run _session.Flush()
if your criteria should not return stale data. Or you should be able to do it correctly by setting the _session.FlushMode
to Auto.
如果您的条件不应返回过时数据,则需要运行_session.Flush()。或者您应该能够通过将_session.FlushMode设置为Auto来正确执行此操作。
With FlushMode.Auto, the session will automatically be flushed before the criteria is executed.
使用FlushMode.Auto,会话将在执行条件之前自动刷新。
EDIT: And important! When reading the code you've shown, it does not look like you're using a transaction for your unit of work. I would recommend wrapping your unit of work in a transaction - that is required for FlushMode.Auto to work if you're using NH2.0+ !
编辑:非常重要!在阅读您显示的代码时,您的工作单元看起来并不像是在使用交易。我建议在事务中包装你的工作单元 - 如果你使用的是NH2.0 +,那么FlushMode.Auto需要工作!
Read further here: NHibernate ISession Flush: Where and when to use it, and why?
在这里进一步阅读:NHibernate ISession Flush:何时何地使用它,为什么?
#2
If you want the new tag to be in the database when you check it each time you need to commit the transaction after you save to put it there.
如果您想在每次需要提交事务后检查新标记时将新标记放在数据库中,并将其保存到该数据库中。
Another approach would be to read the tags into a collection before you process the photos. Then like you said you would search local and add new tags as needed. When you are done with the folder you can commit the session.
另一种方法是在处理照片之前将标签读入集合。然后就像你说你会搜索本地并根据需要添加新标签。完成文件夹后,您可以提交会话。
You should post your mappings as i may not have interpreted your question correctly.
您应该发布您的映射,因为我可能没有正确解释您的问题。
#3
This is that typical "lock something that is not there" problem. I faced it already several times and still do not have a simple solution for it.
这就是典型的“锁定不存在的东西”的问题。我已经多次面对它,但仍然没有一个简单的解决方案。
This are the options I know until now:
这是我直到现在才知道的选项:
- Optimistic: have a unique constraint on the name and let one of the sessions throw on commit. Then you try it again. You have to make sure that you don't end in a infinite loop when another error occurs.
- Pessimistic: When you add a new Tag, you lock the whole Tag table using TSQL.
- .NET Locking: you synchronize the threads using .NET locks. This only works if you parallel transactions are in the same process.
- Create Tags using a own session (see bellow)
乐观:对名称有一个唯一约束,让其中一个会话抛出提交。然后你再试一次。当发生另一个错误时,您必须确保不会以无限循环结束。
悲观:当您添加新标记时,使用TSQL锁定整个标记表。
.NET锁定:使用.NET锁同步线程。这仅适用于并行事务处于同一进程中的情况。
使用自己的会话创建标签(见下文)
Example:
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);
}
}
This looks simple, but has some problems. You get always a tag, that is either existing or created (and committed immediately). But the tag you get is from another session, so it is detached for your main session. You need to attach it to your session using cascades (which you probably don't want to) or update.
这看起来很简单,但有一些问题。您始终会获得一个标记,即现有标记或已创建标记(并立即提交)。但是你得到的标签来自另一个会话,所以它是为你的主会话分离的。您需要使用级联(您可能不希望)或更新将其附加到会话。
Creating tags is not coupled to your main transaction anymore, this was the goal but also means that rolling back your transaction leaves all created tags in the database. In other words: creating tags is not part of your transaction anymore.
创建标记不再与您的主事务耦合,这是目标,但也意味着回滚您的事务会在数据库中保留所有创建的标记。换句话说:创建标签不再是您交易的一部分。