NHibernate - Как сопоставить класс, который не имеет таблицы (для пользовательских запросов sql)

Обновление - измененная конфигурация для удобства чтения в SO

Привет,

Я изучаю NHibernate на день или два, но застрял в одной точке.

Мне нужно иметь возможность выполнять собственные хранимые процедуры и использовать NHibernate для их сопоставления с классами домена.

У меня это работает для сценария, в котором пользовательский запрос возвращается к объекту, который сопоставляется с таблицей базы данных, как показано многими примерами nhibernate (см. первый раздел ниже).

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

Когда я запускаю свои тесты, я получаю исключение вроде:

NHibernate.MappingException: Нет настаивать на: Proj.DataEntityTracker.Domain.Entities.CustomObject

Итак, я думаю, что отображение в разделе sql-query недостаточно для NHibernate для сопоставления возвращаемых значений с объектами.

Итак, мой вопрос: как настроить сопоставление, для которого нет никакой эквивалентной таблицы в базе данных, чтобы я мог сопоставить результаты хранимой процедуры с этим объектом?

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="Proj.DataEntityTracker.Domain"
                   namespace="Proj.DataEntityTracker.Domain.Entities">

  <class name="TrackedEntityProperty" table="TrackedEntityProperties">
    <id name="ID" type="Int32" unsaved-value="0">
      <generator class="native"></generator>
    </id>
    <property name="TrackedEntityID" />
    <property name="Name" />
    <property name="CreatedDate" />
    <property name="ChangedDate" />
    <property name="DataType" />
    <property name="CurrentValue" />
    <property name="RequestPropertyValueQuestion" />
    <property name="NullResponseIsAcceptable" />
    <property name="Duplication" />
    <property name="Frequency" />
    <property name="IsActive" />
    <property name="IsDeleted" />
    <property name="LastUpdateTaskGenerated" />
    <property name="LastUpdateTaskCompleted" />
    <property name="LastUpdateTaskCancelled" />
  </class>

  <sql-query name="usp_GetTrackedEntityPropertiesDueForUpdate" >
    <return alias="usp_GetTrackedEntityPropertiesDueForUpdate" class="TrackedEntityProperty">

      <return-property name="ID" column="ID" />
      <return-property name="TrackedEntityID" column="TrackedEntityID" />
      <return-property name="Name" column="Name" />
      <return-property name="CreatedDate" column="CreatedDate" />
      <return-property name="ChangedDate" column="ChangedDate" />
      <return-property name="DataType" column="DataType" />
      <return-property name="CurrentValue" column="CurrentValue" />
      <return-property name="RequestPropertyValueQuestion" column="RequestPropertyValueQuestion" />
      <return-property name="NullResponseIsAcceptable" column="NullResponseIsAcceptable" />
      <return-property name="Duplication" column="Duplication" />
      <return-property name="Frequency" column="Frequency" />
      <return-property name="IsActive" column="IsActive" />
      <return-property name="IsDeleted" column="IsDeleted" />
      <return-property name="LastUpdateTaskGenerated" column="LastUpdateTaskGenerated" />
      <return-property name="LastUpdateTaskCompleted" column="LastUpdateTaskCompleted" />
      <return-property name="LastUpdateTaskCancelled" column="LastUpdateTaskCancelled" />

    </return>

    exec usp_GetTrackedEntityPropertiesDueForUpdate :TrackedEntityID

  </sql-query>

  <sql-query name="usp_SomeCustomSproc">
    <return alias="usp_SomeCustomSproc" class="CustomObject">

      <return-property name="ID" column="ID" />
      <return-property name="Name" column="Name" />

    </return>

    exec usp_SomeCustomSproc :TrackedEntityID

  </sql-query>

</hibernate-mapping>

Ответ 1

То, что вы ищете, это прогнозы. Прежде всего, вам нужно изменить свой запрос

  <sql-query name="usp_SomeCustomSproc">
    <return-scalar column="Id" type="Int32"/>
    <return-scalar column="Name" type="String"/>

    exec usp_SomeCustomSproc :TrackedEntityID

  </sql-query>

Затем в коде, где вы его называете, вы указываете трансформатор результата. AliasToBeanTransformer примет псевдонимы столбцов и сопоставляет их свойствам объекта.

session.GetNamedQuery("usp_SomeCustomSproc")
       .SetInt32("TrackedEntityID", 15)
       .SetResultTransformer(Transformers.AliasToBean<CustomObject>())
       .List<CustomObject>()

Ответ 2

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

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

http://forums.asp.net/t/1407518.aspx/1

Итак, я создал класс домена для хранения результата хранимой процедуры.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyProj.DataEntityTracker.Domain.Entities
{
    public class DemoCustomSprocObj
    {
        public virtual Guid Guid { get; set; }
        public virtual int ID { get; set; }
        public virtual string Name { get; set; }
    }
}

Этот класс не должен иметь соответствующую таблицу на SQL-сервере, хотя я обнаружил, что определение класса необходимо создать (без атрибута таблицы), чтобы избежать "NHibernate.MappingException: no persister for:" type ошибки.

Обратите внимание, что класс домена, созданный для хранения результата хранимой процедуры, требует поля идентификатора, и это должно быть возвращено из базы данных. В этом случае я вернул NEWID() из SQL Server и настроил класс сопоставления для использования генератора GUID для поля ID.

CREATE PROCEDURE usp_DemoCustomSproc
  @TrackedEntityID INT
AS
SET NOCOUNT ON
BEGIN
    SELECT NEWID() AS [Guid],
           ID ,
           Name 
    FROM TrackedEntityProperties AS tep
    WHERE TrackedEntityID = @TrackedEntityID
END

И класс отображения:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="MyProj.DataEntityTracker.Domain"
                   namespace="MyProj.DataEntityTracker.Domain.Entities">

  <!-- This mapping does not use a table, rather it exists to
  allow the mapping of a stored procedure result back to a domain object. 
  Note the use of the GUID. An ID must be present and returned by the stored 
  procedure result, otherwise the object will not work with NHibernate.
  Note also the absence of a table in the class tag. No table exists in
  the database, but the mapping must exist to avoid "No persiter" errors.

  Arguments are passed with the :Arg syntax.

  It seems that NHibernate was not designed for use with stored procedures,
  though it may be useful to be able to use them in some situations. This
  is a means of doing so.

  -->

  <class name="DemoCustomSprocObj">
    <id name="Guid" type="guid" unsaved-value="00000000-0000-0000-0000-000000000000">
      <generator class="guid"></generator>
    </id>
    <property name="ID" />
    <property name="Name" />
  </class>

  <sql-query name="usp_DemoCustomSproc">
    <return alias="usp_DemoCustomSproc" class="DemoCustomSprocObj">

      <return-property name="Guid" column="Guid" />
      <return-property name="ID" column="ID" />
      <return-property name="Name" column="Name" />

    </return>

    exec usp_DemoCustomSproc :TrackedEntityID

  </sql-query>

</hibernate-mapping>