NHibernate custom insert
От: D.Triton Украина  
Дата: 24.06.08 09:39
Оценка:
Добрый день, господа.

Какой генератор следует использовать в маппинге объекта,
если вставка объекта в таблицу реализуется через хранимую процедуру и там-же происходит генерация первичного ключа?

Если я использую:

<class name="AST.PartnerSite20.Core.Domain.User, AST.PartnerSite20.Core.Dao">
        <id name="UserId" type="System.Int32" column="UserId" unsaved-value="0">
            <generator class="assigned"/>
        </id>
        <property name="ClientId" type="System.Int32" not-null="true" />
    ......
                 <sql-insert check="none">
            exec up_User_i @ClientId=?, @Login=?, @FirstName=?, @LastName=?, @Email=?, @Password=?, @UserId=?
        </sql-insert>

В up_User_i параметр @UserId является out параметром

То Id-шник не ассигнится.

Если поставить <generator class="identity"/> — тогда вызывается "прямой" insert в таблицу и затем выполняется select SCOPE_ISENTITY()

А это не возможно, т.к. нет прав на таблицы/вьюхи.

Кто и как решал данную проблему?

Спасибо.
Re: NHibernate custom insert
От: Нахлобуч Великобритания https://hglabhq.com
Дата: 24.06.08 10:00
Оценка:
Здравствуйте, D.Triton, Вы писали:

DT>Кто и как решал данную проблему?


Просто мысль -- а если попробовать сделать

exec up_User_i @ClientId=?, @Login=?, @FirstName=?, @LastName=?, @Email=?, @Password=?, @UserId=?; select scope_identity()
HgLab: Mercurial Server and Repository Management for Windows
Re[2]: NHibernate custom insert
От: D.Triton Украина  
Дата: 24.06.08 10:10
Оценка:
Здравствуйте, Нахлобуч, Вы писали:

Н>Здравствуйте, D.Triton, Вы писали:


DT>>Кто и как решал данную проблему?


Н>Просто мысль -- а если попробовать сделать


Н>exec up_User_i @ClientId=?, @Login=?, @FirstName=?, @LastName=?, @Email=?, @Password=?, @UserId=?; select scope_identity()


Думаю, нужно сделать так.

Сейчас сорцы хибернейта посмотрю (как работает генератор identity). И сделаю по его образу и подобию свой, только
чтобы давал на запуск кастомный запрос на добавление. Т.е итого мы получим запрос, приведенный вами выше.

Спасибо за мыслю Если получится, пример добавлю
Re[2]: NHibernate custom insert
От: D.Triton Украина  
Дата: 02.07.08 16:23
Оценка:
Здравствуйте, Нахлобуч, Вы писали:

Н>Здравствуйте, D.Triton, Вы писали:


DT>>Кто и как решал данную проблему?


Н>Просто мысль -- а если попробовать сделать


Н>exec up_User_i @ClientId=?, @Login=?, @FirstName=?, @LastName=?, @Email=?, @Password=?, @UserId=?; select scope_identity()


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

Для этого понадобилось реализовать свой драйвер MsSql, свой Persister и IdentityGenerator

Даже, я бы сказал, работает поддержка out параметров. Синтаксис такой:


<sql-insert check="none">
[sql]

            exec up_User_i @ClientId=?, @Login=?, @FirstName=?, @LastName=?, @Email=?, @Password=?, @UserId=? output
[/sql]
</sql-insert>



Т.е. после @parameterName=? нужно обязательно ставить output

Если кого заинтересует — отпишитесь в топике, я тогда напишу как и что нужно сделать.
Всем спасибо.
nhibernate
Re: NHibernate custom insert
От: снежок Россия  
Дата: 02.07.08 17:20
Оценка:
при маппинге на хп, NHibernate не умеет использовать ID который возвращается через out параметр.
ID в данном случае нужно возвращать select-ом из хп, тогда можно использовать <generator class="identity" />
--SELECT @ID
Пример:
CREATE PROCEDURE xsp_Ins_Person(
--<ParamDefBlock>
 @ID                              bigint                = null out
,@firstName                       varchar(254)          = null
,@lastName                        varchar(254)          = null
,@Debug                           bit                   = 0
--</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 Person(firstName, lastName)
                VALUES(@firstName, @lastName)
    --</InsertBlock>
    
    set @ERROR_CODE = @@ERROR
    if (@ERROR_CODE <> 0) or (@RET_CODE <> 0) GOTO UNDO
    --<SuccBlock>
    SUCC:
        if (@TRAN_CHECK = 0) and (@@TRANCOUNT > 0) COMMIT TRAN
        set @ID = SCOPE_IDENTITY()
        SELECT @ID
        RETURN(0)
    --</SuccBlock>

    --<UndoBlock>
    UNDO:
        if (@@TRANCOUNT > 0)
            if @TRAN_CHECK = 0 ROLLBACK TRAN 
        RETURN(1)
    --</UndoBlock>
END
nhibernate
Re[2]: NHibernate custom insert
От: Polevi  
Дата: 03.07.08 05:43
Оценка:
Здравствуйте, снежок, Вы писали:

С>при маппинге на хп, NHibernate не умеет использовать ID который возвращается через out параметр.

С>ID в данном случае нужно возвращать select-ом из хп, тогда можно использовать <generator class="identity" />

Note that the custom sql-insert will not be used if you use identity to generate identifier values for the class.

??

не работает это
Re[3]: NHibernate custom insert
От: снежок Россия  
Дата: 03.07.08 05:53
Оценка:
Здравствуйте, Polevi, Вы писали:
P>Note that the custom sql-insert will not be used if you use identity to generate identifier values for the class.
P>??
P>не работает это

Какая версия? Я с NHibernate 2.0.0 Alpha2 прототипирую и тестирую. У меня работает, правда все равно в конечном итоге планирую guid использовать с генерацией на стороне приложения.
Re[4]: NHibernate custom insert
От: Polevi  
Дата: 03.07.08 06:17
Оценка:
Здравствуйте, снежок, Вы писали:

С>Здравствуйте, Polevi, Вы писали:

P>>Note that the custom sql-insert will not be used if you use identity to generate identifier values for the class.
P>>??
P>>не работает это

С>Какая версия? Я с NHibernate 2.0.0 Alpha2 прототипирую и тестирую. У меня работает, правда все равно в конечном итоге планирую guid использовать с генерацией на стороне приложения.


1.2.1 у меня.. с альфой 2.0 решил пока не связываться
Re[5]: NHibernate custom insert
От: Polevi  
Дата: 03.07.08 07:15
Оценка:
попробовал с 2.0 beta 1
SP вызывается, но генерируется вот такой sql
exec sp_executesql N'exec ClientCreate @Id=@p0', N'@p0 int', @p0 = default

такой запрос сервер не кушает — default ему непонятно
Re[6]: NHibernate custom insert
От: снежок Россия  
Дата: 03.07.08 08:12
Оценка:
Здравствуйте, Polevi, Вы писали:

P>попробовал с 2.0 beta 1

P>SP вызывается, но генерируется вот такой sql
P>exec sp_executesql N'exec ClientCreate @Id=@p0', N'@p0 int', @p0 = default
P>такой запрос сервер не кушает — default ему непонятно

Была такая проблема, решилась путем описания в файле маппинга свойства ID, причем последним в списке свойств класса.
Тогда все ок.


<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Enterprise.Employee, NHibernateStartLib" table="vEmployee">
<id name="ID" column="ID" type="Int32">
<generator class="identity" />
</id>
<version name="LastChangeLabel" column="LastChangeLabel" type="Byte[]" generated="always">
</version>
<property name="FirstName" />
<property name="LastName" />
<property name="Position" />
<property name="ID" />
<sql-insert>
          exec xsp_Ins_Employee
           @firstName                      = ?
          ,@lastName                       = ?
          ,@position                       = ?
          ,@Debug                          = 0
          ,@ID                             = ? out
</sql-insert>
</class>
</hibernate-mapping>
Re[7]: NHibernate custom insert
От: Polevi  
Дата: 03.07.08 08:36
Оценка:
Здравствуйте, снежок, Вы писали:

С>Здравствуйте, Polevi, Вы писали:


P>>попробовал с 2.0 beta 1

P>>SP вызывается, но генерируется вот такой sql
P>>exec sp_executesql N'exec ClientCreate @Id=@p0', N'@p0 int', @p0 = default
P>>такой запрос сервер не кушает — default ему непонятно

С>Была такая проблема, решилась путем описания в файле маппинга свойства ID, причем последним в списке свойств класса.

С>Тогда все ок.


да так лучше
но теперь он после после вставки сразу пытается выполнить запрос на изменение.. и вываливается с ошибкой "не могу изменять идентити поле"

exec sp_executesql N'exec ClientCreate @Id=@p0', N'@p0 int', @p0 = 0
exec sp_executesql N'UPDATE Client SET Id = @p0 WHERE Id = @p1', N'@p0 int,@p1 int', @p0 = 44, @p1 = 44

вообщем сплошные пляски с бубном, видимо тоже буду смотреть в сторону GUID
Re: NHibernate custom insert
От: Ziaw Россия  
Дата: 03.07.08 08:38
Оценка:
Здравствуйте, D.Triton, Вы писали:

Вижу уже не первый вопрос по маппингу с хранимками.
Никак не пойму зачем использовать хранимые процедуры если используется NHibernate и затем мужественно бороться с побочными эффектами возникающими при этом.

Я могу предположить только 3 варианта:
  1. натягиваем ORM на существующую и работающую схему БД сильно завязанную на ХП.
  2. ХП кроме вставки делает что-то в других таблицах не замапленых NH.
  3. ХП вставляет не то, что мы передали, делая какие-то хитрые БЛ преобразования.

Уважительной я считаю только первую причину. Во втором случае лучше сделать триггер, третьего лучше вообще не допускать.

Может есть причины которые я не увидел? Хочу понять, для чего это делается.
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re[2]: NHibernate custom insert
От: снежок Россия  
Дата: 03.07.08 08:58
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Вижу уже не первый вопрос по маппингу с хранимками.

Z>Никак не пойму зачем использовать хранимые процедуры если используется NHibernate и затем мужественно бороться с побочными эффектами возникающими при этом.

Z>Может есть причины которые я не увидел? Хочу понять, для чего это делается.


Философская и флеймогонная тема.
Могу лишь высказать свои суждения вдополнение к выше приведенным по поводу использования хп, которые вполне могут быть оспорены (только не будем это делать здесь, ладно? .
1. По моему мнению с БД нужно работать аккуратно, потому выделяю CRUD в виде хп.
2. хп и вью дают некоторый уровень абстракции работы с БД.
3. Маппинг непосредственно на таблицы "связывает" руки разработчику БД. Т.е. не получается абстрагироваться от приложения, не получается жестко разделить роли. Разработчику/администратору БД приходится более тесно общаться с другими членами комманды, он теперь обязан знать маппинг NH и оценивать свои процедуры рефакторинга с оглядкой на маппинг.
4. DBA раздражает каша, которую он видит в профайлере. Ему трудно анализировать возникающие проблемы.
5. Часто существуют жесткие требования по безопасности, когда закрывают доступ к таблицам и дают доступ только к хп и вью.
6. Нет хп — нет возможности по-нормальному(самостоятельно) провести тестирование создания объектов БД, например, с помощью DbUnit, т.е. опять же без оглядки на код среднего звена/клиента.
Re[8]: NHibernate custom insert
От: Polevi  
Дата: 03.07.08 09:00
Оценка:
P>да так лучше
P>но теперь он после после вставки сразу пытается выполнить запрос на изменение.. и вываливается с ошибкой "не могу изменять идентити поле"

уфф, получилось в итоге, вот маппинг

<class name="Client" table="Client">
<id name="Id" column="Id" type="Int32">
<generator class="identity"/>
</id>
<property name="Id" update="false"/>
<sql-insert>exec ClientCreate @Id=? out</sql-insert>
</class>
Re[8]: NHibernate custom insert
От: снежок Россия  
Дата: 03.07.08 09:05
Оценка:
P>да так лучше
P>но теперь он после после вставки сразу пытается выполнить запрос на изменение.. и вываливается с ошибкой "не могу изменять идентити поле"

P>exec sp_executesql N'exec ClientCreate @Id=@p0', N'@p0 int', @p0 = 0

P>exec sp_executesql N'UPDATE Client SET Id = @p0 WHERE Id = @p1', N'@p0 int,@p1 int', @p0 = 44, @p1 = 44

P>вообщем сплошные пляски с бубном, видимо тоже буду смотреть в сторону GUID


Покажите маппинг-файл.
Скорее всего в пред. версиях эта проблема нерешаемая.
...
GUID лучше — не будет впоследствии проблем с репликацией, бек-офисом и т.п.
Re[9]: NHibernate custom insert
От: снежок Россия  
Дата: 03.07.08 09:06
Оценка:
P>уфф, получилось в итоге, вот маппинг

поздравляю
Re[7]: NHibernate custom insert
От: D.Triton Украина  
Дата: 03.07.08 09:17
Оценка: 1 (1)
Здравствуйте, снежок, Вы писали:

С>Здравствуйте, Polevi, Вы писали:


P>>попробовал с 2.0 beta 1

P>>SP вызывается, но генерируется вот такой sql
P>>exec sp_executesql N'exec ClientCreate @Id=@p0', N'@p0 int', @p0 = default
P>>такой запрос сервер не кушает — default ему непонятно

С>Была такая проблема, решилась путем описания в файле маппинга свойства ID, причем последним в списке свойств класса.

С>Тогда все ок.


С>
С><?xml version="1.0" encoding="utf-8"?>
С><hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
С><class name="Enterprise.Employee, NHibernateStartLib" table="vEmployee">
С><id name="ID" column="ID" type="Int32">
С><generator class="identity" />
С></id>
С><version name="LastChangeLabel" column="LastChangeLabel" type="Byte[]" generated="always">
С></version>
С><property name="FirstName" />
С><property name="LastName" />
С><property name="Position" />
С><property name="ID" />
С><sql-insert>
С>          exec xsp_Ins_Employee
С>           @firstName                      = ?
С>          ,@lastName                       = ?
С>          ,@position                       = ?
С>          ,@Debug                          = 0
С>          ,@ID                             = ? out
С></sql-insert>
С></class>
С></hibernate-mapping>
С>



Похоже, что в 2-м хибернейте добавили

Для себя же в своем драйвере ( я использую хибернейт 1.2.1) пришлось выкручиваться следующим образом:

1. Наследоваться от SqlClientDriver
2. Перекрыть

public virtual IDbCommand GenerateCommand(CommandType type, SqlString sqlString, SqlType[] parameterTypes);


Т.к. ее текущая реализация не создавала output парамерты. Все создаваемые IDbDataParameter параметры имели Detination — Input.

Поэтому я и создал свой драйвер и перекрыл соотв. метод:


public class MySqlClientDriver : global::NHibernate.Driver.SqlClientDriver
    {
        private readonly string parameterPairPattern = @"@[^,]*";

        public override IDbCommand GenerateCommand(CommandType type, SqlString sqlString, SqlType[] parameterTypes)
        {
            IDbCommand command = base.GenerateCommand(type, sqlString, parameterTypes);
            string cmdSql = command.CommandText.ToLowerInvariant().Trim();

            ///Works only for stored procs
            if (cmdSql.StartsWith("exec", StringComparison.InvariantCulture))
            {
                cmdSql = cmdSql.Remove(0, 4).Trim();
                Regex regex = new Regex(parameterPairPattern);
                MatchCollection coll = regex.Matches(cmdSql);

                for (int i = 0; i < coll.Count; ++i)
                {
                    Match match = coll[i];
                    if (match.Value.EndsWith("out") || match.Value.EndsWith("output"))
                    {
                        ((IDbDataParameter) command.Parameters[i]).Direction = ParameterDirection.Output;
                    }
                }
            }
            return command;
        }

         /// <summary>
        /// Checks whether identifier is generated by output parameter or not
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public virtual bool IsIdentifierGeneratedByOutParameter(SqlCommandInfo sql)
        {
            string cmdSql = sql.Text.ToString().ToLowerInvariant().Trim();
            ///Works only for stored procs
            if (cmdSql.StartsWith("exec", StringComparison.InvariantCulture))
            {
                cmdSql = cmdSql.Remove(0, 4).Trim();
                Regex regex = new Regex(parameterPairPattern);
                MatchCollection coll = regex.Matches(cmdSql);
                if (coll.Count == 0)
                    return false;
                Match match = coll[coll.Count - 1]; ///Identifier match
                return match.Value.EndsWith("out") || match.Value.EndsWith("output");
            }
            return false;
        }
}


Потом пришлось реализовать персистер.
По сути за основу взял AbstractPersister.

Только в реализацию метода

/// <summary>
        /// Persist an object, using a natively generated identifier
        /// </summary>
        protected object Insert(object[] fields, bool[] notNull, SqlCommandInfo sql, object obj,
                                ISessionImplementor session);

внес небольшие изменения (отмечено жирным курсивом):


/// <summary>
        /// Persist an object, using a natively generated identifier
        /// </summary>
        protected object Insert(object[] fields, bool[] notNull, SqlCommandInfo sql, object obj,
                                ISessionImplementor session)
        {
            if (log.IsDebugEnabled)
            {
                log.Debug("Inserting entity: " + ClassName + " (native id)");
                if (IsVersioned)
                {
                    log.Debug("Version: " + Versioning.GetVersion(fields, this));
                }
            }

            try
            {
                bool useOutputParameterForId = false;
                SqlString insertSelectSQL = null;

                if (sql.CommandType == CommandType.Text)
                {
                    MySqlClientDriver driver = session.SessionFactory.ConnectionProvider.Driver as MySqlClientDriver;
                    if (driver != null && driver.IsIdentifierGeneratedByOutParameter(sql))
                    {
                        useOutputParameterForId = true;
                    }
                    else
                    {
                        insertSelectSQL =
                            Dialect.AddIdentitySelectToInsert(sql.Text, GetKeyColumns(0)[0], GetTableName(0));
                    }
                  
                }

                if (insertSelectSQL != null)
                {
                    // Use one statement to insert the row and get the generated id
                    IDbCommand insertSelect =
                        session.Batcher.PrepareCommand(CommandType.Text, insertSelectSQL, sql.ParameterTypes);

                    IDataReader rs = null;
                    try
                    {
                        // Well, it's always the first table to dehydrate, so pass 0 as the position
                        Dehydrate(null, fields, notNull, propertyColumnInsertable, 0, insertSelect, session, 0);
                        rs = session.Batcher.ExecuteReader(insertSelect);
                        return GetGeneratedIdentity(obj, session, rs);
                    }
                    finally
                    {
                        session.Batcher.CloseCommand(insertSelect, rs);
                    }
                }
                else
                {
                    // Do the insert
                    IDbCommand statement = session.Batcher.PrepareCommand(sql.CommandType, sql.Text, sql.ParameterTypes);
                    try
                    {
                        // Well, it's always the first table to dehydrate, so pass 0 as the position
                        Dehydrate(null, fields, notNull, propertyColumnInsertable, 0, statement, session, 0);
                        session.Batcher.ExecuteNonQuery(statement);
                        ProcessOutputParameters(null, fields, notNull, PropertyColumnInsertable, 0, statement, session,
                                                0,obj); // выставляет значениея пропертям класса, которые смаплены на хранимку и помечены как output
                                                        // но не выставляет идентификатор
                        

                        // Fetch the generated id in a separate query. This is done inside the first try/finally block
                        // to keep the insert command open, so that the batcher does not close the connection.
                        //
                        // It's possible that some ADO.NET provider will not allow two open IDbCommands for the same connection,
                        // in that case we'll have to rewrite the code to use some sort of lock on IBatcher.
                        if (!useOutputParameterForId)
                        {
                            SqlString idselectSql =
                                new SqlString(SqlIdentitySelect(IdentifierColumnNames[0], GetTableName(0)));
                            IDbCommand idselect =
                                session.Batcher.PrepareCommand(CommandType.Text, idselectSql, SqlTypeFactory.NoTypes);
                            IDataReader rs = null;

                            try
                            {
                                rs = session.Batcher.ExecuteReader(idselect);
                                return GetGeneratedIdentity(obj, session, rs);
                            }
                            finally
                            {
                                session.Batcher.CloseCommand(idselect, rs);
                            }
                        }
                        else // Получаем идентификатор
                        {
                            int paramCount = statement.Parameters.Count;
                            if (((IDbDataParameter) statement.Parameters[paramCount - 1]).Direction ==
                                ParameterDirection.Output)
                            {
                                object val = ((IDbDataParameter) statement.Parameters[paramCount - 1]).Value;
                                if (System.Convert.IsDBNull(val))
                                    return null;
                                return val;
                            }
                            else
                            {
                                return null;
                            }
                        }
                    }
                    finally
                    {
                        session.Batcher.CloseCommand(statement, null);
                    }
                }
            }
            catch (HibernateException)
            {
                // Do not call Convert on HibernateExceptions
                throw;
            }
            catch (Exception sqle)
            {
                throw Convert(sqle, "could not insert: " + MessageHelper.InfoString(this), sql.Text);
            }
        }





Плюс пришлось немного сменить след. код:


    public object Insert(object[] fields, object obj, ISessionImplementor session)
        {
            int span = TableSpan;
            object id;

            if (entityMetamodel.IsDynamicInsert)
            {
                // For the case of dynamic-insert="true", we need to generate the INSERT SQL
                bool[] notNull = GetPropertiesToInsert(fields);
                id = Insert(fields, notNull, GenerateInsertString(true, notNull), obj, session);
                for (int j = 1; j < span; j++)
                {
                    Insert(id, fields, notNull, j, GenerateInsertString(notNull, j), obj, session);
                }
            }
            else
            {
                // For the case of dynamic-insert="false", use the static SQL
                if (IsIdentifierAssignedByInsert)
                    id = Insert(fields, PropertyInsertability, SqlIdentityInsertString, obj, session); //вызов вышеприведенной ф-ции
                else ///if assigned by stored proc through out parameter
                {
                    //SqlCommandInfo stubCmd = new SqlCommandInfo("select null");
                    id = Insert(fields, PropertyInsertability, SqlInsertStrings[0], obj, session); //вызов вышеприведенной ф-ции
                }
                for (int j = 1; j < span; j++)
                {
                    Insert(id, fields, PropertyInsertability, j, SqlInsertStrings[j], obj, session);
                }
            }

            return id;
        }


И, наконец, в самом конце, пришлось использовать свой генератор:

По сути он идентичен реализации:
public class IdentityGenerator : IIdentifierGenerator{...}


Но главное отличие — другой тип

Поэтому св-во AbstractPersister.IsIdentifierAssignedByInsert — будет всегда false (оно берется из EntityMetamodel.IdentifierProperty.IsIdentifierAssignedByInsert )



Исходя из всего вышеприведенного можно сказать следущее:
Мы смогли добавить поддержку out параметров не меняя исходников NHibernate. Мы всего-лишь через конфиги
1. Подменили драйвер
2. Использовали свой персистер и генератор


Всем спасибо. Задавайте вопросы, если что отвечу.
Re[2]: NHibernate custom insert
От: D.Triton Украина  
Дата: 03.07.08 09:23
Оценка: +1
Здравствуйте, Ziaw, Вы писали:

Z>Здравствуйте, D.Triton, Вы писали:


Z>Вижу уже не первый вопрос по маппингу с хранимками.

Z>Никак не пойму зачем использовать хранимые процедуры если используется NHibernate и затем мужественно бороться с побочными эффектами возникающими при этом.

Иногда такое бывает, что заказчик "упирается рогом" и требует

И на все твои доводы просто закрывает глаза.

В некоторых случаях, когда заказчик "технически грамотный" приходится бороться с ветряными мельницами
Re[3]: NHibernate custom insert
От: Ziaw Россия  
Дата: 03.07.08 09:57
Оценка:
Здравствуйте, снежок, Вы писали:

С>Философская и флеймогонная тема.

Флейма по поводу использования ХП достаточно, меня интересует конретно связка SP+NH.

С>Могу лишь высказать свои суждения вдополнение к выше приведенным по поводу использования хп, которые вполне могут быть оспорены (только не будем это делать здесь, ладно? .

С>1. По моему мнению с БД нужно работать аккуратно, потому выделяю CRUD в виде хп.
Вы пропустили логическую цепочку, а без нее это напоминает утверждение: на автомобиле надо ездить аккуратно, поэтому я всегда одеваю перчатки. Оба утверждения более менее понятны, непонятно только как из первого следует второе.

С>2. хп и вью дают некоторый уровень абстракции работы с БД.

Уровня абстракции который дает NH недостаточно?

С>3. Маппинг непосредственно на таблицы "связывает" руки разработчику БД. Т.е. не получается абстрагироваться от приложения, не получается жестко разделить роли. Разработчику/администратору БД приходится более тесно общаться с другими членами комманды, он теперь обязан знать маппинг NH и оценивать свои процедуры рефакторинга с оглядкой на маппинг.

Какой рефакторинг можно произвести не оглядываясь на маппинг хранимых процедур и нельзя произвести имея маппинг на таблицыи view?

С>4. DBA раздражает каша, которую он видит в профайлере. Ему трудно анализировать возникающие проблемы.

Тут вообще непонятно, каша там или нет зависит только от запросов, при чем тут хранимки если в них будут те же самые запросы?
Архитектура приложения выбирается исходя из личных эстетических соображений DBA? А придет другой DBA, с другими мерками?

С>5. Часто существуют жесткие требования по безопасности, когда закрывают доступ к таблицам и дают доступ только к хп и вью.

Эта причина выглядит достаточно логичной. Хотелось бы пример, в котором безопасность обеспечивается ХП.

С>6. Нет хп — нет возможности по-нормальному(самостоятельно) провести тестирование создания объектов БД, например, с помощью DbUnit, т.е. опять же без оглядки на код среднего звена/клиента.

Тестирование NH? Насколько правильно он генерирует insert/update/delete? Уверены, что это необходимо?

Вы берете ORM продукт, отрезаете от него две основные функции: генерацию запросов и CRUD операций, добавляете неявный слой логики приложения, получаете кучу проблем решаемых зачастую героическими усилиями
Автор: D.Triton
Дата: 03.07.08
. Что остается? Маппинг результатов? Unit of work? Кеш который рискует выдать неверные данные, т.к. расчитан на то, что в БД лежит именно то, что сохранили, а счастливый DBA исправил ХП не вдаваясь в тонкости работы NH? Необходимость хардкодить все CRUD операции и поддерживать эту кучу кода?
... << RSDN@Home 1.2.0 alpha 4 rev. 0>>
Re[3]: NHibernate custom insert
От: Ziaw Россия  
Дата: 03.07.08 10:27
Оценка:
Здравствуйте, D.Triton, Вы писали:

DT>Иногда такое бывает, что заказчик "упирается рогом" и требует


DT>И на все твои доводы просто закрывает глаза.


DT>В некоторых случаях, когда заказчик "технически грамотный" приходится бороться с ветряными мельницами


Кстати, а вы не рассматривали возможность вызова хранимок в instead of триггерах? Есть вероятность обойтись меньшей кровью.
... << RSDN@Home 1.2.0 alpha 4 rev. 0>>
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.