Nhibernate "cascade =" all-delete-orphan "error

В моей базе данных есть 3 таблицы:

  • Проекты (id, name)
  • Теги (id, name)
  • 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, связанной ни с чем другим. Теперь мне нужно создать отчет, чтобы показать тег, и как часто этот тег используется, поэтому мне нужно присоединиться к тегу в ProjectTag. Я попытался добавить эту строку в таблицу тегов:

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

но когда я иду, чтобы обновить имя объекта тега и зафиксировать, я получаю эту ошибку:

Коллекция с каскадом = "all-delete-orphan" больше не ссылалась на экземпляр объекта-владельца

может кто-нибудь увидеть что-то не так с тем, что я добавил, что вызвало бы это исключение 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; }
}

Ответ 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();

Ответ 2

Где-то в вашем коде вы должны разыменовать исходную коллекцию в своем домене проекта. Я подозреваю, что ваш код выглядит следующим образом:

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);