Ответ 1
Пока коллекция не изменяется, NH все еще может думать, что она есть. Что-то вроде этого может быть вызвано призрачным обновлением. Из поваренной книги NHibernate 3.0 Джейсона Дентлера (стр. 184): "В рамках автоматической грязной проверки NHibernate сравнивает исходное состояние объекта с его текущее состояние. В противном случае неизменный объект может быть обновлен без необходимости, поскольку преобразование типа привело к сбою этого сравнения ".
Обновление 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 возвращает коллекцию в оболочке readonly, поэтому код клиента не может добавлять или удалять элементы в/из коллекции.
Ошибка появится, даже если имя тега не изменилось:
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 должна содержать только идентификатор тега и идентификатора проекта, тогда было бы проще использовать двунаправленное сопоставление 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();