Проблемы с приемом интерфейса в коллекцию (ковариантность) с nHibernate - PullRequest
0 голосов
/ 31 января 2011

Я использую Fluent nHibernate для своего уровня персистентности в приложении ASP.NET MVC, и я столкнулся с небольшим затруднением.

У меня есть ситуация, когда мне нужно использовать абстракцию для хранения объектов в коллекции, в этой ситуации интерфейс является наиболее логичным выбором, если вы смотрите на чистую перспективу C #.

По существу, объект (Item) может иметь Requirements. Требование может быть много вещей. В нативной ситуации с C # я бы просто выполнил это с помощью следующего кода.

interface IRequirement
{
 // methods and properties neccessary for evaluation
}

class Item
{
 virtual int Id { get; set; }
 virtual IList<IRequirement> Requirements { get; set; }
}

Грубый пример. Это прекрасно работает в нативном C # - однако, поскольку объекты должны храниться в базе данных, это становится немного сложнее, чем это. Каждый объект, который реализует IRequirement, может быть совершенно другим видом объекта. Поскольку nHibernate (или любой другой ORM, который я обнаружил) не может по-настоящему понять, как сериализовать интерфейс , я не могу себе представить, как подойти к этому сценарию. Я имею в виду, я понимаю проблему.

Это не имеет смысла для базы данных / orm. Я тоже полностью понимаю, почему.

class SomeKindOfObject
{
 virtual int Id { get; set; }
 // ... some other methods relative to this base type
}
class OneRequirement : SomeKindOfObject, IRequirement
{
 virtual string Name { get; set; }
 // some more methods and properties
}
class AnotherKindOfObject
{
 virtual int Id { get; set; }
 // ... more methods and properties, different from SomeKindOfObject
}
class AnotherRequirement : AnotherKindOfObject, IRequirement
{
 // yet more methods and properties relative to AnotherKindOfObject's intentive hierarchy
}

class OneRequirementMap : ClassMap<OneRequirement>
{
 // etc
 Table("OneRequirement");
}
class AnotherRequirementMap : ClassMap<AnotherRequirement>
{
 //
 Table("OtherRequirements");
}
class ItemMap : ClassMap<Item>
{
 // ... Now we have a problem.
 Map( x => x.Requirements ) // does not compute... 
 // additional mapping
}

Итак, у кого-нибудь есть идеи? Кажется, я тоже не могу использовать дженерики, поэтому кажется, что создание базового типа Requirement<T> не подходит. Я имею в виду, что код работает и работает, но ORM не может его понять. Я понимаю, что я спрашиваю здесь, возможно, невозможно, но все, что я могу сделать, это спросить.

Я также хотел бы добавить, что у меня нет большого опыта работы с nHibernate, только Fluent nHibernate, но я осознал, что оба сообщества очень хороши, и поэтому я отмечаю это как оба. Но мое отображение в настоящее время на 100% "свободно".

Редактировать

Я действительно обнаружил Программирование интерфейсов при отображении с помощью Fluent NHibernate , которое немного затрагивает этот вопрос, но я все еще не уверен, что оно применимо к моему сценарию. Любая помощь приветствуется.

ОБНОВЛЕНИЕ (02/02/2011)

Я добавляю это обновление в ответ на некоторые опубликованные ответы, так как мои результаты ... немного неловкие.

Принимая советы и проводя дополнительные исследования, я разработал базовый интерфейс.

interface IRequirement
{
 // ... Same as it always was
}

и теперь я устанавливаю свое сопоставление классов ..

class IRequirementMap : ClassMap<IRequirement>
{
 public IRequirementMap()
 {
    Id( x => x.Id );
    UseUnionSubclassForInheritanceMapping();
    Table("Requirements");
 }
}

А потом я сопоставляю то, что реализует это. Вот где он становится очень причудливым.

class ObjectThatImplementsRequirementMap : ClassMap<ObjectThatImplementsRequirement>
{
 ObjectThatImplementsRequirementMap()
 {
  Id(x => x.Id); // Yes, I am base-class mapping it.
  // other properties
  Table("ObjectImplementingRequirement");
 }
}

class AnotherObjectThatHasRequirementMap : ClassMap<AnotherObjectThatHasRequirement>
    {
     AnotherObjectThatHasRequirementMap ()
     {
      Id(x => x.Id); // Yes, I am base-class mapping it.
      // other properties
      Table("AnotheObjectImplementingRequirement");
     }
}

Это не , что люди предложили, но это был мой первый подход. Хотя я сделал это, потому что получил очень странные результаты. Результаты, которые действительно имеют для меня нет смысла.

Это на самом деле работает ... Вроде Выполнение следующего кода дает неожиданные результаты.

// setup ISession
// setup Transaction
var requirements = new <IRequirement>
{
 new ObjectThatImplementsRequirement
 {
  // properties, etc.. 
 },
 new AnotherObjectThatHasRequirement
 {
  // other properties.
 }
}
// add to session.
// commit transaction.
// close writing block.

// setup new session
// setup new transaction
var requireables = session.Query<IRequirable>();
foreach(var requireable in requireables)
   Console.WriteLine( requireable.Id );

Теперь все становится странно. Я получаю результаты ...

1 1

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

class SomethingThatHasRequireables
{ 
 // ...
 public virtual IList<IRequirement> Requirements { get; set; }
}

Попытка добавить в эту коллекцию не удалась (как я и ожидал). Вот почему я в замешательстве.

  • Если я могу добавить к универсальному IList<IRequirement> в моем сеансе, почему не в объекте?
  • Как nHibernate понимает разницу между двумя сущностями с одинаковым Id, если они оба отображаются как один и тот же тип объекта в одном сценарии, а не в другом?

Может кто-нибудь объяснить мне, что здесь происходит в мире?

Предлагаемый подход заключается в использовании SubclassMap<T>, однако проблема заключается в количестве идентификаторов и размере таблицы. Я обеспокоен масштабируемостью и производительностью, если несколько объектов (до 8) ссылаются на идентификаторы из одной таблицы. Может кто-нибудь дать мне некоторое представление об этом конкретно?

1 Ответ

3 голосов
/ 31 января 2011

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

Насколько я понимаю, вы выбрали одну из стратегий "таблица на конкретный класс". Вам может понадобиться <one-to-many> с inverse=true или <many-to-any>, чтобы отобразить его.

Если вы хотите избежать этого, вам нужно отобразить IRequirement как базовый класс в таблицу, тогда возможно иметь внешние ключи для этой таблицы. При этом вы превращаете его в отображение «таблица на иерархию классов» или «таблица на подкласс». Это, конечно, невозможно, если другой базовый класс уже сопоставлен. Например. SomeKindOfObject.


Редактировать: дополнительная информация о <one-to-many> с inverse=true и <many-to-any>.

Когда вы используете <one-to-many>, внешний ключ фактически находится в таблицах требований, указывающих на элемент. Пока это хорошо работает, NH объединяет все таблицы требований, чтобы найти все элементы в списке. Требуется обратное, потому что оно заставляет вас ссылаться из требования на Элемент, который используется NH для создания внешнего ключа.

<many-to-any> еще более гибок. Сохраняет список в дополнительной таблице ссылок. Эта таблица имеет три столбца:

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

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

То, что это отношение «многие ко многим», не должно вас беспокоить, это означает, что оно сохраняет отношение в дополнительной таблице, которая технически способна связать требование с более чем одним элементом.


Редактировать 2 : странные результаты:

Вы сопоставили 3 таблицы: IRequirement, ObjectThatImplementsRequirement, AnotherObjectThatHasRequirement. Все они полностью независимы. Вы по-прежнему находитесь на «столе в каждом конкретном классе с неявным полиморфизмом». Вы только что добавили еще одну таблицу, содержащую IRequirements, что также может привести к некоторой неопределенности, когда NH попытается найти правильную таблицу.

Конечно, вы получите 1, 1 в результате. У них есть независимые таблицы и, следовательно, независимые идентификаторы, которые начинаются с 1.

Работающая часть: NHibernate может найти все объекты, реализующие интерфейс во всей базе данных, когда вы запрашиваете его. Попробуйте session.CreateQuery("from object"), и вы получите всю базу данных.

Часть, которая не работает: С другой стороны, вы не можете получить объект только по id и интерфейсу или object. Так что session.Get<object>(1) не работает, потому что есть много объектов с идентификатором 1. Та же проблема со списком. И здесь есть еще некоторые проблемы, например, тот факт, что при неявном полиморфизме не указан внешний ключ, указывающий на каждый тип, реализующий IRequirement, на Item.

Любые типы: Здесь происходит сопоставление любых типов. Любые типы хранятся с дополнительной информацией о типах в базе данных, и это делается с помощью сопоставления <many-to-any>, в котором хранятся внешний ключ и тип. информация в дополнительной таблице. С помощью этой дополнительной информации о типе NH может найти таблицу, в которой хранится запись.

Странные результаты: Учтите, что NH нужно найти оба пути, от объекта до одной таблицы и от записи до одного класса. Поэтому будьте осторожны при сопоставлении как интерфейса, так и конкретных классов независимо. Может случиться, что NH использует одну или другую таблицу в зависимости от того, как вы получаете доступ к данным. Это могло быть причиной или ваши странные результаты .

Другое решение: Используя любую из других стратегий отображения наследования, вы определяете одну таблицу, в которой NH может начать чтение и поиск типа.

Область идентификатора: Если вы используете Int32 в качестве идентификатора, вы можете создавать 1 запись каждую секунду в течение 68 лет, пока у вас не закончатся идентификаторы.Если этого недостаточно, просто переключитесь на long, и вы получите ... вероятно, больше, чем база данных сможет хранить в ближайшие несколько тысяч лет ...

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