Tình huống phát sinh khi sử dụng kbmMWClientQuery với View: Giả sử có bảng vật lý t1, tạo View v1 từ t1, thực hiện truy vấn: ClientQuery.Query.Text:='Select f1,f2 from v1' Sau khi thao tác thêm/xóa/sửa bản ghi, gọi: ClientQuery.Resolve() để lưu thay đổi.
Lỗi xảy ra khi đặt: ClientQuery.TableName='v1' → Tạo SQL trống Insert into v1 ( ) values ( ) Trong khi ClientQuery.TableName='t1' thì hoạt động bình thường
Giải pháp: Cập nhật thuộc tính Origin của Field tại thời gian chạy: ClientQuery.FieldByName('f1').Origin:='v1.f1'; ClientQuery.FieldByName('f2').Origin:='v1.f2'; Hoặc: ClientQuery.FieldByName('f1').Origin:='.f1'; ClientQuery.FieldByName('f2').Origin:='.f2'; Hoặc: ClientQuery.FieldByName('f1').Origin:=''; ClientQuery.FieldByName('f2').Origin:='';
Chú ý: Phải thực hiện thay đổi Origin bằng mã nguồn trong runtime, thiết kế không hiệu quả
Nguyên nhân: Khi mở ClientQuery, thuộc tính Origin mặc định là 't1.f1' (tên bảng vật lý). Server resolver sẽ kiểm tra sự khớp nối giữa TableName và Origin. Khi TableName là 'v1' nhưng Origin vẫn là 't1.f1' → Không thể tạo SQL chính xác.
Chi tiết logic kiểm tra nằm trong phương thức TkbmMWCustomTableFieldResolver.BuildFieldFilter.
Khi để Origin rỗng, cần chú ý đến thuộc tính kbmMWUNIDACResolver.SkipFieldsWithoutOrigin:
- True: Bỏ qua các field không có Origin
- False: Xử lý các field không có Origin (mặc định)
Ứng dụng: Không chỉ cập nhật View, việc kết hợp ClientQuery.TableName và Field.Origin cho phép điều khiển hành vi cập nhật của ClientQuery.Resolve() theo ý muốn.
Dưới đây là ví dụ sử dụng Class Helper để tự động hóa quá trình này:
// Khai báo Helper class cho kbmMWClientQuery
TkbmMWClientQueryHelper = class helper for TkbmMWClientQuery
procedure Open;
end;
// Triển khai phương thức mở lại
{ TkbmMWClientQueryHelper }
procedure TkbmMWClientQueryHelper.Open;
var
i: Integer;
begin
inherited Open;
for I := 0 to Self.FieldCount-1 do
begin
if Self.Fields[i].Origin.Substring(0,1) <> '.' then // Trường vật lý
Self.Fields[i].Origin := '.' + Self.Fields[i].FieldName
else // Trường ảo
Self.Fields[i].Origin := 'xxxxxxxxxx.' + Self.Fields[i].FieldName;
end;
end;
Chú ý: Đơn vị sử dụng ClientQuery.Open() phải import đơn vị chứa Helper class, ngược lại đoạn mã trên sẽ không được thực thi.
Vấn đề mở rộng: Khi cập nhật View cần loại bỏ một số trường khỏi SQL: Cách 1:
- Thiết lập Origin cho các trường cần cập nhật, để trống cho các trường còn lại
- Cấu hình kbmMWUNIDACResolver.SkipFieldsWithoutOrigin := True
Cách 2:
- Sử dụng thuộc tính ProviderFlags của TField để loại bỏ trường khỏi cập nhật:
procedure TPersonManagerFrame.qOneTableAfterOpen(DataSet: TDataSet);
begin
inherited;
// Loại bỏ 2 trường khỏi cập nhật
DataSet.FieldByName('FPositionName').ProviderFlags := DataSet.FieldByName('FPositionName').ProviderFlags - [pfInUpdate,pfInWhere];
DataSet.FieldByName('FPositionID').ProviderFlags := DataSet.FieldByName('FPositionID').ProviderFlags - [pfInUpdate,pfInWhere];
end;
Cách 3: Xử lý sự kiện ExcludeFromUpdateInsert và ExcludeFromWhere của Resolver để kiểm soát trường cập nhật.