:: Visual Foxpro, Foxpro for DOS
Как автомат-ки получать автоинкриментное поле SQL сервера в КА
A1

Сообщений: 96
Дата регистрации: 11.11.2005
Уважаемые помогите рассказав

Использую курсорадаптеры для подсоединения к SQL SErver-ским таблицам. Все просто замечательно, но чую что кое-что можно сделать лучше, а точнее сказать правальнее. Допустим на сервере есть таблица, поле ID которой автоинкриментное, при создании курсорадаптера указваю, что разрещаю изменения и ключом ставлю поле ID
И теперь сам вопрос: в программе, когда пишу TABLEUPDATE, как можно автоматически получать поле ID, сгенерированное на сервере. Сейчас получаю так - делаю рефреш КА после чего перемещаюсь на максимальное значение ID.
Ratings: 0 negative/0 positive
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
MichaelD

Сообщений: 7578
Дата регистрации: 14.05.2005
A1,

Цитата:
как можно автоматически получать поле ID, сгенерированное на сервере.
см. @@IDENTITY, IDENT_CURRENT('table_name'), SCOPE_IDENTITY() SQL Book online

Цитата:
Сейчас получаю так - делаю рефреш КА после чего перемещаюсь на максимальное значение ID
Здесь проблема в том, что неизвестно чей именно ID ты получишь... О-очень вероятно, что не этим клиентом добавленный при интенсивной работе на добавление...


------------------
С уважением,
Михаил Дроздов, Пермь, Россия
Ratings: 0 negative/0 positive
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Влад Колосов

Сообщений: 22664
Откуда: Ростов-на-Дону
Дата регистрации: 05.05.2005
При использовании VIEW надежнее всего отказаться от автоинкремента на сервере.


------------------
Совершенство - это не тогда, когда нельзя
ничего прибавить, а тогда, когда нечего убавить.
Ratings: 0 negative/0 positive
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Aleksey Tsingauz [MSFT]
Автор

Сообщений: 407
Дата регистрации: 15.06.2004
A1
Уважаемые помогите рассказав
Использую курсорадаптеры для подсоединения к SQL SErver-ским таблицам. Все просто замечательно, но чую что кое-что можно сделать лучше, а точнее сказать правальнее. Допустим на сервере есть таблица, поле ID которой автоинкриментное, при создании курсорадаптера указваю, что разрещаю изменения и ключом ставлю поле ID
И теперь сам вопрос: в программе, когда пишу TABLEUPDATE, как можно автоматически получать поле ID, сгенерированное на сервере. Сейчас получаю так - делаю рефреш КА после чего перемещаюсь на максимальное значение ID.

Здесь есть пример 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
********************************
Ratings: 0 negative/0 positive
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Владимир Максимов

Сообщений: 14100
Откуда: Москва
Дата регистрации: 02.09.2000
Извиняюсь, что поднял старую тему, но тут на 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, даже в пределах выборки.
Ratings: 0 negative/0 positive
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Aleksey Tsingauz [MSFT]
Автор

Сообщений: 407
Дата регистрации: 15.06.2004
Здравствуйте, Владимир!

Действительно вопрос достаточно серьезный и общие пояснения не помешают. Существут несколько разных способов извлечения значения 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.

Владимир Максимов
3) AutoRefresh - в таблицу вводится поле или набор полей, по значению которого также можно однозначно идентифицировать запись. Это НЕ ключевые поля. После вставки записи осуществляется поиск новой записи по значению этих вспомогательных полей
Как раз таки ЭТО КЛЮЧЕВЫЕ поля. Часто таблица имеет несколько уникальных ключей, надо просто не проглядеть это во время дизайна и создать соответствующие CONSTRAINTS.

Владимир Максимов
3) AutoRefresh - Далеко не всегда в таблице есть дублирующие поля, которые могут взять на себя роль ID, даже в пределах выборки.
Если рассматривать таблицу с IDENTITY, то не всегда, но очень, очень часто таблица имеет альтернативный уникальный ключ. Почему? Потому что, как правило, IDENTITY поле - это служебное поле, используемое для создания связей между таблицами и не несущее никакой смысловой нагрузки. С точки зрения дизайна таблицы, можно считать, что это поле добавляется в уже готовую таблицу, которая по законам дизайна уже должна иметь уникальный ключ. После добавления IDENTITY, тот ключ никуда не исчезает, просто его "забывают", что делать очень вредно, хотя бы исходя из соображений поддержания целостности и непротиворечивости базы.

Простой пример:
Для таблицы Categories в Northwind создан всего один уникальный ключ по CategoryId - явный баг в дизайне. Должен быть еще уникальный ключ по CategoryName. Разве имеет смысл иметь две разные категории с одним и тем же именем? Как пользователь должен их различать, помнить номера?

Владимир Максимов
2) и 4) через ADO - Вполне рабочее решение. Единственный недостаток - это избыточность (зачем посылать те поля, в которых ничего не ввели) и проблемы в случае, если в таблице допустимы поля со значением NULL (как отличить те поля в которые ничего не ввели от тех, в которые ввели пустое значение. Здесь подошло бы DEFAULT на поля, но как это настроить в CAD?)
Проблему избыточности можно решить, анализируя 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
********************************



Исправлено 1 раз(а). Последнее : Aleksey Tsingauz [MSFT], 20.03.06 08:17
Ratings: 0 negative/0 positive
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
WiRuc

Сообщений: 1012
Дата регистрации: 09.04.2002
Небольшое уточнение.
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 (через ХП) является единственно правильным.
Ratings: 0 negative/0 positive
Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Aleksey Tsingauz [MSFT]
Автор

Сообщений: 407
Дата регистрации: 15.06.2004
Поскольку вопрос продолжает быть актуальным, помещаю еще один способ извлечения 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
********************************

Алексей.
Ratings: 0 negative/0 positive


Извините, только зарегистрированные пользователи могут оставлять сообщения в этом форуме.

On-line: 28 (Гостей: 28)

© 2000-2024 Fox Club 
Яндекс.Метрика