В документации и статьях описано как произвести маппинг объектов на хранимые процедуры (CRUD).
Описано также что существуют некоторые ограничения, так например, порядок следования параметров в хп должен соответветствовать порядку в котором NHibernate генерит запрос к БД.
Исходя из этого чато делаются выводы:
1. что с помощью NHibernate можно организовать маппинга на хп, если они (хп) создаются с "чистого листа" и если определять параметры в хп в том же порядке в котором генерит NHibernate.
2. NHibernate не подходит для маппинга объектов на хп, разработанные без оглядки на NHibernate
Однако,
это не совсем так, по крайней мере для MSSQL.
MSSQL поддерживает синтаксис вызова хп, в котором можно явно задать имя параметра, таким образом, порядок следования параметров в синтаксисе вызова не обязательно должен соответствовать порядку параметров в определении хп.
пример, подобного синтаксиса:
/*exec @RET_CODE = CreateDocument*/
exec CreateDocument
@Title = ?
,@FileName = ?
,@FileExtension = ?
,@Revision = ?
,@ChangeNumber = ?
,@Status = ?
,@DocumentSummary = ?
,@ModifiedDate = ?
,@DocumentID = ?
Я всегда стараюсь использовать именно такой вызов хп, потому что этот синтаксис более строгий и более информативный, а поэтому предупреждает появление ошибок (в частности ошибок несоответствия следования параметров).
Хранимая процедура может быть определена следующим образом:
CREATE PROCEDURE CreateDocument(
--<ParamDefBlock>
@DocumentID int = null out
,@Title nvarchar(50) = null
,@FileName nvarchar(400) = null
,@FileExtension nvarchar(8) = null
,@Revision nchar(5) = null
,@ChangeNumber int = null
,@Status tinyint = null
,@DocumentSummary nvarchar(Max) = null
,@ModifiedDate datetime = null
--</ParamDefBlock>
)
AS
BEGIN
SET NOCOUNT ON
--<DeclareBlock>
--<DeclareSysVarBlock>
declare
@RET_CODE int
,@ERROR_CODE int
,@TRAN_CHECK int
--</DeclareSysVarBlock>
--<DeclareVarBlock>
--</DeclareVarBlock>
--</DeclareBlock>
--<PrepareTranBlock>
set @TRAN_CHECK = case when @@TRANCOUNT > 0 or @@OPTIONS & 2 > 0 then 1 else 0 end
select
@RET_CODE = 0
,@ERROR_CODE = 0
if @TRAN_CHECK = 0 BEGIN TRAN
--</PrepareTranBlock>
--<InsertBlock>
INSERT INTO Production.Document(Title, FileName, FileExtension, Revision, ChangeNumber, Status, DocumentSummary, ModifiedDate)
VALUES(@Title, @FileName, @FileExtension, @Revision, @ChangeNumber, @Status, @DocumentSummary, @ModifiedDate)
--</InsertBlock>
set @ERROR_CODE = @@ERROR
if (@ERROR_CODE <> 0) or (@RET_CODE <> 0) GOTO UNDO
--<SuccBlock>
SUCC:
SET @DocumentID = SCOPE_IDENTITY()
SELECT @DocumentID as DocumentID
if (@TRAN_CHECK = 0) and (@@TRANCOUNT > 0) COMMIT TRAN
RETURN(0)
--</SuccBlock>
--<UndoBlock>
UNDO:
if (@@TRANCOUNT > 0)
if @TRAN_CHECK = 0 ROLLBACK TRAN
RETURN(1)
--</UndoBlock>
END
Маппинг в NHibernate может выглядеть так, как показано ниже.
В определении маппинга используем синтаксис с явным определением имен параметров.
Параметр, соответствующий id (DocumentID) определяем последним в списке (так требуется для корректной работы NHibernate).
Порядок параметров при вызове из NHibernate не соответствует порядку параметров в определении хп, однако решение работоспособно и мы добились некоторой развязки NHibernate и хп.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="WebApplication.Document, WebApplication" table="Production.Document">
<id name="DocumentID" column="DocumentID">
<generator class="identity" />
</id>
<property name="Title" />
<property name="FileName" />
<property name="FileExtension" />
<property name="Revision" />
<property name="ChangeNumber" />
<property name="Status" />
<property name="DocumentSummary" />
<property name="ModifiedDate" />
<property name="DocumentID" />
<sql-insert check="none">
exec xsp_Ins_Document
@Title = ?
,@FileName = ?
,@FileExtension = ?
,@Revision = ?
,@ChangeNumber = ?
,@Status = ?
,@DocumentSummary = ?
,@ModifiedDate = ?
,@DocumentID = ?
</sql-insert>
</class>
</hibernate-mapping>
С> <sql-insert check="none">
С> exec CreateDocument
С> @Title = ?
С> ,@FileName = ?
С> ,@FileExtension = ?
С> ,@Revision = ?
С> ,@ChangeNumber = ?
С> ,@Status = ?
С> ,@DocumentSummary = ?
С> ,@ModifiedDate = ?
С> ,@DocumentID = ?
С> </sql-insert>
Добрый день.
У меня вопрос в догонку.
Не пробовали-ли вы делать custom-mapping для отношения многие-ко-многим на хранимых процедурах?
У меня почему-то в итоге происходит NullReferenceException внутри коллекции, которые
использует NHibernate для хранения соотв. множеств записей при реализации указанного отношения независимо от того, использую я lazy-load или нет.
(Происходит именно с many-to-many).
Спасибо.
Добрый день.
Вот уже сутки борюсь с получением id-шника после insert-a
Меня смущает указанный вами
<generator class="identity" />
У меня в этом случае вызывается "прямой" insert в таблицу, а потом select SCOPE_IDENTITY()
Здравствуйте, D.Triton, Вы писали:
DT>У меня в этом случае вызывается "прямой" insert в таблицу, а потом select SCOPE_IDENTITY()
Тут же все сказано:
Note that the custom sql-insert will not be used if you use identity to generate identifier values for the class.
Здравствуйте, Нахлобуч, Вы писали:
Н>Здравствуйте, D.Triton, Вы писали:
DT>>У меня в этом случае вызывается "прямой" insert в таблицу, а потом select SCOPE_IDENTITY()
Н>Тут же все сказано:
Н>Н>Note that the custom sql-insert will not be used if you use identity to generate identifier values for the class.
Спасибо, не увидел, но проверил опытным путем.
Я отдельно запостил свою проблему:
здесьАвтор: D.Triton
Дата: 24.06.08
Меня интересует, кто и как выставляет первичные ключи, сгенерированные на стороне сервера.