LCConnection

VladSh

начинающий
Lotus team
11.12.2009
1 260
5
#1
LCConnection - родное Lotus'овское API для работы с реляционными базами данных (РДБ).
По времени освоения ADO/ODBC и LCConnection одинаковы, потому добрый совет - не связывайтесь с ADO и ODBC, которые заточены чисто под винду, просто не тратьте время понапрасну.

Старая тема: Использование, какие есть мнения?

LCConnection'у всё равно куда писать, в db2, Oracle и т.д., т.к. это всё базы данных.
Для знающих Java конечно же лучше уже разобраться с JDBC. Но преимущество LCConnection в том, что не нужно мудохаться подбирая драйвера под "свою" реляционку. Указываете тип РБД при открытии соединения, а он автоматом использует нужный драйвер, работающий именно с этой РДБ.

Для использования этого механизма в LS-коде необходимо в разделе Options прописать строку:
Код:
Uselsx "*lsxlc"
По DECS'у немного (чтобы не размазывалось по форуму):
VladSh сказал(а):
Nikitoss сказал(а):
Надо посмотреть что за зверь такой DECS)
Оно использует тот же механизм LCConnection, но всё делается настройками в отдельной БД, созданной по определённому шаблону (он есть на сервере). Для простой логики переноса со строгим соответствием {Lotus_документ} -> {RDB_запись} и {Lotus_поле} -> {RDB_поле} этого достаточно, для более сложной - программинг с LCConnection.

Достоинства DECS'а (это задача на сервере) в том, что он может отслеживать изменения (добавление/сохранение/удаление) доков по определённой форме и автоматом переносить их в реляционку, т.е. не надо встраивать никакого кода на события форм.
Добавляйте, уточняйте (лучше в личку, я здесь выложу).

<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">С чего начиналась данная тема...</div></div><div class="sp-body"><div class="sp-content">Вопрос по LSX. Подключаюсь к БД Oracle и нужно в 2 разные таблицы сделать по записи.
Так вот, нужно чтобы гарантированно записать эти 2 записи, а не одну (к примеру, процесс оборвался).

Алгоритм примерно должен быть такой (правильно ли я понимаю?):

'Указываю 1-ю таблицу, в которую собираюсь писать:
LCConnection.metadata = "TABLE1"

'Создаю запись, заполняю поля...

'Указываю 2-ю таблицу, в которую собираюсь писать:
LCConnection.metadata = "TABLE2"

'Создаю запись, заполняю поля...

Есть несколько примеров, в них используется:
Call LCConnection.Call(RecordFieldList, 1, Nothing)
или
Call LCConnection.Insert(RecordFieldList)
Не совсем понятно, какой комадной всё-таки надо производить запись.. в чём тонкость?

Т.к. у меня 2 записи, то нужен:
Call LCConnection.Action(LCACTION_COMMIT)
но непонятно, как отключить автоматический Commit при записи данных.

Буду рад любой помощи!
 

VladSh

начинающий
Lotus team
11.12.2009
1 260
5
#2
Нашёл в редбуке Implementing IBM Lotus Enterprise Integrator 6 (sq246067.pdf) такую главу:
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">9.3.9 Control Commit and Rollback</div></div><div class="sp-body"><div class="sp-content">You may have applications that require that you update multiple records at a time and
complete all the updates before you commit any of them. This may especially be the case
when there’s data in multiple tables and you need to commit your changes only once the data
in all the tables are in a consistent state.

The LCConnection class contains properties that let you control when commits happen. The
LCConnection.CommitFrequency property can be used to control how frequently commits
happen in that connection — a value of zero makes the connector not commit until you
explicitly tell it to using the LCConnection.Action method.

Normally, if your script is accessing data in multiple tables (or views) of the same database,
you might create a separate LCConnection object for each table. This saves you having to
change the Metadata property as you switch among the tables. However, if you use more
than one LCConnection to do updates, then there’s not a way to guarantee commit of all the
updates. If it’s critical that commits on multiple tables happen simultaneously (or that you be
able to roll them all back), then you must use a single LCConnection object for all your
operations, and use the CommitFrequency property and Action method as described above
to control when commits happen.

Example 9-1 Insert records into two tables and commit them simultaneously
Код:
Function InsertDogData(con As LCConnection, dogRec As LCFieldList, awardRecs As LCFieldList, awardCount As Long) As Integer
On Error Goto HandleError

con.CommitFrequency = 0 ‘ only commit when we say to commit.
con.Metadata = ”DOGS”
con.Insert dogRec
con.Metadata = ”DOGSHOWAWARDS”
con.Insert awardRecs, 1, awardCount
con.Action LCACTION_COMMIT ‘ commit changes to both tables.
InsertDogData = 1 ‘ code for success
Exit Function

HandleError:
‘ In case of error, roll back any changes already made.
con.Action LCACTION_ROLLBACK
InsertDogData = 0 ‘ code for failure
Exit Function
End Function
Добавлено:

До того, как нашёл этот redbook, в Lotus Connectors and Connectivity Guide (help\lccon.nsf) в документе Fields in the LEI Connection Document нашёл переключатель Commit Options, содержащий следующие значения:
- Commit At Disconnect;
- Commit Every N Operations;
- Commit Every Operation.

Описание Commit At Disconnect: Enable this option to commit changes to the database as the database is being disconnected at the end of the operation. This is the default Commit option.

Подумал, что по аналогии так и должно быть, т.е. началом транзакции можно считать вызов метода LCConnection.Connect(), а окончанием транзакции - LCConnection.Disconnect(). Оказалось, что всё так и есть, - записи в таблице сохраняются после вызова метода Disconnect. В пределах одной таблицы это работает на ура и без вызовов метода LCConnection.Action(LCACTION_...).
Если нужно гарантированное одновременное внесение изменений в разные таблицы, то используем алгоритм, описанный в редбуке.

N.B.: В мануалах встречал в коде, обрабатывающем ошибки, вызов действия LCACTION_TRUNCATE. После его использования очистилась вся текущая таблица, т.е. все данные, которые там были ранее - до моей записи! Использовать, когда создал таблицу, начал писать данные, но возникла ошибка и нужно быстро "обнулить" данные.

Правильный код обработчика ошибок:
Код:
If LCConnection.IsConnected Then 
Call LCConnection.Action(LCACTION_ROLLBACK)
Call LCConnection.Action(LCACTION_CLEAR)
End If
 

VladSh

начинающий
Lotus team
11.12.2009
1 260
5
#3
Продолжаю писать сюда эссе "по чужим граблям")))

Ещё одна проблема была... при записи вот таким кодом:
Код:
Set LCRecord = New LCFieldList
Set LCField = LCRecord.Append(fieldName, dataType)
'...
'а затем:
Call LCConnection.Insert(LCRecord)
вылетала ошибка 12546: "Field type mismatch: 'FIELDNAME' Source Connector field is type Integer, Target Connector field is type Text, Connector".

Причина: такое происходит, когда в записи есть, к примеру 4 поля, а я, методом LCConnection.Insert(LCFieldList), записываю 1-е, 2-е и 4-е. Хотя обращаюсь по имени поля, но почему-то происходит "сдвиг" поля в записи с 4-го на 3-е (пропущенное).

Workaround: если в записи есть 6 полей, но мне в данный момент нужно записывать 1-е, 2-е и 4-е поля, то я должен записать, как минимум, все поля с 1-го по 4-е.

Решение (из того же монструозного редбука):
9.3.5 Insert a new record
...
As regards mapping the fields in the fieldlist to the fields in the table, there are two modes of operation.
Use LCConnection.MapByName = False (the default) to insert the first field in the fieldlist into column 1, the second field into column 2, and so on.
Use LCConnection.MapByName = True to match the fields based on their fieldnames. Mapping by name is slightly less efficient, but it continues to work if the table definition changes.
Кстати, это свойство не описано в документации и не видно у объекта в отладчике ;)
 

VladSh

начинающий
Lotus team
11.12.2009
1 260
5
#4
Метод Fetch, в отличие от простого перебора, как написано в хэлпе, ещё и вытягивает данные в LCFieldList.
Переменная типа LCFieldList при
Код:
Dim LCRecord As New LCFieldList
тупо пуста, до тех пор, пока не сделаешь Fetch.
Т.о. с "рекордом" (переменной типа LCFieldList) можно работать только ПОСЛЕ Fetch, ДО - бесполезно.
 

Xalet

Well-known member
08.08.2008
410
0
#5
Я немного не по теме. Никак не нахожу, как через коннектор функцию запустить Оракл. Скажем с процедурами нет проблем, например:

Код:
con.Procedure="SOME_PROC"

con.Fieldnames="status, status_msg"


Set Parm=Parms.Append("some_parm", LCTYPE_TEXT)
Parm.text=FormatNumber("some_text","")


Call Con.Call (Parms, 1, Result)

Set fld = Result.Lookup ("status")
Set fld2 = Result.Lookup ("status_msg")
If (Con.Fetch (Result) > 0) Then
status$ = fld.value(0)
msg$ = fld2.value(0)
End If
А вот с функциями пока не понял, как можно. И можно ли вообще.
 

VladSh

начинающий
Lotus team
11.12.2009
1 260
5
#6
Никак не нахожу, как через коннектор функцию запустить Оракл.
Если запускаешь процедуру, то Оракл значит уже запущен.
Не понял вопроса.

А вот с функциями пока не понял, как можно. И можно ли вообще.
Кусок, выпиленный из класса:
Код:
%REM
Function ExecuteProcedure
Description: выполнение хранимой процедуры с обработкой ошибок
%END REM
Private Function ExecuteProcedure(sqlQuery As String, LCRecord As LCFieldList) As Boolean
Set LCRecord = New LCFieldList

bResult = Me.m_LCConnector.LCC.Execute(sqlQuery, LCRecord)
If bResult Then
Me.ExecuteProcedure = True

'Else
'	Call RTE.AddScalar("sqlQuery", sqlQuery)
'	Call RTE.WriteError("Ошибка выполнения хранимой процедуры: неверный запрос", 1111, 0, False)
End If
End Function
закоменчено потому, что при bResult = True не всегда всё хорошо. Например, у меня был такой момент, что синтаксис LND посчитал правильным, а Оракл в LCRecord вернул пустоту, только потому, что в конце выражения не стояла точка с запятой. Ну и ещё подобные странности бывают (ORA-Error, например, генерится)...

В LCRecord возвращаются записи, т.е. грубо говоря это можно считать функцией :lovecodeby:
Чтобы точно проверить, правильно ли всё выполнилось, надо чтобы программист-ораклоид так написал хранимую процедуру, чтобы она всегда возвращала LCRecord, в который добавлял какое-нибудь поле с условным именем, в которое писать результат (1 - значит True, 0 - False), а на выходе уже его анализировать.
 

Xalet

Well-known member
08.08.2008
410
0
#7
Если запускаешь процедуру, то Оракл значит уже запущен.
Не понял вопроса.
Похоже плохо вопрос сформулировал.

С Oracle все хорошо и все обрабатывается. В Oracle есть процедуры и функции:

Код:
procedure some_proc
as
parm number;
begin
some_code;
end;

FUNCTION				"some_func" 
( parm		  IN NUMBER )
RETURN NUMBER
IS
BEGIN
some_code;
RETURN some_result;
END;
С процедурами работать через коннектор очень удобно(см код выше). Есть ли что-то подобное и для функций(я не нашел)? Конечно написать кусок SQL и запустить через Execute всегда можно, но это не так удобно.
 

VladSh

начинающий
Lotus team
11.12.2009
1 260
5
#8
А какой смысл вызова оракловых функций из LND?
Оракловые функции можно вызывать из хранимых процедур, а хранимые процедуры вызывать из LND, получая результат в LCFieldList, что почти тоже самое, что вызов функции.
Смысл вызова хранимых процедур: это или выполнение каких-то действий без получения результата либо получение результата в LCFieldList.

Я не видел в документации, чтобы что-то было для функций, везде речь идёт о 'хранимых процедурах'. Могу предположить, что можно вызвать с помощью Execute и функцию, но, думаю, что результат LCFieldList будет пустой, или ошибка вылетит. Но о5 же, не вижу в этом смысла.
 

Xalet

Well-known member
08.08.2008
410
0
#9
Оракловые функции можно вызывать из хранимых процедур, а хранимые процедуры вызывать из LND, получая результат в LCFieldList, что почти тоже самое, что вызов функции.
Да. Именно так и сделал. Просто функции были в Оракле готовы, а процедур не было.

Я не видел в документации, чтобы что-то было для функций, везде речь идёт о 'хранимых процедурах'.
Вот и я поискал и ничего не нашел. Собственно и спрашивал чтобы узнать, плохо искал или на самом деле нет.

Спасибо за ответы.
 

Cleric-Lviv

Lotus team
03.01.2008
600
0
#10
VladSh

скажите пожалуста, пробовали ви перед тем как записать дание в таблицу обнулить ее???

сначала
DELETE * FROM table_name
потом
Call connection.Insert(LCRecord)

у мну не получается удалить все записи из таблици.....
 

VladSh

начинающий
Lotus team
11.12.2009
1 260
5
#13
Давно хотел что-то поконкретнее выложить, чтобы люди не мучились с виндо-пердуляторами.. да и смешно бывает, когда некто любит туману напустить, типа супер-пупер зашифрованые мега-специалисты))
Вот, выдалось время, и нате - примеры подключения, записи, вызова хранимых процедур, чтения:

<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Функция создания подключения</div></div><div class="sp-body"><div class="sp-content">
Код:
%REM
Function LCConnect
Description: Подключение к реляционной базе данных (основная функция)
%END REM
Function LCConnect(sDbType As String, sHost As String, sLogin As String, sPassword As String) As LCConnection
Dim LCC As LCConnection
On Error Goto ErrH

Set LCC = New LCConnection(sDbType)		'передаётся тип БД

If Not LCC Is Nothing Then
Select Case LCase(sDbType)		'Иначе на Database вылетает ошибка "Method does not exist"!
Case "db2":
LCC.Database = sHost
Case Else
LCC.Server = sHost
End Select
LCC.UserId = sLogin
LCC.Password = sPassword
Call LCC.Connect()

If LCC.IsConnected Then Set LCConnect = LCC
End If

Exit Function

ErrH:
'пишем в лог ошибку...
Exit Function
End Function
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Класс LSXLCRdbWriter</div></div><div class="sp-body"><div class="sp-content">
Код:
%REM
Class LSXLCRdbWriter
Description: Настраиваемая запись данных в РДБ с помощью LSXLC
%END REM
Class LSXLCRdbWriter
Private LCC As LCConnection
Private LCRecord As LCFieldList
Private LCErr As Integer

Sub New(LCC As LCConnection)
Set Me.LCC = LCC.Copy()
Me.LCC.CommitFrequency = 0		'only commit when we say to Commit
End Sub


%REM
Property Set SetTable
Description: Устанавливает текущее имя таблицы для записи 
%END REM
Property Set SetMetadata
Me.LCC.metadata = SetMetadata
End Property


%REM
Property Set MapByName
Description: Устанавливает соединению свойство записи данных по наименованиям полей, а не по порядку, как установлено по умолчанию 
%END REM
Property Set MapByName As Boolean
Me.LCC.MapByName = MapByName	'so that we don’t have to give a value for every field!
End Property


%REM
Sub CreateRecord
Description: Создание пустого объекта новой записи
%END REM
Public Sub RecordCreate()
Set Me.LCRecord = New LCFieldList
End Sub


%REM
Function Run
Description: Сохранение текущей записи в текущей таблице
%END REM
Public Function RecordComplete() As boolean
On Error GoTo ErrH

If Me.LCErr = 0 Then
Call Me.LCC.Insert(Me.LCRecord)
Me.RecordComplete = True
End If

Exit Function

ErrH:
Call Me.SaveError(Error$, Err, Erl)
Exit Function
End Function


%REM
Sub AppendFieldValue
Description: Добавляет объект поля записывает его значение в текущую запись
%END REM
Public Sub AppendFieldValue(RDB_FieldName As String, LCFieldType As Long, Value As Variant)
Dim LCField As LCField
On Error GoTo ErrH

Set LCField = Me.LCRecord.Append(RDB_FieldName, LCFieldType)
LCField.Value = Value
Exit Sub

ErrH:
Me.LCErr = Err
Call Me.SaveError(Error$, Err, Erl)
Exit Sub
End Sub


%REM
Sub ConnectionClose
Description: Завершает работу с RDB, подтверждая внесённые изменения
%END REM
Public Sub ConnectionClose()
Call Me.LCC.Action(LCACTION_COMMIT)	'commit changes to all tables
Call Me.LCC.Disconnect()
End Sub


%REM
Sub SaveError
Description: Отмена всех действий по записи в RDB, сохранение информации об ошибке
%END REM
Private Sub SaveError(pError As String, pErr As Long, pErl As Integer)
Call Me.LCC.Action(LCACTION_ROLLBACK)
Call Me.LCC.Action(LCACTION_CLEAR)
Call Me.LCC.Disconnect()

'пишем в лог ошибку...
End Sub

End Class
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Пример записи в 2 таблицы с общим Commit'ом (тоже выпилено из ещё одного своего класса, так что звыняйтэ)</div></div><div class="sp-body"><div class="sp-content">
Код:
Dim oLccW As New LSXLCRdbWriter(Me.m_LCConnector.LCC)

'Запись данных (в 1-ю таблицу)
oLccW.SetMetadata = "TABLE_DATA"
For i% = 0 To UBound(ArrContent)
Call oLccW.RecordCreate()
'пишите что хотите...
'Call oLccW.AppendFieldValue("ESSENCE_NAME", LCTYPE_TEXT, Me.m_ndSource.GetItemValue("Form")(0))
'Call oLccW.AppendFieldValue("NUMBER_REC", LCTYPE_INT, i%)
'Call oLccW.AppendFieldValue("DATE_TIME", LCTYPE_DATETIME, Now)
'Call oLccW.AppendFieldValue("VALUE", LCTYPE_TEXT, ArrContent(i%))
If Not oLccW.RecordComplete() Then Exit Function
Next

'Запись ссылок на данные (во 2-ю таблицу)
oLccW.SetMetadata = "TABLE_LINKS"
Call oLccW.RecordCreate()
'также пишите что хотите...
'Call oLccW.AppendFieldValue("GUID", LCTYPE_TEXT, "...")
'Call oLccW.AppendFieldValue("DATE_TIME", LCTYPE_DATETIME, Now)
If Not oLccW.RecordComplete() Then Exit Function

Call oLccW.ConnectionClose()
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Кусок класса ReqLCProcedureBase</div></div><div class="sp-body"><div class="sp-content">
Код:
%REM
Function ExecuteProcedure
Description: выполнение хранимой процедуры с обработкой ошибок
%END REM
Private Function ExecuteProcedure(sqlQuery As String, LCRecord As LCFieldList) As Boolean
Set LCRecord = New LCFieldList

bResult = Me.m_LCConnector.LCC.Execute(sqlQuery, LCRecord)
'тут надо самим определять, ошибочный вызов или нет (выше об этом писал...)
If bResult Then
Me.ExecuteProcedure = True
Else
'пишем в лог ошибку...
'Call RTE.AddScalar("sqlQuery", sqlQuery)
'Call RTE.WriteError("Ошибка выполнения хранимой процедуры: неверный запрос", ERRc1111, 0, False)
End If
End Function


%REM
Function GetFieldValue
Description:	выполняет хранимую процедуру и возвращает значение поля 1-й записи
NB:	- результат проверять IsEmpty
- если нужно чтобы возвратило массив значений, то в FieldIndex передаём -1
%END REM
Private Function GetFieldValue(sqlQuery As String, FieldName As String, FieldIndex As Integer) As Variant
Dim LCRecord As LCFieldList
Dim LCField As LCField

If Me.ExecuteProcedure(sqlQuery, LCRecord) Then

While Me.m_LCConnector.LCC.Fetch(LCRecord)	'Fetch вытягивает данные в LCRecord
Set LCField = LCRecord.Lookup(FieldName)	'получаем поле по имени
If Not LCField Is Nothing Then
If FieldIndex >= 0 Then
Me.GetFieldValue = LCField.Value(FieldIndex)
Else
Me.GetFieldValue = LCField.Value()
End If
Else
'сами записывайте ошибку как хотите...
'Call RTE.AddScalar("sqlQuery", sqlQuery)
'Call RTE.AddScalar("FieldName", FieldName)
'Call RTE.AddScalar("FieldIndex", FieldIndex)
'Call RTE.WriteError("Ошибка выполнения хранимой процедуры: в возвращаемой записи отсутствует необходимое поле", 0, 0, False)
End If
Wend

End If
End Function
Добавлено:
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Дискуссия по ошибке "Assignment of a NULL value to a NOT NULL column"</div></div><div class="sp-body"><div class="sp-content">
Nikitoss сказал(а):
...на строке "Call Connection.Insert(LCRecord)" возникает ошибка. Сообщение:
<Error 12325: Error [IBM][CLI Drivers][DB/NT] SQL0407N Assignment of a NULL value to a NOT NULL column "TBSPACEID=3, TABLEID=6, COLNO=0" is on line 36>
nvy сказал(а):
...означает, что Вы пытаетесь записать значение Null в столбец, который этого не позволяет. Скорее всего не указали значение для этого столбца и у него отсутствует значение по умолчанию.
Драйвер JDBC в такой ситуации вернул бы аналогичную ошибку.
Nikitoss сказал(а):
Я поменял в свойствах столбцов:
1. Default Value (этого оказалось недостаточно, и та же ошибка появлялась и дальше);
2. Nullable с "No" на "Yes" (теперь позволяет записывать значение Null).
Спасибо, ребята!
 
N

Nikitoss

#14
Какие ещё причины ошибки "№217 Error creating product project" могут быть?
Кроме не не подключения Uselsx "*lsxlc" конечно=)

Dim LCC As LCConnection
Set LCC = New LCConnection("SQL")
 

Gandliar

Lotus team
16.02.2004
341
8
#16
А можно ли подключиться через lcconnection к mySql

что для этого необходимо?
 

Serduko

Well-known member
11.10.2011
170
0
#20
При подключении к MSSQL, при доменной авторизации, не коннектит и пишет ошибку "недопустимая спецификация авторизации". Что делать?

Нашел:

Код:
LCC.Auth_Integrated = "SSPI"