Commits

Andreas Stenius  committed f9853f0

Collection provider in place!

  • Participants
  • Parent commits dcf2963

Comments (0)

Files changed (4)

File Demo/TestMain.dfm

   Font.Name = 'Tahoma'
   Font.Style = []
   OldCreateOrder = False
-  OnCreate = FormCreate
-  OnDestroy = FormDestroy
   DesignSize = (
     698
     511)
   PixelsPerInch = 96
   TextHeight = 13
   object DBText1: TDBText
-    Left = 417
-    Top = 211
+    Left = 498
+    Top = 146
     Width = 65
     Height = 17
     DataField = 'Name'
   end
   object DBGrid2: TDBGrid
     Left = 0
-    Top = 421
+    Top = 390
     Width = 698
-    Height = 90
+    Height = 121
     Align = alBottom
     DataSource = DataSource2
     TabOrder = 4
   end
   object DBNavigator2: TDBNavigator
     Left = 8
-    Top = 390
+    Top = 359
     Width = 240
     Height = 25
     DataSource = DataSource2
     TabOrder = 5
   end
   object DBEdit1: TDBEdit
-    Left = 488
-    Top = 208
+    Left = 569
+    Top = 143
     Width = 121
     Height = 21
     DataField = 'Caption'

File Demo/TestMain.pas

     Action3: TAction;
     Action4: TAction;
     procedure Button1Click(Sender: TObject);
-    procedure FormCreate(Sender: TObject);
-    procedure FormDestroy(Sender: TObject);
     procedure Button2Click(Sender: TObject);
     procedure DataSource2DataChange(Sender: TObject; Field: TField);
     procedure DataSource2UpdateData(Sender: TObject);
     end;
 end;
 
-procedure TMainForm.FormCreate(Sender: TObject);
-begin
-end;
-
-procedure TMainForm.FormDestroy(Sender: TObject);
-begin
-end;
-
 end.

File Src/ASProvider.pas

     procedure ApplyInsert(var Index: Integer); virtual; abstract;
     procedure ApplyDelete(Index: Integer); virtual; abstract;
 
-    function CreateDataSet: TPacketDataSet; virtual;
+    procedure InitializeDataset; virtual;
     property DataSet: TPacketDataSet read GetDataSet;
     property DSWriter: TDataPacketWriter read GetDSWriter;
 
   public
-    constructor Create; overload;
-    constructor Create(AOwner: TComponent = nil); overload; override;
+//    constructor Create; overload;
+    constructor Create(AOwner: TComponent); {overload;} override;
     destructor Destroy; override;
 
     procedure Open;
     procedure SetProvidedObject(const Value: TObject); virtual;
     function GetProvidedObject: TObject; virtual;
 
+    procedure ExtractObjectData(Obj: TObject); virtual;
+
     procedure ExtractMetaData; override;
     procedure ExtractData; override;
 
   end;
 
   TASCollectionProvider = class(TASGenericProvider<TCollection>)
-  private
   protected
+    function CreateResolver: TCustomResolver; override;
     procedure CheckProvidedClass(const Value: TClass); override;
     procedure CheckProvidedObject(const Value: TObject); override;
     function GetProvidedTypedObject: TCollection; override;
     procedure SetProvidedTypedObject(const Value: TCollection); override;
 
+    procedure ExtractData; override;
+
     procedure ApplyUpdate(Index: Integer; const FieldName: string; const Value: Variant); override;
     procedure ApplyInsert(var Index: Integer); override;
     procedure ApplyDelete(Index: Integer); override;
 
   TASResolver = class(TCustomResolver)
   protected
+    function GetDeltaIndex(Tree: TUpdateTree): Integer; virtual;
+
     procedure DoUpdate(Tree: TUpdateTree); override;
     procedure DoDelete(Tree: TUpdateTree); override;
     procedure DoInsert(Tree: TUpdateTree); override;
     procedure InitTreeData(Tree: TUpdateTree); override;
   end;
 
+  TASCollectionResolver = class(TASResolver)
+  protected
+    function GetDeltaIndex(Tree: TUpdateTree): Integer; override;
+  end;
+
 procedure Register;
 
 implementation
 
 procedure Register;
 begin
-  RegisterComponents('ASTEKK', [TASProvider, TASComponentProvider]);
+  RegisterComponents('ASTEKK', [TASProvider, TASComponentProvider, TASCollectionProvider]);
 end;
 
 { TASCustomProvider }
   Options := [poDisableInserts, poDisableDeletes, poUseQuoteChar];
 end;
 
-constructor TASCustomProvider.Create;
+{constructor TASCustomProvider.Create;
 begin
   Create(nil);
-end;
+end;}
 
 procedure TASCustomProvider.CreateDataPacket(PacketOpts: TGetRecordOptions; ProvOpts: TProviderOptions;
   var RecsOut: Integer; var Data: OleVariant);
   end;
 end;
 
-function TASCustomProvider.CreateDataSet: TPacketDataSet;
-begin
-  Result := TPacketDataSet.Create(Self);
-end;
-
 function TASCustomProvider.CreateResolver: TCustomResolver;
 begin
   Result := TASResolver.Create(Self);
 function TASCustomProvider.GetDataSet: TPacketDataSet;
 begin
   if not Assigned(FDataSet) then
-    FDataSet := CreateDataSet;
+    FDataSet := TPacketDataSet.Create(Self);
 
   Result := FDataSet;
 end;
   Result := FFieldDefs;
 end;
 
+procedure TASCustomProvider.InitializeDataset;
+begin
+  DataSet.Active := False;
+  DataSet.FieldDefs := FieldDefs;
+  DataSet.CreateDataSet;
+end;
+
 function TASCustomProvider.InternalApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
   out ErrorCount: Integer): OleVariant;
 begin
 
 procedure TASResolver.DoDelete(Tree: TUpdateTree);
 begin
-  (Provider as TASCustomProvider).ApplyDelete(Tree.Delta.RecNo - 1);
+  (Provider as TASCustomProvider).ApplyDelete(GetDeltaIndex(Tree));
 end;
 
 procedure TASResolver.DoInsert(Tree: TUpdateTree);
   F: TField;
   Index: Integer;
 begin
-  Index := Tree.Delta.RecNo - 1;
+  Index := -1;
   (Provider as TASCustomProvider).ApplyInsert(Index);
 
   for F in Tree.Delta.Fields do begin
   F: TField;
   Index: Integer;
 begin
-  Index := Tree.Delta.RecNo - 1;
+  Index := GetDeltaIndex(Tree);
   for F in Tree.Delta.Fields do begin
     if F.ReadOnly then
       Continue;
   end;
 end;
 
+function TASResolver.GetDeltaIndex(Tree: TUpdateTree): Integer;
+begin
+  Result := -1;
+end;
+
 procedure TASResolver.InitializeConflictBuffer(Tree: TUpdateTree);
 begin
 
 end;
 
 procedure TASObjectProvider.ExtractData;
-var
-  Ctx: TRttiContext;
-  T: TRttiType;
-  P: TRttiProperty;
-  F: TField;
-  R: Boolean;
-
 begin
-  DataSet.Active := False;
-  DataSet.FieldDefs := FieldDefs;
-  DataSet.CreateDataSet;
-
-  if not Assigned(FObject) then
-    Exit;
-
-  Ctx := TRttiContext.Create;
-  try
-    T := Ctx.GetType(ProvidedClass);
-    DataSet.Insert;
-    try
-      for F in DataSet.Fields do begin
-        P := T.GetProperty(F.FieldName);
-        if Assigned(P) then begin
-          R := F.ReadOnly;
-          if R then
-            F.ReadOnly := False;
-
-          F.Value := P.GetValue(GetProvidedObject).AsVariant;
-
-          if R then
-            F.ReadOnly := True;
-//          CodeSite.Send('Set value %s = %s', [F.FieldName, VarToStr(P.GetValue(ProvidedObject).AsVariant)]);
-        end;
-      end;
-
-      DataSet.Post;
-    except
-      DataSet.Cancel;
-      raise;
-    end;
-  finally
-    Ctx.Free;
-  end;
+  InitializeDataset;
+  ExtractObjectData(ProvidedObject);
 end;
 
 procedure TASObjectProvider.ExtractMetaData;
   end;
 end;
 
+procedure TASObjectProvider.ExtractObjectData(Obj: TObject);
+var
+  Ctx: TRttiContext;
+  T: TRttiType;
+  P: TRttiProperty;
+  F: TField;
+  R: Boolean;
+
+begin
+  if not Assigned(Obj) then
+    Exit;
+
+  Ctx := TRttiContext.Create;
+  try
+    T := Ctx.GetType(Obj.ClassType);
+    DataSet.Append;
+    try
+      for F in DataSet.Fields do begin
+        P := T.GetProperty(F.FieldName);
+        if Assigned(P) then begin
+          R := F.ReadOnly;
+          if R then
+            F.ReadOnly := False;
+
+          F.Value := P.GetValue(Obj).AsVariant;
+
+          if R then
+            F.ReadOnly := True;
+        end;
+      end;
+
+      DataSet.Post;
+    except
+      DataSet.Cancel;
+      raise;
+    end;
+  finally
+    Ctx.Free;
+  end;
+end;
+
 function TASObjectProvider.GetProvidedClass: TClass;
 begin
   Result := FClass;
   Options := [poUseQuoteChar];
 end;
 
+function TASCollectionProvider.CreateResolver: TCustomResolver;
+begin
+  Result := TASCollectionResolver.Create(Self);
+end;
+
+procedure TASCollectionProvider.ExtractData;
+var
+  I: TCollectionItem;
+begin
+  InitializeDataset;
+
+  for I in ProvidedCollection do
+    ExtractObjectData(I);
+
+  DataSet.First;
+end;
+
 function TASCollectionProvider.GetProvidedTypedObject: TCollection;
 begin
   Result := inherited;
   inherited;
 end;
 
+{ TASCollectionResolver }
+
+function TASCollectionResolver.GetDeltaIndex(Tree: TUpdateTree): Integer;
+var
+  F: TField;
+begin
+  Result := -1;
+
+  F := Tree.Delta.FindField('ID');
+  if Assigned(F) then
+    Result := F.AsInteger;
+end;
+
 end.
 

File Test/TestASProvider.pas

     Aint: Integer;
   end;
 
-  // Test methods for class TASObjectProvider
+  TASCustomProviderClass = class of TASCustomProvider;
 
-  TestTASProviders<T: TASObjectProvider, constructor> = class(TTestCase)
+  TTestASProviderTestCase = class(TTestCase)
   strict private
-    FASCustomProvider: T;
+    FProvider: TASCustomProvider;
   protected
     procedure CheckFieldDef(Expect: TExpectFieldDef; const Msg: string = '');
     procedure CheckMetaData(ExpectFields: array of TExpectFieldDef; const Msg: string = '');
+    procedure CheckItem(Expect: TExpectItem; const Msg: string = '');
+    procedure CheckCollection(ExpectItems: array of TExpectItem; const Msg: string = '');
+
+    function GetProviderClass: TASCustomProviderClass; virtual;
+    property CustomProvider: TASCustomProvider read FProvider write FProvider;
   public
     procedure SetUp; override;
     procedure TearDown; override;
+  end;
+
+  // Test methods for class TASObjectProvider
+
+  TestTASProviders<T: TASObjectProvider, constructor> = class(TTestASProviderTestCase)
+  protected
+    function Provider: T;
+    function GetProviderClass: TASCustomProviderClass; override;
   published
     procedure TestProvidedClass;
     procedure TestMetaData;
     procedure TestApply;
   end;
 
-  TestCollectionProvider = class(TTestCase)
-  strict private
-    FProvider: TASCollectionProvider;
+  TestCollectionProvider = class(TTestASProviderTestCase)
   protected
-    procedure CheckItem(Expect: TExpectItem; const Msg: string = '');
-    procedure CheckCollection(ExpectItems: array of TExpectItem; const Msg: string = '');
-  public
-    procedure SetUp; override;
-    procedure TearDown; override;
+    function Provider: TASCollectionProvider;
+    function GetProviderClass: TASCustomProviderClass; override;
   published
     procedure TestProvidedClass;
+    procedure TestMetaData;
+    procedure TestData;
     procedure TestInsertData;
     procedure TestDeleteData;
     procedure TestInsertUpdateDeleteData;
     (Idx: 1; Name: 'MyModifiedInt';   Size: 0;  DataType: ftInteger;  Attr: [])
   );
 
-  TestCollectionItems: array[0..2] of TExpectItem = (
-    (Idx: 0; Astring: 'my updated string'; Aint: 123),
-    (Idx: 1; Astring: 'my org string'; Aint: 456),
-    (Idx: 3; Astring: 'my new string'; Aint: 789)
+  TestCollectionMetaData_fields: array[0..4] of TExpectFieldDef = (
+    (Idx: 0; Name: 'Astring';     Size: 20; DataType: ftWideString; Attr: []),
+    (Idx: 1; Name: 'Aint';        Size: 0;  DataType: ftInteger;    Attr: []),
+    (Idx: 2; Name: 'ID';          Size: 0;  DataType: ftInteger;    Attr: [faReadOnly]),
+    (Idx: 3; Name: 'Index';       Size: 0;  DataType: ftInteger;    Attr: []),
+    (Idx: 4; Name: 'DisplayName'; Size: 20; DataType: ftWideString; Attr: [])
+  );
+
+  TestCollectionItems_PreCondition: array[0..2] of TExpectItem = (
+    (Idx: 0; Astring: 'My first string'; Aint: 123),
+    (Idx: 1; Astring: 'My second string'; Aint: 456),
+    (Idx: 2; Astring: 'My third string'; Aint: 789)
+  );
+
+  TestCollectionItems_PostCondition: array[0..2] of TExpectItem = (
+    (Idx: 0; Astring: 'My modified string'; Aint: 654),
+    (Idx: 1; Astring: 'My third string'; Aint: 789),
+    (Idx: 2; Astring: 'My new string'; Aint: 321)
   );
 
 implementation
 uses
   Forms, Rtti, StrUtils;
 
-procedure TestTASProviders<T>.CheckFieldDef(Expect: TExpectFieldDef; const Msg: string);
-var
-  S: string;
+function TestTASProviders<T>.GetProviderClass: TASCustomProviderClass;
 begin
-  S := Format('%sField #%d ''%s'' ', [IfThen(Length(Msg) > 0, Msg + ', ', ''), Expect.Idx, Expect.Name]);
-  with FASCustomProvider.FieldDefs[Expect.Idx] do begin
-    CheckEqualsString(Expect.Name, Name, S + 'name');
-    CheckEquals(Expect.Size, Size, S + 'size');
-    Check(Expect.DataType = DataType,
-      Format(
-        S + 'expected field type ''%s'' <> actual field type ''%s''',
-        [
-          FieldTypeNames[Expect.DataType],
-          FieldTypeNames[DataType]
-        ]
-      )
-    );
-    Check(Expect.Attr = Attributes,
-      Format(
-        S + 'attributes missmatch; missing attributes %s; unexpected attributes %s',
-        [
-          TValue.From<TFieldAttributes>(Expect.Attr - Attributes).ToString,
-          TValue.From<TFieldAttributes>(Attributes - Expect.Attr).ToString
-        ]
-      )
-    );
-  end;
+  Result := T;
 end;
 
-procedure TestTASProviders<T>.CheckMetaData(ExpectFields: array of TExpectFieldDef; const Msg: string);
-var
-  I: Integer;
-  S: string;
+function TestTASProviders<T>.Provider: T;
 begin
-  if Length(Msg) > 0 then
-    S := Msg + ', '
-  else
-    S := '';
-
-  CheckEquals(ExpectFields[High(ExpectFields)].Idx + 1, FASCustomProvider.FieldDefs.Count, S + 'Unexpected number of field defs');
-
-  for I := Low(ExpectFields) to High(ExpectFields) do
-    CheckFieldDef(ExpectFields[I], Msg);
-end;
-
-procedure TestTASProviders<T>.SetUp;
-begin
-  FASCustomProvider := T.Create;
-  Application.InsertComponent(FASCustomProvider);
-end;
-
-procedure TestTASProviders<T>.TearDown;
-begin
-  FreeAndNil(FASCustomProvider);
+  Result := T(CustomProvider);
 end;
 
 procedure TestTASProviders<T>.TestApply;
       Aint := 5;
     end;
 
-    FASCustomProvider.ProvidedObject := Obj;
-    FASCustomProvider.Name := 'TestProvider';
+    Provider.ProvidedObject := Obj;
+    Provider.Name := 'TestProvider';
 
     DS.ProviderName := 'TestProvider';
     DS.Active := True;
       Aint := 5;
     end;
 
-    FASCustomProvider.ProvidedObject := Obj;
-    FASCustomProvider.Name := 'TestProvider';
+    Provider.ProvidedObject := Obj;
+    Provider.Name := 'TestProvider';
 
     DS.ProviderName := 'TestProvider';
     DS.Active := True;
     CheckEquals(1, DS.RecordCount, 'Record count');
     CheckEqualsString('a ro string', DS.FieldByName('AroString').AsString);
     CheckEqualsString('a string', DS.FieldByName('Astring').AsString);
-    CheckEquals(5, DS.FieldByName('Aint').AsInteger, 'Aint field');
+    CheckEquals(5, DS.FieldByName('Aint').AsInteger, 'Aint');
   finally
     Obj.Free;
     DS.Free;
 
 procedure TestTASProviders<T>.TestMetaData;
 begin
-  FASCustomProvider.ProvidedClass := TTestData;
-  CheckEquals(TTestData, FASCustomProvider.ProvidedClass, 'Provided class');
+  Provider.ProvidedClass := TTestData;
+  CheckEquals(TTestData, Provider.ProvidedClass, 'Provided class');
   CheckMetaData(TestMetaData_fields);
 end;
 
 procedure TestTASProviders<T>.TestModifiedFieldDefs;
 begin
-  FASCustomProvider.ProvidedClass := TTestData;
+  Provider.ProvidedClass := TTestData;
   CheckMetaData(TestMetaData_fields, 'Original');
 
-  with FASCustomProvider.FieldDefs do begin
+  with Provider.FieldDefs do begin
     Find('AroString').Free;
     Find('Aint').Name := 'MyModifiedInt';
     with Find('Astring') do begin
   CheckMetaData(TestModified_fields, 'Modified');
 
   { Should not extract meta data from class if there already exists field data }
-  FASCustomProvider.ProvidedClass := TTestData;
+  Provider.ProvidedClass := TTestData;
   CheckMetaData(TestModified_fields, 'Reloaded');
 
-  FASCustomProvider.FieldDefs.Clear;
-  FASCustomProvider.ProvidedClass := TTestData;
+  Provider.FieldDefs.Clear;
+  Provider.ProvidedClass := TTestData;
   CheckMetaData(TestMetaData_fields, 'Cleared and reloaded');
 end;
 
   if not T.ClassNameIs('TASProvider') then
     ExpectedException := EArgumentException;
 
-  FASCustomProvider.ProvidedClass := Self.ClassType;
-  Check(FASCustomProvider.ProvidedClass = Self.ClassType);
+  Provider.ProvidedClass := Self.ClassType;
+  Check(Provider.ProvidedClass = Self.ClassType);
 end;
 
 procedure TestTASProviders<T>.TestStreamingDFM;
 var
   Stream: TMemoryStream;
 begin
-  FASCustomProvider.ProvidedClass := TTestData;
-  with FASCustomProvider.FieldDefs do begin
+  Provider.ProvidedClass := TTestData;
+  with Provider.FieldDefs do begin
     Find('AroString').Free;
     Find('Aint').Name := 'MyModifiedInt';
     with Find('Astring') do begin
 
   Stream := TMemoryStream.Create;
   try
-    Stream.WriteComponent(FASCustomProvider);
-    FreeAndNil(FASCustomProvider);
+    Stream.WriteComponent(Provider);
+    CustomProvider.Free;
+    CustomProvider := nil;
+
     Stream.Position := 0;
-    FASCustomProvider := T(Stream.ReadComponent(T.Create));
+    CustomProvider := T(Stream.ReadComponent(GetProviderClass.Create(Application)));
   finally
     Stream.Free;
   end;
 
 { TestCollectionProvider }
 
-procedure TestCollectionProvider.CheckCollection(ExpectItems: array of TExpectItem; const Msg: string);
-var
-  I: Integer;
-  S: string;
+function TestCollectionProvider.GetProviderClass: TASCustomProviderClass;
 begin
-  if Length(Msg) > 0 then
-    S := Msg + ', '
-  else
-    S := '';
-
-  CheckEquals(ExpectItems[High(ExpectItems)].Idx + 1, FProvider.ProvidedCollection.Count, S + 'Unexpected number of items');
-
-  for I := Low(ExpectItems) to High(ExpectItems) do
-    CheckItem(ExpectItems[I], Msg);
+  Result := TASCollectionProvider;
 end;
 
-procedure TestCollectionProvider.CheckItem(Expect: TExpectItem; const Msg: string);
-var
-  S: string;
+function TestCollectionProvider.Provider: TASCollectionProvider;
 begin
-  S := Format('%sItem #%d ', [IfThen(Length(Msg) > 0, Msg + ', ', ''), Expect.Idx]);
-  with FProvider.ProvidedCollection.Items[Expect.Idx] as TTestItem do begin
-    CheckEqualsString(Expect.Astring, Astring, S + 'Astring');
-    CheckEquals(Expect.Aint, Aint, S + 'Aint');
-  end;
+  Result := CustomProvider as TASCollectionProvider;
 end;
 
-procedure TestCollectionProvider.SetUp;
+procedure TestCollectionProvider.TestData;
+var
+  C: TCollection;
+  DS: TClientDataSet;
 begin
-  FProvider := TASCollectionProvider.Create(Application);
-end;
+  C := TCollection.Create(TTestItem);
+  DS := TClientDataSet.Create(Application);
+  try
+    with C.Add as TTestItem do begin
+      Astring := 'a string';
+      Aint := 69;
+    end;
 
-procedure TestCollectionProvider.TearDown;
-begin
-  FreeAndNil(FProvider);
+    with C.Add as TTestItem do begin
+      Astring := 'a second string';
+      Aint := 96;
+    end;
+
+    Provider.ProvidedCollection := C;
+    Provider.Name := 'TestProvider';
+    DS.ProviderName := 'TestProvider';
+    DS.Open;
+
+    CheckEquals(2, DS.RecordCount, 'Record count');
+    CheckEqualsString('a string', DS.FieldByName('Astring').AsString, 'First record');
+    CheckEquals(69, DS.FieldByName('Aint').AsInteger, 'First record');
+
+    DS.Next;
+    CheckEqualsString('a second string', DS.FieldByName('Astring').AsString, 'Second record');
+    CheckEquals(96, DS.FieldByName('Aint').AsInteger, 'Second record');
+  finally
+    DS.Free;
+    C.Free;
+  end;
 end;
 
 procedure TestCollectionProvider.TestDeleteData;
   DS := TClientDataSet.Create(Application);
   try
     C.Add;
-    FProvider.ProvidedCollection := C;
-    FProvider.Name := 'TestProvider';
+    Provider.ProvidedCollection := C;
+    Provider.Name := 'TestProvider';
     DS.ProviderName := 'TestProvider';
     DS.Active := True;
     DS.Delete;
   C := TCollection.Create(TTestItem);
   DS := TClientDataSet.Create(Application);
   try
-    FProvider.ProvidedCollection := C;
-    FProvider.Name := 'TestProvider';
+    Provider.ProvidedCollection := C;
+    Provider.Name := 'TestProvider';
     DS.ProviderName := 'TestProvider';
     DS.Active := True;
     DS.Insert;
   end;
 end;
 
+{
+TEST insert update delete
+
+  Pre condition:
+    collection with 3 items:
+      0: "My first string" #123
+      1: "My second string" #456
+      2: "My third string" #789
+
+  Test:
+    modify data:
+      a) Delete item 0
+      b) Modify item 1: "My modified string" #654
+      c) Add item: "My new string" #321
+
+  Post condition:
+    collection with 3 items:
+      0: "My modified string" #654
+      1: "My third string" #789
+      2: "My new string" #321
+}
 procedure TestCollectionProvider.TestInsertUpdateDeleteData;
 var
   C: TCollection;
   C := TCollection.Create(TTestItem);
   DS := TClientDataSet.Create(Application);
   try
-    FProvider.ProvidedCollection := C;
-    FProvider.Name := 'TestProvider';
+    with C.Add as TTestItem do begin
+      Astring := 'My first string';
+      Aint := 123;
+    end;
+    with C.Add as TTestItem do begin
+      Astring := 'My second string';
+      Aint := 456;
+    end;
+    with C.Add as TTestItem do begin
+      Astring := 'My third string';
+      Aint := 789;
+    end;
+
+    Provider.ProvidedCollection := C;
+    Provider.Name := 'TestProvider';
     DS.ProviderName := 'TestProvider';
     DS.Active := True;
-    DS.Insert;
-    DS.FindField('Astring').AsString := 'my string';
-    DS.FindField('Aint').AsInteger := 55;
+
+    { Pre condition }
+    CheckCollection(TestCollectionItems_PreCondition, 'Pre condition');
+
+    { Test A }
+    CheckTrue(DS.FindFirst, 'Find first');
+    CheckEqualsString('My first string', DS.FindField('Astring').AsString, 'Current Astring');
+    DS.Delete;
+
+    { Test B }
+    DS.Edit;
+    CheckEqualsString('My second string', DS.FindField('Astring').AsString, 'Current Astring');
+    DS.FindField('Astring').AsString := 'My modified string';
+    DS.FindField('Aint').AsInteger := 654;
+    DS.Post;
+
+    { Test C }
+    DS.AppendRecord(['My new string', 321]);
+
+    { Post condition }
+    CheckEquals(3, DS.ChangeCount, 'Change count');
     CheckEquals(0, DS.ApplyUpdates(-1), 'Apply updates');
-    CheckEquals(1, C.Count, 'Collection count');
-    CheckEqualsString('my string', TTestItem(C.Items[0]).Astring, 'Astring');
-    CheckEquals(55, TTestItem(C.Items[0]).Aint, 'Aint');
+    CheckCollection(TestCollectionItems_PostCondition, 'Post condition');
   finally
     DS.Free;
     C.Free;
   end;
 end;
 
+procedure TestCollectionProvider.TestMetaData;
+var
+  C: TCollection;
+begin
+  C := TCollection.Create(TTestItem);
+  try
+    Provider.ProvidedCollection := C;
+    CheckEquals(TTestItem, Provider.ProvidedClass, 'Provided class');
+    CheckMetaData(TestCollectionMetaData_fields);
+  finally
+    C.Free;
+  end;
+end;
+
 procedure TestCollectionProvider.TestProvidedClass;
 var
   C: TCollection;
 begin
   C := TCollection.Create(TTestItem);
   try
-    FProvider.ProvidedCollection := C;
-    Check(FProvider.ProvidedClass = TTestItem, 'Provided class');
+    Provider.ProvidedCollection := C;
+    Check(Provider.ProvidedClass = TTestItem, 'Provided class');
   finally
     C.Free;
   end;
 end;
 
+{ TTestASProviderTestCase }
+
+procedure TTestASProviderTestCase.CheckCollection(ExpectItems: array of TExpectItem; const Msg: string);
+var
+  I: Integer;
+  S: string;
+begin
+  if Length(Msg) > 0 then
+    S := Msg + ', '
+  else
+    S := '';
+
+  CheckEquals(ExpectItems[High(ExpectItems)].Idx + 1, (CustomProvider as TASCollectionProvider).ProvidedCollection.Count, S + 'Unexpected number of items');
+
+  for I := Low(ExpectItems) to High(ExpectItems) do
+    CheckItem(ExpectItems[I], Msg);
+end;
+
+procedure TTestASProviderTestCase.CheckFieldDef(Expect: TExpectFieldDef; const Msg: string);
+var
+  S: string;
+begin
+  S := Format('%sField #%d ''%s'' ', [IfThen(Length(Msg) > 0, Msg + ', ', ''), Expect.Idx, Expect.Name]);
+  with CustomProvider.FieldDefs[Expect.Idx] do begin
+    CheckEqualsString(Expect.Name, Name, S + 'name');
+    CheckEquals(Expect.Size, Size, S + 'size');
+    Check(Expect.DataType = DataType,
+      Format(
+        S + 'expected field type ''%s'' <> actual field type ''%s''',
+        [
+          FieldTypeNames[Expect.DataType],
+          FieldTypeNames[DataType]
+        ]
+      )
+    );
+    Check(Expect.Attr = Attributes,
+      Format(
+        S + 'attributes missmatch; missing attributes %s; unexpected attributes %s',
+        [
+          TValue.From<TFieldAttributes>(Expect.Attr - Attributes).ToString,
+          TValue.From<TFieldAttributes>(Attributes - Expect.Attr).ToString
+        ]
+      )
+    );
+  end;
+end;
+
+procedure TTestASProviderTestCase.CheckItem(Expect: TExpectItem; const Msg: string);
+var
+  S: string;
+begin
+  S := Format('%sItem #%d ', [IfThen(Length(Msg) > 0, Msg + ', ', ''), Expect.Idx]);
+  with (CustomProvider as TASCollectionProvider).ProvidedCollection.Items[Expect.Idx] as TTestItem do begin
+    CheckEqualsString(Expect.Astring, Astring, S + 'Astring');
+    CheckEquals(Expect.Aint, Aint, S + 'Aint');
+  end;
+end;
+
+procedure TTestASProviderTestCase.CheckMetaData(ExpectFields: array of TExpectFieldDef; const Msg: string);
+var
+  I: Integer;
+  S: string;
+begin
+  if Length(Msg) > 0 then
+    S := Msg + ', '
+  else
+    S := '';
+
+  try
+    CheckEquals(ExpectFields[High(ExpectFields)].Idx + 1, CustomProvider.FieldDefs.Count, S + 'Unexpected number of field defs');
+  except
+    with TStringBuilder.Create do try
+      Append('Actual fields:').AppendLine;
+      for I := 0 to CustomProvider.FieldDefs.Count - 1 do
+        AppendFormat('%d: %s', [I, CustomProvider.FieldDefs[I].Name]).AppendLine;
+
+      Status(ToString);
+    finally
+      Free;
+    end;
+
+    raise;
+  end;
+
+  for I := Low(ExpectFields) to High(ExpectFields) do
+    CheckFieldDef(ExpectFields[I], Msg);
+end;
+
+function TTestASProviderTestCase.GetProviderClass: TASCustomProviderClass;
+begin
+  Result := TASCustomProvider;
+end;
+
+procedure TTestASProviderTestCase.SetUp;
+begin
+  FProvider := GetProviderClass.Create(Application);
+end;
+
+procedure TTestASProviderTestCase.TearDown;
+begin
+  FreeAndNil(FProvider);
+end;
+
 initialization
   // Register any test cases with the test runner
   RegisterTest('Provider', TestTASProviders<TASProvider>.Suite);