![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Insert, Update, Delete своими руками [D7, MSSQL]
mvg_first © (2004-01-19 16:40:00)
Добрый день мастера и остальные.
Заранее прошу прощения за возможно-банальный вопрос, но я настолько давно не общался с сообществом Delphi-программистов что правильно впорос поставить сейчас и не смогу. Поэтому прошу отнестись с пониманием и "больно не пинать"
Суть задачи.
Я пробую разрабатывать приложение для работы с БД MSSQL2000/ Для этого использую компоненты TADQQuery. В качестве контролов использую стандартный DBGrid.
Но сложность заключается в том что я хочу все запросы на вставку, добавление и удаление записей из отображаемых таблиц - делать самостоятельно. Т.е. когда пользователь добавляет в Гриде запись и пробует ее записать - что бы каким-то образом получить доступ к изменившимся полям выполнить операцию по вставке и если не случилось ошибки дать пользователю возможность работать с программой дальше, а в случае ошибки отменить вставку строки (вомзожно с выводом сообщения).
Компонет реализующий очень похожую функциональность в Делфи есть это TUpdateSQL - но он работает только с BDE версиями компонентов доступа к данным. Я же хочу использовать технологию ADO.
Пожалуйста порекомендуйте литературу (интеренет ресурсы), компоненты, образцы исходного кода - проработав которые я смогу самостоятельно разобраться и решить поставленную задачу.
Если к кому нибудь из Вас можно обратится посредством ICQ буду очень признателен (мой ICQ 84705448)
mvg_first (2004-01-29 14:45:00) [1]
lmatveev (29.01.04 14:36) [63]
Блин - у меня такой же код (ну почти такой же) единственное что не так я при инсерте пробую получить IDENTITY используюя функцию MSSQL SCOPE_IDENTITY() и позиционирую запись именно на этом идентификаторе.
Сходу получается ограничение на использование такого компонента в том что таблица должна иметь обязательный превичный ключи (автоинкремент). Хотя это и не сильно уж трудно и тяжело (почит все таблицы в стандартных прикладных задачах его имеют)
sniknik (2004-01-29 14:41:00) [2]
mvg_first © (29.01.04 14:39) [64]
описка скорее всего
ClientDataSet1: TClientDataSet;
> И по ходу вопрос где можно посмотреть примерчик работы трехзвенки с таким вот классом
в дельфях пример есть вроде
sniknik (2004-01-29 14:39:00) [3]
Polevi © (29.01.04 14:25) [62]
> ну вот, я же говорил !
> :-)
а я что против? я изначально ничего про методы трехзвенки не упоминал. (можеш пересмотреть мои посты, там не присвоение рекодсета или клонирование а именно отсоединение)
если чегото нужно то делать надо так как работает (чего и расписывал), а если понимать все по своему и под себя переделывать то и удивлятся не надо почему не работает.
mvg_first (2004-01-29 14:39:00) [4]
Извините за возможно глупый вопрос - но что такое CClientDataSet - все классы в дельфине насколько я знаю обычно начинаются с "T". А с "C" начинаются классы писанные на Visual С++. И по ходу вопрос где можно посмотреть примерчик работы трехзвенки с таким вот классом
lmatveev (2004-01-29 14:36:00) [5]
Вот так это работает:
TAEMADOQuery = class(TADOQuery)
private
FKeyField: string;
FInsertSQL: TStrings;
FUpdateSQL: TStrings;
FDeleteSQL: TStrings;
FKeyValue: variant;
function GetKeyFieldValue: variant;
function DoExecSQL(aSQL: string): variant;
procedure SetInsertSQL(Value: TStrings);
procedure SetUpdateSQL(Value: TStrings);
procedure SetDeleteSQL(Value: TStrings);
protected
procedure DoAfterOpen; override;
procedure DoBeforeClose; override;
procedure InternalPost; override;
procedure InternalDelete; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure RefreshDataSet; // Делает DisableControls, затем переоткрывается и делает EnableControls
property KeyFieldValue: variant read GetKeyFieldValue;
published
property SQLInsert: TStrings read FInsertSQL write SetInsertSQL; // Пользовательская команда на Insert. Если не указана - выполняется стандартная
property SQLUpdate: TStrings read FUpdateSQL write SetUpdateSQL; // Пользовательская команда на Insert. Если не указана - выполняется стандартная
property SQLDelete: TStrings read FDeleteSQL write SetDeleteSQL; // Пользовательская команда на Insert. Если не указана - выполняется стандартная
property KeyField: string read FKeyField write FKeyField; // Название ключевого поля. Если указано, то после открытия позиционируется на значение, которое было до закрытия
end;
implementation
constructor TAEMADOQuery.Create;
begin
inherited Create(AOwner);
FInsertSQL := TStringList.Create;
FUpdateSQL := TStringList.Create;
FDeleteSQL := TStringList.Create;
end;
destructor TAEMADOQuery.Destroy;
begin
FInsertSQL.Free;
FUpdateSQL.Free;
FDeleteSQL.Free;
inherited Destroy;
end;
function TAEMADOQuery.GetKeyFieldValue: variant;
begin
if Active and (FindField(KeyField) <> nil) then Result := FieldByName(KeyField).Value else Result := null;
end;
procedure TAEMADOQuery.InternalPost;
var
id: variant;
i: integer;
begin
id := null;
if ((State = dsEdit) and (Trim(FUpdateSQL.Text) = "")) or
((State = dsInsert) and (Trim(FInsertSQL.Text) = "")) then
inherited InternalPost
else begin
for I := 0 to Fields.Count - 1 do
with Fields[I] do
if Required and not ReadOnly and (FieldKind = fkData) and IsNull then
begin
FocusControl;
DatabaseErrorFmt(SFieldRequired, [DisplayName]);
end;
if State = dsEdit then
id := DoExecSQL(FUpdateSQL.Text)
else
id := DoExecSQL(FInsertSQL.Text);
if (KeyField <> "") and not(VarIsEmpty(id) or VarIsNull(id)) then
Locate(KeyField, id, []);
end;
end;
procedure TAEMADOQuery.InternalDelete;
var
id: variant;
begin
if (Trim(FDeleteSQL.Text) = "") then
inherited InternalDelete
else begin
id := DoExecSQL(FDeleteSQL.Text);
if (KeyField <> "") and not(VarIsEmpty(id) or VarIsNull(id)) then
Locate(KeyField, id, []);
end;
end;
function TAEMADOQuery.DoExecSQL;
var
FCommand: TADOCommand;
i: integer;
begin
Result := null;
FCommand := TADOCommand.Create(Self);
try
if ConnectionString <> "" then FCommand.ConnectionString := ConnectionString
else FCommand.Connection := Connection;
FCommand.CommandText := aSQL;
for i := 0 to FCommand.Parameters.Count-1 do
if FindField(FCommand.Parameters[i].Name) <> nil then begin
FCommand.Parameters[i].Assign(FieldByName(FCommand.Parameters[i].Name));
if FieldByName(FCommand.Parameters[i].Name).IsNull then
FCommand.Parameters[i].Value := null;
if FCommand.Parameters[i].Name = KeyField then FCommand.Parameters[i].Direction := pdInputOutput;
end
FCommand.Execute;
if FCommand.Parameters.FindParam(KeyField) <> nil then
Result := FCommand.Parameters.ParamByName(KeyField).Value;
finally
FCommand.Free;
end;
RefreshDataSet;
end;
procedure TAEMADOQuery.RefreshDataSet;
begin
if not Active then exit;
DisableControls;
try
Close;
Open;
finally
EnableControls;
end;
end;
procedure TAEMADOQuery.SetInsertSQL;
begin
FInsertSQL.Assign(Value);
end;
procedure TAEMADOQuery.SetUpdateSQL;
begin
FUpdateSQL.Assign(Value);
end;
procedure TAEMADOQuery.SetdeleteSQL;
begin
FDeleteSQL.Assign(Value);
end;
procedure TAEMADOQuery.DoBeforeClose;
var
Field: TField;
begin
inherited DoBeforeClose;
Field := FindField(KeyField);
if Field <> nil then FKeyValue := Field.Value
else FKeyValue := Unassigned;
end;
procedure TAEMADOQuery.DoAfterOpen;
var
Field: TField;
begin
Field := FindField(KeyField);
if (Field <> nil) and not(VarIsEmpty(FKeyValue)) then begin
if not(VarIsNull(FKeyValue)) then
try
Locate(Field.FieldName, FKeyValue, [])
except
end
else
if Field.ReadOnly then Last;
end;
inherited DoAfterOpen;
end;
... и дальше забываем, что это не обычный DataSet. При вставке новый ID возвращаем output параметром. Заодно этот компоент после переоткрытия позиционируется на запись, которая была активна перед закрытием :))
Можно также расставить флагов и чуть-чуть модифицировать, чтобы снизить накладные расходы при переоткрывании
Polevi (2004-01-29 14:25:00) [6]
ну вот, я же говорил !
:-)
sniknik (2004-01-29 14:19:00) [7]
> на клиенте Connection не определен вообще за ненадобностью, как на форму бросили его так там нил с тех пор и лежит :)
это понятно, но разница то есть, может она в методе отсоеденения от базы (которая у тебя впрочем не выполнится
там код
procedure TCustomADODataSet.SetConnection(const Value: TADOConnection);
begin
if Connection <> Value then
begin
.....
)
даже скорее всего именно изза этого, вот переделал немного, перестало работать!!! (в смысле редактироватся ID)
ADODataSet2.Clone(ADODataSet1);
ADODataSet2.Sort:= "AUTOID";
ADODataSet2.FieldByName("AUTOID").ReadOnly:= false;
ADODataSet2.Edit;
ADODataSet2.FieldByName("AUTOID").AsInteger:= 201; //первую запись на последнее место
ADODataSet2.Post;
DataSource1.DataSet:= ADODataSet2;
ADODataSet2 также просто бросил на форму, без подключений и т.д.
Polevi (2004-01-29 14:06:00) [8]
>sniknik © (29.01.04 13:16) [59]
>а если явно
>MyAdoDataset.Connection:= nil; прописать?
на клиенте Connection не определен вообще за ненадобностью, как на форму бросили его так там нил с тех пор и лежит :)
sniknik (2004-01-29 13:16:00) [9]
отперестановки мест слагеемых сумма не меняется... ;о)
Polevi © (29.01.04 12:59) [57]
а если явно
MyAdoDataset.Connection:= nil; прописать?
или уже ломает менять чегонибудь? CLientDataset тоже неплохо :о), жаль только *.cds сохраненный из ADODataSet не открывает. :(
sniknik (2004-01-29 13:12:00) [10]
вот мое проверочное, без трехзвенки
ADODataSet1.Open;
ADODataSet1.Connection:= nil;
ADODataSet1.Sort:= "AUTOID";
ADODataSet1.FieldByName("AUTOID").ReadOnly:= false;
ADODataSet1.Edit;
ADODataSet1.FieldByName("AUTOID").AsInteger:= 201; //первую запись на последнее место
ADODataSet1.Post;
AUTOID создан с AUTOID INT IDENTITY (1,1)
и после прям в базе назначен ключом.
не знаю, все вроде похоже может только
MyAdoDataset.FieldByName("AutoIncField").ReadOnly:=false;
MyAdoDataset.Insert;
???
треззвенка вроде бы влиять не должна.
Polevi (2004-01-29 12:59:00) [11]
>sniknik © (29.01.04 12:27) [54]
у меня 3-звенка
на среднем звене делается выборка, курсор локальный, рекордсет отсоединятеся и возращается клиенту как результат вызова фасадной ф-ии
на клиенте это выглядит как
MyAdoDataset.RecordSet:=Facade.SelectSomeData;
и после этого
MyAdoDataset.Insert;
MyAdoDataset.FieldByName("AutoIncField").ReadOnly:=false;
MyAdoDataset.FieldByName("AutoIncField").AsInteger:=666;
MyAdoDataset.Post;
не работало, может тупил конечно, но вопрос я изучал конкретно, так что ???
в результате перешел на CLientDataset и не жалею
sniknik (2004-01-29 12:57:00) [12]
в общем высылаю обоим, если у вас не работает то значит адо версия виновата. (хз. у меня вроде не последняя версия, что там в SP4 для w2k. стоит, после ного обновлений не качал)
там переделано, надо открыть и кнопку нажать, увидите какую ;о)), после можно и в гриде автонкремент править и так (первая запись сразу должна последней стать, меняется и индекс по нему).
зы... а ящика то я не нашол, ну чтож после.
mvg_first (2004-01-29 12:32:00) [13]
Может у нас весрии ADO разные?
Все то что ты писал - я делал на стандартном ADQQuery, Delphi7
sniknik (2004-01-29 12:27:00) [14]
Polevi © (29.01.04 12:12) [53]
странно. у меня и для MSSQL также работает. пример могу прислать. (???) хотя чего там, 3 строчки всего, но все одно чтото значит не так.
курсор локальный?
данные остались посде отсоеденения?
в гриде меняются/добавляются?
Polevi (2004-01-29 12:12:00) [15]
>sniknik © (29.01.04 12:03) [52]
у меня не получалось в отсоединенном рекордсете присвоить значение AutoInc полю, ReadOnly:=false не помогало, а подменить метаданные я не нашел способа, кроме парсинга XML
sniknik (2004-01-29 12:03:00) [16]
> Именно так неработает - поле в таблице (MSSQL2000) описано как IDENTITY, и именно снимал флаг только для чтения и именно проставлял
> значение - нихрена, хотя другие поля по такому же принципу в гриде обновляются.
надеюсь говорим про тот ADOQuery1 у которого коннекшн отключен, и про изменения в рекордсете а не в базе->таблице.
счас проверю, я так делал для аксесной базы у которой тоже ID IDENTITY(1,1), может MSSQL чтото еще в настройку передает.
> Вопрос решил
поздравляю. ;о)) (но проверю все одно, просто для себя)
mvg_first (2004-01-29 11:48:00) [17]
sniknik © (29.01.04 10:20) [50]
Именно так неработает - поле в таблице (MSSQL2000) описано как IDENTITY, и именно снимал флаг только для чтения и именно проставлял значение - нихрена, хотя другие поля по такому же принципу в гриде обновляются.
Вопрос решил
Все таки написал свой компонент потомок TCustomADODataset и в нем вызывал Requery после чего позиционировался на записи по ключевому полю (ID) используюя LOCATE
sniknik (2004-01-29 10:20:00) [18]
mvg_first © (29.01.04 09:17) [44]
> При этом записи добавляются конечно, но в гриде поле ID не обновляется.
как это? должно.
вот так сделай
ADOQuery1.FieldByName("Id").ReadOnly :=false;
ADOQuery1.FieldByName("Id").Value := 30;
обновилось?
даже если оно автоинкрементное, оно такое в базе/таблице а тут ты уже с рекордсетом работаеш. и т.д. ...
извини, на остальное реагировать не буду, больно много у тебя "нестыковок" в связках желания-знания. опровергать/доказывать все лень.
но вопрос похоже решон? все что изначально описано выполнилось. другое дело хотел ты оказывается не того.
Polevi (2004-01-29 10:07:00) [19]
>mvg_first
ну и каша у тебя в голове :)
DenK_vrtz (2004-01-29 10:03:00) [20]
>ПОсле этого при попытка добавить в гриде запись добавление происходило но в гриде оставались значения занесенные туда пользователем, а те данные которые реально попадают в таблицу с использованием INSTEAD OF триггера - неотображались - пока не переоткроешь таблицу :(
а ты чуда ждал? :)
а refresh, если сделать?