Невозможно объединить сущность с составным идентификатором, если эта сущность была предварительно извлечена с помощью Get - PullRequest
4 голосов
/ 27 июля 2011

Проект, над которым я работаю, требует, чтобы данные в нашей системе были синхронизированы с данными другой (другая система довольно популярна, поэтому синхронизация так важна). Однако у меня возникает странная проблема, когда я пытаюсь обновить существующую сущность с составным идентификатором.

Проблема в том, что всякий раз, когда обновляемый объект извлекается (используя Get ) до вызова Merge , он не работает (изменения не сохраняются в БД, но исключение не выбрасывается). Когда я удаляю вызов Get , обновление сущности работает. Знание того, существует ли сущность, необходимо, потому что, если она создается, необходимо создать часть составного идентификатора.

bool exists = ScanForInstance(instance);
using (var session = SessionFactoryFactory.GetSessionFactory<T>().OpenSession())
{
    if (exists)
    {
        instance = (T)session.Merge(instance);
    }
    else
    {
        KeyGenerator.Assign<T>(instance);
        newId = session.Save(instance);
    }

    session.Flush();
}

Вызов Get выполняется в методе ScanForInstance :

private bool ScanForInstance<T>(T instance)
    where T : class
{
    var id = IdResolver.ResolveObject<T>(instance);
    using (var session = SessionFactoryFactory.GetSessionFactory<T>().OpenStatelessSession())
    {
        return session.Get<T>(id) != null;
    }
}

IdResolver используется для определения того, что следует использовать для идентификатора (значение одного ключа в отображении, в противном случае сам объект для сущностей с составными идентификаторами).

Как я уже сказал, если я уберу вызов Get , он будет работать нормально. Он отлично работает и для всех других операций (создание, чтение и удаление). Все операции, включая обновление, отлично работает для сущностей с одним ключом.


База данных является распространяющейся и существует определенное количество ограничений:

  • Нет, я не могу изменить какую-либо схему (я вижу это как частый ответ на проблемы с FNB).
  • Я не хочу просто удалять, а затем вставлять, поскольку есть некоторые столбцы, которые мы не синхронизируем с нашей системой, и я не хочу стирать их

ОБНОВЛЕНО: Я добавил простой пример, когда люди могут копировать / вставлять, чтобы проверить это странное поведение (если оно действительно универсально). Я надеюсь, что люди сделают это, чтобы хотя бы подтвердить мою проблему.

Тип для отображения, Отображение по флюенту:

public class ParentType
{
    public virtual long AssignedId { get; set; }

    public virtual long? GeneratedId { get; set; }

    public virtual string SomeField { get; set; }

    public override bool Equals(object obj)
    {
        return Equals(obj as ParentType);
    }

    private bool Equals(ParentType other)
    {
        if (ReferenceEquals(this, other)) return true;
        if (ReferenceEquals(null, other)) return false;

        return AssignedId == other.AssignedId &&
            GeneratedId == other.GeneratedId;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = GetType().GetHashCode();
            hash = (hash * 31) ^ AssignedId.GetHashCode();
            hash = (hash * 31) ^ GeneratedId.GetHashCode();

            return hash;
        }
    }
}

public class ParentMap : ClassMap<ParentType>
{
    public ParentMap()
    {
        Table("STANDARDTASKITEM");

        CompositeId()
            .KeyProperty(x => x.AssignedId, "STANDARDTASK")
            .KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

        Map(x => x.SomeField, "DESCRIPTION");

        Not.LazyLoad();
    }
}

Не обращайте внимания на тот факт, что он называется «ParentType». На самом деле у меня нет других сопоставлений с этим, и я на самом деле не использую тип в качестве родительского типа в этом примере. Это называется так, потому что я собираюсь открыть еще один вопрос, который связан с проблемами с составными идентификаторами и наследованием ( НЕ ИСПОЛЬЗУЙТЕ КОМПОЗИЦИОННЫЙ ИД!! -D ).

Для реального тестирования я только что создал консольный проект в VS с таким именем Program.cs :

static void Main(string[] args)
{
    var smFactory = Fluently.Configure()
        .Database(() => new OdbcPersistenceConfigurer()
            .Driver<OdbcDriver>()
            .Dialect<GenericDialect>()
            .Provider<DriverConnectionProvider>()
            .ConnectionString(BuildSMConnectionString())
            .ProxyFactoryFactory(typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory))
            .UseReflectionOptimizer()
            .UseOuterJoin())
            .Mappings
            (m => 
                m.FluentMappings.Add<ParentMap>()
            );

    var sessionFactory = smFactory.BuildSessionFactory();

    var updatedInstance = new ParentType
    {
        AssignedId = 1,
        GeneratedId = 13,
        SomeField = "UPDATED"
    };

    bool exists;

    using (var session = sessionFactory.OpenStatelessSession())
    {
        exists = session.Get<ParentType>(updatedInstance) != null;
    }

    using (var session = sessionFactory.OpenSession())
    {
        if (exists)
        {
            session.Merge(updatedInstance);

            session.Flush();
        }
    }
}

private static string BuildSMConnectionString()
{
    // Return your connection string here
}

class OdbcPersistenceConfigurer : PersistenceConfiguration<OdbcPersistenceConfigurer, OdbcConnectionStringBuilder>
{

}

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

1 Ответ

3 голосов
/ 29 июля 2011

Ну, я, по крайней мере, нашел решение моей проблемы, но не почему. Мое решение состояло в том, чтобы создать новый тип, который охватывал бы свойства, которые я использовал в качестве составного идентификатора:

public class CompositeIdType
{
    public virtual long AssignedId { get; set; }

    public virtual long GeneratedId { get; set; }

    public override bool Equals(object obj)
    {
        return Equals(obj as CompositeIdType);
    }

    private bool Equals(CompositeIdType other)
    {
        if (ReferenceEquals(this, other)) return true;
        if (ReferenceEquals(null, other)) return false;

        return AssignedId == other.AssignedId &&
            GeneratedId == other.GeneratedId;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = GetType().GetHashCode();

            hash = (hash * 31) ^ AssignedId.GetHashCode();
            hash = (hash * 31) ^ GeneratedId.GetHashCode();

            return hash;
        }
    }
}

Затем замените свойства в ParentType ссылкой на этот новый тип:

public class ParentType
{
    public virtual CompositeIdType Key { get; set; }

    public virtual string SomeField { get; set; }
}

С этими изменениями новое отображение будет:

public class ParentMap : ClassMap<ParentType>
{
    public ParentMap()
    {
        Table("STANDARDTASKITEM");

        CompositeId<CompositeIdType>(x => x.Key)
            .KeyProperty(x => x.AssignedId, "STANDARDTASK")
            .KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

        Map(x => x.SomeField, "DESCRIPTION");

        Not.LazyLoad();
    }
}

После всех этих изменений Слияние работает, даже когда Get вызывается до вызова Слияние . Моя лучшая ставка - неуниверсальная форма CompositeId не делает что-то правильно или что его отображение не работает с NH, когда вы вызываете Merge для объекта, который использует это (я хотел бы обратиться к источнику FNH, чтобы исправить это, если это так, но я уже потратил слишком много времени на выяснение, как обойти эту проблему).

Это все хорошо, но это потребовало бы от меня создания нового типа для каждой сущности, которую я отображаю, или, по крайней мере, нового типа для идентификатора с другим количеством ключей (т. Е. Тип с 2 ключами, введите 3 ключа и т. д.).

Чтобы избежать этого, я могу взломать его, чтобы вы добавили ссылку того же типа, который вы отображаете, и задали ссылку на this в конструкторе:

public class ParentType
{
    public ParentType()
    {
        Key = this;
    }

    public virtual ParentType Key { get; set; }

    public virtual long AssignedId { get; set; }

    public virtual long GeneratedId { get; set; }

    public virtual string SomeField { get; set; }

    public override bool Equals(object obj)
    {
        return Equals(obj as ParentType);
    }

    private bool Equals(ParentType other)
    {
        if (ReferenceEquals(this, other)) return true;
        if (ReferenceEquals(null, other)) return false;

        return AssignedId == other.AssignedId &&
            GeneratedId == other.GeneratedId;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = GetType().GetHashCode();

            hash = (hash * 31) ^ AssignedId.GetHashCode();
            hash = (hash * 31) ^ GeneratedId.GetHashCode();

            return hash;
        }
    }
}

Тогда отображение будет:

public class ParentMap : ClassMap<ParentType>
{
    public ParentMap()
    {
        Table("STANDARDTASKITEM");

        CompositeId<ParentType>(x => x.Key)
            .KeyProperty(x => x.AssignedId, "STANDARDTASK")
            .KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

        Map(x => x.SomeField, "DESCRIPTION");

        Not.LazyLoad();
    }
}

Я проверил это для обновления и вставки, используя Слияние с Получение , вызываемый до слияния, и удивительно ЭТО РАБОТАЕТ . Я все еще нахожусь в поиске того, какое исправление использовать (новый тип, включающий составной идентификатор или самоссылку), поскольку самоссылка кажется мне немного хакерской.

Если кто-нибудь узнает, ПОЧЕМУ изначально это не сработало, я все равно хотел бы знать ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...