![]() |
:: Главная :: Решения :: Статьи :: Сайт М. Дроздова :: Файловый архив :: Книга по VFP 9 :: Русский Help Online :: OFF-LINE Форум | ![]() |
![]() |
Лисоводы всех стран, объединяйтесь !!! |
Как автомат-ки получать автоинкриментное поле SQL сервера в КА | |||
---|---|---|---|
A1 Сообщений: 96 |
Уважаемые помогите рассказав
Использую курсорадаптеры для подсоединения к SQL SErver-ским таблицам. Все просто замечательно, но чую что кое-что можно сделать лучше, а точнее сказать правальнее. Допустим на сервере есть таблица, поле ID которой автоинкриментное, при создании курсорадаптера указваю, что разрещаю изменения и ключом ставлю поле ID И теперь сам вопрос: в программе, когда пишу TABLEUPDATE, как можно автоматически получать поле ID, сгенерированное на сервере. Сейчас получаю так - делаю рефреш КА после чего перемещаюсь на максимальное значение ID. ![]() |
||
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА | |||
---|---|---|---|
MichaelD Сообщений: 7578 |
A1,
Цитата:см. @@IDENTITY, IDENT_CURRENT('table_name'), SCOPE_IDENTITY() SQL Book online Цитата:Здесь проблема в том, что неизвестно чей именно ID ты получишь... О-очень вероятно, что не этим клиентом добавленный при интенсивной работе на добавление... ------------------ С уважением,
Михаил Дроздов, Пермь, Россия ![]() |
||
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА | |||
---|---|---|---|
Влад Колосов Сообщений: 22664 Откуда: Ростов-на-Дону |
При использовании VIEW надежнее всего отказаться от автоинкремента на сервере.
------------------ Совершенство - это не тогда, когда нельзя
ничего прибавить, а тогда, когда нечего убавить. ![]() |
||
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА | |||
---|---|---|---|
Aleksey Tsingauz [MSFT] Автор Сообщений: 407 |
Здесь есть пример forum.foxclub.ru. Примеры я постил на UT: ****************************** SET MULTILOCKS ON SET DELETED ON CLOSE DATABASES ALL CLEAR oCA=CREATEOBJECT("CACategoriesODBC") * Use ODBC connection to the Northwind database on the local SQL Server as a data source oCA.DataSource=SQLSTRINGCONNECT("DRIVER=SQL Server;SERVER=(local);Trusted_Connection=Yes; DataBase=Northwind") IF !oCA.AutoOpen() AERROR(errs) DISPLAY MEMORY like errs return ENDIF SELECT (oCA.Alias) LIST ? "Add new category" INSERT INTO (oCA.Alias) (Name) VALUES ("New category") ?[TABLEUPDATE(.F.)=],TABLEUPDATE(.F.) LIST * delete the new record from the table GO bottom DELETE TABLEUPDATE(.F.) SQLDISCONNECT(oCA.DataSource) RETURN DEFINE CLASS CACategoriesODBC AS CursorAdapter Alias="CACategories" DataSourceType="ODBC" SelectCmd="select * from categories" CursorSchema="Id I, Name C(15), Descr M, Pict M" * set update properties Tables="Categories" KeyFieldList="Id" UpdatableFieldList="Name, Descr, Pict" UpdateNameList="Id Categories.CategoryId, Name Categories.CategoryName,"+; "Descr Categories.Description, Pict Categories.Picture" CompareMemo=.F. * Use ConversionFunc property to remove leading and trailing spaces from the Name field before sending * its value to the server ConversionFunc="Name AllTrim" * set conflict check type ConflictCheckType=3 FUNCTION AutoOpen() NODEFAULT RETURN this.CursorFill(.T.) && use schema ENDFUNC PROCEDURE AfterInsert LPARAMETERS cFldState, lForce, cInsertCmd, lResult IF lResult=.T. && get new Id LOCAL currentArea currentArea=SELECT() * if insert is successful, update ID field with * real value using @@IDENTITY variable from the server * (CategoryId is IDENTITY field) TRY IF 1=SQLEXEC(this.DataSource,"SELECT @@IDENTITY as CategoryId","IDRes") replace id with IDRes.CategoryId IN (This.Alias) USE IN IDRes ELSE AERROR(errs) DISPLAY MEMORY like errs ENDIF FINALLY SELECT (currentArea) ENDTRY ENDIF ENDPROC ENDDEFINE ****************************** ******************************** *Here is an example for ADO data source type, the same approach should work for ODBC. SET EXCLUSIVE OFF SET MULTILOCKS ON SET DELETED ON CLOSE DATABASES ALL CLEAR oCA=CREATEOBJECT("CACategoriesAdo2") ?oCA.AutoOpen() WAIT WINDOW "Insert/Update/Delete records in the BROWSE" BROWSE NORMAL DEFINE CLASS CACategoriesAdo2 AS CursorAdapter Alias="CACategories" * set data source type, SELECT command and cursor schema DataSourceType="ADO" SelectCmd="select CategoryId, CategoryName, Description from categories" CursorSchema="Id I, Name C(15), Descr M" * These properties must be set in order to send updates using ADODB.Command object InsertCmdDataSourceType="ADO" UpdateCmdDataSourceType="ADO" DeleteCmdDataSourceType="ADO" * We will use custom InsertCmd, UpdateCmd and DeleteCmd * Pass ID field as an output parameter to the #CategoryIns procedure, * It will be automatically updated with new IDENTITY value InsertCmd="EXEC #CategoryIns ?@CACategories.Id OUTPUT, ?CACategories.Name, ?CACategories.Descr" UpdateCmd="EXEC #CategoryUpd ?CACategories.Id, ?OLDVAL('Name','CACategories'), ?CACategories.Name, ?CACategories.Descr" DeleteCmd="EXEC #CategoryDel ?CACategories.Id, ?OLDVAL('Name','CACategories')" PROCEDURE Init LOCAL oCon as ADODB.Connection, oRS as ADODB.Recordset, oCom as ADODB.Command * When object is created, create ADO connection object and connect * to the Northwind database on the local SQL Server oCon=CREATEOBJECT("ADODB.Connection") oCon.Open("Provider=SQLOLEDB.1;Integrated Security=SSPI;Initial Catalog=Northwind;Data Source=(local)") * Create stored procedure for Insert, return new value for CategoryId field * using output parameter @ID TEXT TO cSQL NOSHOW CREATE procedure #CategoryIns @id int OUTPUT, @Name varchar(15), @Descr text AS INSERT into Categories (CategoryName,Description) VALUES (@Name, @Descr) SELECT @id=SCOPE_IDENTITY( ) ENDTEXT oCon.Execute(cSQL) * Create stored procedure for Update, it'll check for update conflicts TEXT TO cSQL NOSHOW CREATE procedure #CategoryUpd @id int, @OldName varchar(15), @Name varchar(15), @Descr text AS SET ANSI_NULLS OFF UPDATE Categories SET CategoryName=@Name,Description=@Descr WHERE CategoryId=@Id AND CategoryName=@OldName IF @@ROWCOUNT=0 AND @@ERROR=0 RAISERROR (' Update conflict.', 16, 1) ENDTEXT oCon.Execute(cSQL) * Create stored procedure for Delete, it'll check for update conflicts TEXT TO cSQL NOSHOW CREATE procedure #CategoryDel @id int, @Name varchar(15) AS SET ANSI_NULLS OFF DELETE Categories WHERE CategoryId=@Id AND CategoryName=@Name IF @@ROWCOUNT=0 AND @@ERROR=0 RAISERROR (' Update conflict.', 16, 1) ENDTEXT oCon.Execute(cSQL) * Create Recordset object and configure it for better fetch performance, * we will not use it for updates oRs=CREATEOBJECT("ADODB.Recordset") oRS.ActiveConnection=oCon oRS.CursorLocation= 2 && adUseServer oRS.CursorType= 0 && adOpenForwardOnly oRS.LockType= 1 && adLockReadOnly this.DataSource=oRS * Create the ADODB.Command object to send updates through oCom=CREATEOBJECT("ADODB.Command") oCom.ActiveConnection=oCon this.InsertCmdDataSource=oCom this.UpdateCmdDataSource=oCom this.DeleteCmdDataSource=oCom ENDPROC FUNCTION AutoOpen() NODEFAULT RETURN this.CursorFill(.T.,.F.,-1) && use schema ENDFUNC * The following events are used only to print * commands that are being executed by the object PROCEDURE BeforeInsert LPARAMETERS cFldState, lForce, cInsertCmd ? ? PROGRAM() ? "cInsertCmd=",cInsertCmd ENDPROC PROCEDURE BeforeUpdate LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd ? ? PROGRAM() ? "cUpdateInsertCmd=",cUpdateInsertCmd ? "cDeleteCmd=",cDeleteCmd ENDPROC PROCEDURE BeforeDelete LPARAMETERS cFldState, lForce, cDeleteCmd ? ? PROGRAM() ? "cDeleteCmd=",cDeleteCmd ENDPROC ENDDEFINE ******************************** ************************************** CLOSE DATABASES all SQLDISCONNECT(0) CLEAR SET MULTILOCKS ON LOCAL nAutoRefreshConn as Integer * Change connection string to connect to any available SQL Server nAutoRefreshConn=SQLSTRINGCONNECT("DRIVER=SQL Server;SERVER=(local);Trusted_Connection=Yes;") IF (nAutoRefreshConn <1) ? "Failed to connect!!!" AERROR(aerrs) DISPLAY MEMORY LIKE aerrs return ENDIF TEXT TO cSQL NOSHOW CREATE TABLE #CAAutoRefreshDemo ( f_IDENTITY int NOT NULL IDENTITY PRIMARY KEY, f1 int NOT NULL UNIQUE, f_TIMESTAMP timestamp ) ENDTEXT IF SQLEXEC(nAutoRefreshConn ,cSQL)!=1 ? "Failed to create demo table!!!" AERROR(aerrs) DISPLAY MEMORY LIKE aerrs ENDIF LOCAL oCA as CursorAdapter oCA=CREATEOBJECT("CursorAdapter") oCA.Alias = "CATest" oCA.BufferModeOverride= 5 oCA.DataSource= nAutoRefreshConn oCA.DataSourceType="ODBC" oCA.SelectCmd="select * from #CAAutoRefreshDemo" oCA.CursorSchema="f_IDENTITY I, f1 I, f_TIMESTAMP Q(8)" oCA.UseCursorSchema= .T. oCA.Tables="#CAAutoRefreshDemo" oCA.TimestampFieldList="f_TIMESTAMP" oCA.UpdatableFieldList="f1" oCA.UpdateNameList="f1 #CAAutoRefreshDemo.f1, f_TIMESTAMP #CAAutoRefreshDemo.f_TIMESTAMP, f_IDENTITY #CAAutoRefreshDemo.f_IDENTITY" oCA.CursorFill() ?"1) Refresh IDENTITY field using [SELECT @@IDENTITY] command" oCA.InsertCmdRefreshFieldList="f_IDENTITY" oCA.InsertCmdRefreshCmd="SELECT @@IDENTITY" INSERT INTO CATest (f1) VALUES (1) INSERT INTO CATest (f1) VALUES (2) INSERT INTO CATest (f1) VALUES (3) IF !TABLEUPDATE(.T.) ? "TABLEUPDATE is failed!!!" AERROR(aerrs) DISPLAY MEMORY LIKE aerrs ELSE LIST ENDIF ? ?"2) Refresh IDENTITY and TIMESTAMP fields using " ?" [SELECT f_IDENTITY, f_TIMESTAMP FROM #CAAutoRefreshDemo WHERE f_IDENTITY=@@IDENTITY] command" oCA.InsertCmdRefreshFieldList="f_IDENTITY" oCA.RefreshTimestamp= .T. oCA.InsertCmdRefreshCmd="SELECT f_IDENTITY, f_TIMESTAMP FROM #CAAutoRefreshDemo WHERE f_IDENTITY=@@IDENTITY" INSERT INTO CATest (f1) VALUES (40) INSERT INTO CATest (f1) VALUES (50) INSERT INTO CATest (f1) VALUES (60) IF !TABLEUPDATE(.T.) ? "TABLEUPDATE is failed!!!" AERROR(aerrs) DISPLAY MEMORY LIKE aerrs ELSE LIST ENDIF ?"3) Refresh IDENTITY and TIMESTAMP fields using alternative unique key" oCA.RefreshTimestamp= .F. && just for demo purposes will include f_TIMESTAMP into InsertCmdRefreshFieldList oCA.InsertCmdRefreshFieldList="f_IDENTITY, f_TIMESTAMP " oCA.InsertCmdRefreshKeyFieldList="f1" oCA.InsertCmdRefreshCmd="" && CursorAdapter will generate command automatically INSERT INTO CATest (f1) VALUES (700) INSERT INTO CATest (f1) VALUES (800) INSERT INTO CATest (f1) VALUES (900) IF !TABLEUPDATE(.T.) ? "TABLEUPDATE is failed!!!" AERROR(aerrs) DISPLAY MEMORY LIKE aerrs ELSE LIST ENDIF TABLEREVERT(.T.) SQLDISCONNECT(0) RETURN ************************************************ ******************************** CLEAR LOCAL oConn as ADODB.Connection, oRS as ADODB.Recordset, oCom as ADODB.Command oConn=CREATEOBJECT("ADODB.Connection") oConn.Open("Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Data Source=(local)") oConn.Execute("create table #test (f1 int IDENTITY)") oConn.Execute("create procedure #test_ins_1 @f1 int OUT as "+; " insert into #test DEFAULT VALUES "+; " select @f1=SCOPE_IDENTITY()") oConn.Execute("create procedure #test_ins_2 as "+; " insert into #test DEFAULT VALUES "+; " return SCOPE_IDENTITY()") oCom = CREATEOBJECT("Adodb.Command") oCom.ActiveConnection = oConn oRS = CREATEOBJECT("Adodb.Recordset") oRS.ActiveConnection = oConn LOCAL oCA as CursorAdapter oCA=CREATEOBJECT("CursorAdapter") oCA.Alias="test" oCA.DataSourceType="ADO" oCA.DataSource=oRS oCA.SelectCmd="select * from #test" oCA.InsertCmdDataSourceType="ADO" oCA.InsertCmdDataSource=oCom oCA.InsertCmd="EXEC #test_ins_1 ?@test.f1 OUT" ?oCA.CursorFill() APPEND BLANK APPEND BLANK APPEND BLANK oCA.InsertCmd="EXEC ?@test.f1 = #test_ins_2" APPEND BLANK APPEND BLANK APPEND BLANK LIST ?oCA.CursorRefresh() LIST USE ******************************** ![]() |
||
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА | |||
---|---|---|---|
Владимир Максимов Сообщений: 13872 Откуда: Москва |
Извиняюсь, что поднял старую тему, но тут на www.sql.ru на нескольких страницах пытаемся убедить товарища FoXXX, что способ поиска новой записи в Remote View как Requery()+Go Bottom - работает только при выполнении определенных условий. Ну, ладно, товарищь так отдыхает...
Но вообще-то, вопрос был поднят более общий. Все по той же теме. Как получить значение поля IDENTITY при работе через CAD. В связи с этим еще раз посмотрел решения предложенные Алексеем. Не ради критики, а для пояснения тем, кто пойдет тем же путем... Вкратце, их суть сводится к следующему 1) через ODBC - после успешного создания новой записи читается значение @@IDENTITY 2) через ADO - вместо прямой команды вставки формируется хранимая процедура, куда в качестве параметра передаются значения полей. В этой процедуре собственно и выполняется команда INSERT и возвращается значение SCOPE_IDENTITY() 3) AutoRefresh - в таблицу вводится поле или набор полей, по значению которого также можно однозначно идентифицировать запись. Это НЕ ключевые поля. После вставки записи осуществляется поиск новой записи по значению этих вспомогательных полей 4) это дубль способа 2) несколько по другому оформленный. Недостатки: 1) через ODBC - Работает, если у таблицы, куда происходит вставка нет триггера, в теле которого также есть вставка в таблицу имеющую поле типа IDENTITY. В этом случае получим значение поля из этой второй таблицы 2) и 4) через ADO - Вполне рабочее решение. Единственный недостаток - это избыточность (зачем посылать те поля, в которых ничего не ввели) и проблемы в случае, если в таблице допустимы поля со значением NULL (как отличить те поля в которые ничего не ввели от тех, в которые ввели пустое значение. Здесь подошло бы DEFAULT на поля, но как это настроить в CAD?) 3) AutoRefresh - Далеко не всегда в таблице есть дублирующие поля, которые могут взять на себя роль ID, даже в пределах выборки. ![]() |
||
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА | |||
---|---|---|---|
Aleksey Tsingauz [MSFT] Автор Сообщений: 407 |
Здравствуйте, Владимир!
Действительно вопрос достаточно серьезный и общие пояснения не помешают. Существут несколько разных способов извлечения значения IDENTITY после вставки новой записи на сервере и примеры демонстрируют как использовать эти способы с CursorAdapter-ом. 1) Запросить значение @@IDENTITY (как уже было отмечено, иногда этот способ использовать нельзя). Как с помощью CursorAdapter-а заставить TABLEUPDATE автоматически сохранять значение в поле локального курсора: - Вручную послать запрос на сервер и изменить значение поля в обработчике AfterInsert (Пример №1), работает для ODBC и ADO. - Вручную запросить значение поля из RecordSet-a и изменить значение поля в обработчике AfterInsert, работает только для ADO и только если CursorAdapter сконфигурирован для вставки новых записей через RecordSet. - Использовать хранимую процедуру для вставки и обновлять значение поля с помощью OUTPUT параметра (аналогично примерам №2 и №4), работает для ODBC и ADO. - Использовать InsertCmdRefreshFieldList без указания InsertCmdRefreshCmd (Пример ниже), работает только для ADO и только если CursorAdapter сконфигурирован для вставки новых записей через RecordSet. - Использовать InsertCmdRefreshFieldList вместе с InsertCmdRefreshCmd (Пример №3.1 и №3.2), работает для ODBC и ADO. 2) Запросить значение SCOPE_IDENTITY(). Как с помощью CursorAdapter-а заставить TABLEUPDATE автоматически сохранять значение в поле локального курсора: - Использовать хранимую процедуру для вставки и обновлять значение поля с помощью OUTPUT параметра (Пример №2 и №4), работает для ODBC и ADO. 3) Запросить значение из записи используя альтернативный уникальный ключ записи. Как с помощью CursorAdapter-а заставить TABLEUPDATE автоматически сохранять значение в поле локального курсора: - Вручную послать запрос на сервер и изменить значение поля в обработчике AfterInsert, работает для ODBC и ADO. - Использовать хранимую процедуру для вставки и обновлять значение поля с помощью OUTPUT параметра, работает для ODBC и ADO. - Использовать InsertCmdRefreshFieldList вместе с InsertCmdRefreshKeyFieldList (Пример №3.3), работает для ODBC и ADO. - Использовать InsertCmdRefreshFieldList вместе с InsertCmdRefreshCmd, работает для ODBC и ADO. Как раз таки ЭТО КЛЮЧЕВЫЕ поля. Часто таблица имеет несколько уникальных ключей, надо просто не проглядеть это во время дизайна и создать соответствующие CONSTRAINTS. Если рассматривать таблицу с IDENTITY, то не всегда, но очень, очень часто таблица имеет альтернативный уникальный ключ. Почему? Потому что, как правило, IDENTITY поле - это служебное поле, используемое для создания связей между таблицами и не несущее никакой смысловой нагрузки. С точки зрения дизайна таблицы, можно считать, что это поле добавляется в уже готовую таблицу, которая по законам дизайна уже должна иметь уникальный ключ. После добавления IDENTITY, тот ключ никуда не исчезает, просто его "забывают", что делать очень вредно, хотя бы исходя из соображений поддержания целостности и непротиворечивости базы. Простой пример: Для таблицы Categories в Northwind создан всего один уникальный ключ по CategoryId - явный баг в дизайне. Должен быть еще уникальный ключ по CategoryName. Разве имеет смысл иметь две разные категории с одним и тем же именем? Как пользователь должен их различать, помнить номера? Проблему избыточности можно решить, анализируя GETFLDSTATE в BeforeInsert и меняя cInsertCmd параметр на вызов соответствующей процедуры. Но, по-моему, проще передавать значения для всех полей вместе с GETFLDSTATE(-1) и анализировать GETFLDSTATE(-1) уже в процедуре. Это так же позволит корректно обрабатывать незаданные поля. А чтобы зря не гонять большие объемы данных для неизмененных полей, можно передавать для них NULL. Что-то типа:
...Cmd="EXEC ..., ?IIF(GETFLDSTATE('Descr','CACategories')%2=0,CACategories.Descr,NULL), ?GETFLDSTATE(-1,'CACategories')" Алексей. Обещанный пример: ******************************** *Here is an example for ADO data source type, when Recordset is used to update data source. SET EXCLUSIVE OFF SET MULTILOCKS ON SET DELETED ON CLOSE DATABASES ALL CLEAR oCA=CREATEOBJECT("CACategoriesAdo2") IF !oCA.AutoOpen() AERROR(aerrs) DISPLAY MEMORY LIKE aerrs RETURN ENDIF WAIT WINDOW "Insert/Update/Delete records in the BROWSE" BROWSE NORMAL DEFINE CLASS CACategoriesAdo2 AS CursorAdapter Alias="CACategories" * set data source type, SELECT command and cursor schema DataSourceType="ADO" SelectCmd="select CategoryId, CategoryName, Description from categories" CursorSchema="Id I, Name C(15), Descr M" Tables ="categories" UpdatableFieldList = "Name, Descr" KeyFieldList = "Id" UpdateNameList = "Id categories.CategoryId, Name categories.CategoryName, Descr categories.Description" InsertCmdRefreshFieldList = "Id" PROCEDURE Init LOCAL oCon as ADODB.Connection, oRS as ADODB.Recordset * When object is created, create ADO connection object and connect * to the Northwind database on the local SQL Server oCon=CREATEOBJECT("ADODB.Connection") oCon.Open("Provider=SQLOLEDB.1;Integrated Security=SSPI;Initial Catalog=Northwind;Data Source=(local)") * Create Recordset object and configure it for data source update, oRs=CREATEOBJECT("ADODB.Recordset") oRS.ActiveConnection=oCon oRS.CursorLocation = 3 && adUseClient oRS.CursorType= 1 && adOpenKeyset oRS.LockType= 3 && adLockOptimistic this.DataSource=oRS ENDPROC FUNCTION AutoOpen() NODEFAULT RETURN this.CursorFill(.T.,.F.,-1) && use schema ENDFUNC * The following events are used only to print * commands that are being executed by the object PROCEDURE BeforeInsert LPARAMETERS cFldState, lForce, cInsertCmd ? ? PROGRAM() ? "cInsertCmd=",cInsertCmd ENDPROC PROCEDURE BeforeUpdate LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd ? ? PROGRAM() ? "cUpdateInsertCmd=",cUpdateInsertCmd ? "cDeleteCmd=",cDeleteCmd ENDPROC PROCEDURE BeforeDelete LPARAMETERS cFldState, lForce, cDeleteCmd ? ? PROGRAM() ? "cDeleteCmd=",cDeleteCmd ENDPROC ENDDEFINE ******************************** Исправлено: Aleksey Tsingauz [MSFT], 20.03.06 08:17 ![]() |
||
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА | |||
---|---|---|---|
WiRuc Сообщений: 1012 |
Небольшое уточнение.
CREATE procedure #CategoryUpd @id int, @OldName varchar(15), @Name varchar(15), @Descr text AS SET ANSI_NULLS OFF UPDATE Categories SET CategoryName=@Name,Description=@Descr WHERE CategoryId=@Id AND CategoryName=@OldName IF @@ROWCOUNT=0 AND @@ERROR=0 RAISERROR (' Update conflict.', 16, 1) Настройка ANSI_NULLS запоминается при создании ХП, так что строка SET ANSI_NULLS OFF просто игнорируется. Надо было сначала выдать SET ANSI_NULLS OFF через SQLEXEC, а затем создавать процедуру. А по сути вопроса, считаю, что метод 2 (через ХП) является единственно правильным. ![]() |
||
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА | |||
---|---|---|---|
Aleksey Tsingauz [MSFT] Автор Сообщений: 407 |
Поскольку вопрос продолжает быть актуальным, помещаю еще один способ извлечения IDENTITY CursrAdapter-ом с помощью SCOPE_IDENTITY(). Этот способ основан на использовании вспомогательной временной хранимой процедуры и на использовании события CursorAdapter.BeforeInsert, которое добавляет вызов процедуры в конец команды INSERT (будь то автоматически сгенерированная команда или команда явно заданная через CursorAdapter.InsertCmd). Код процедуры и события BeforeInsert абсолютно не зависят от схемы таблицы, единственное что требуется знать - имя столбца в курсоре куда сохранить значение. Думаю, что самые придирчевые критики будут удовлетворены этим подходом, можно написать один базовый класс и использовать его для любой таблицы с IDENTITY.
Пример для ODBC, но способ также должен работать для ADO: ************************************** CLOSE DATABASES all SQLDISCONNECT(0) CLEAR SET MULTILOCKS ON LOCAL nAutoRefreshConn as Integer * Change connection string to connect to any available SQL Server nAutoRefreshConn=SQLSTRINGCONNECT("DRIVER=SQL Server;SERVER=(local);Trusted_Connection=Yes;") IF (nAutoRefreshConn <1) ? "Failed to connect!" AERROR(aerrs) DISPLAY MEMORY LIKE aerrs return ENDIF TEXT TO cSQL NOSHOW CREATE TABLE #CAAutoRefreshDemo ( f_IDENTITY int NOT NULL IDENTITY PRIMARY KEY, f1 int NOT NULL UNIQUE, f_TIMESTAMP timestamp ) ENDTEXT IF SQLEXEC(nAutoRefreshConn ,cSQL)!=1 ? "Failed to create demo table!" AERROR(aerrs) DISPLAY MEMORY LIKE aerrs ENDIF LOCAL oCA as CursorAdapter oCA=CREATEOBJECT("CA_SCOPE_IDENTITY") oCA.Alias = "CATest" oCA.BufferModeOverride= 5 oCA.DataSource= nAutoRefreshConn oCA.DataSourceType="ODBC" oCA.SelectCmd="select * from #CAAutoRefreshDemo" oCA.CursorSchema="f_IDENTITY I, f1 I, f_TIMESTAMP Q(8)" oCA.UseCursorSchema= .T. oCA.Tables="#CAAutoRefreshDemo" oCA.UpdatableFieldList="f1" oCA.UpdateNameList="f1 #CAAutoRefreshDemo.f1, f_TIMESTAMP #CAAutoRefreshDemo.f_TIMESTAMP, f_IDENTITY #CAAutoRefreshDemo.f_IDENTITY" oCA.KeyFieldList="f_IDENTITY" oCA.CursorFill() oCA.IDENTITY_Field="f_IDENTITY" oCA.RefreshTimestamp= .F. && just for demo purposes will include f_TIMESTAMP into InsertCmdRefreshFieldList oCA.InsertCmdRefreshFieldList="f_TIMESTAMP " INSERT INTO CATest (f1) VALUES (100) INSERT INTO CATest (f1) VALUES (200) INSERT INTO CATest (f1) VALUES (300) INSERT INTO CATest (f1) VALUES (400) INSERT INTO CATest (f1) VALUES (500) INSERT INTO CATest (f1) VALUES (600) INSERT INTO CATest (f1) VALUES (700) INSERT INTO CATest (f1) VALUES (800) INSERT INTO CATest (f1) VALUES (900) IF !TABLEUPDATE(.T.) ? "TABLEUPDATE is failed!" AERROR(aerrs) DISPLAY MEMORY LIKE aerrs ELSE LIST ENDIF TABLEREVERT(.T.) SQLDISCONNECT(0) RETURN DEFINE CLASS CA_SCOPE_IDENTITY AS CursorAdapter IDENTITY_Field="" PROCEDURE DataSource_ASSIGN LPARAMETERS tAssign this.DataSource = tAssign TRY SQLEXEC(this.DataSource, ; "create procedure #Get_ValHelper @in Int, @out int OUTPUT " + ; "AS SET NOCOUNT ON " + ; "SELECT @out=@in") CATCH ENDTRY ENDPROC PROCEDURE BeforeInsert LPARAMETERS cFldState, lForce, cInsertCmd IF LEN(ALLTRIM(this.IDENTITY_Field))>0 cInsertCmd = cInsertCmd + ; "; DECLARE @id int; SELECT @id = SCOPE_IDENTITY()" + ; "; EXEC #Get_ValHelper @id, ?@" + ; this.Alias + "." + ALLTRIM(this.IDENTITY_Field) ENDIF ? ? PROGRAM() ? "cInsertCmd=",cInsertCmd ENDPROC ENDDEFINE ******************************** Алексей. ![]() |
||
© 2000-2021 Fox Club  |