Friday, May 06, 2005

Circular reference issue of IInterface Object: Delphi implementation

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
IParent = interface(IInterface)
['{0097DA7B-2A00-48EC-9E40-80FA7D443DD4}']
end;

IChild = interface(IInterface)
['{06284805-8F15-4221-B1FF-C43B872C4A14}']
function GetParent: IParent;
property Parent: IParent read GetParent;
end;

TChild = class(TInterfacedObject, IChild)
private
FOuter: pointer;
function GetParent: IParent;
public
constructor Create(const AOuter: IParent);
procedure BeforeDestruction; override;
end;

TParent = class(TInterfacedObject, IParent)
private
FChild: IChild;
public
procedure AfterConstruction; override;
end;

TForm1 = class(TForm)
private
FMyParent: TParent;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.AfterConstruction;
begin
inherited;
FMyParent := TParent.Create;
end;

procedure TForm1.BeforeDestruction;
begin
inherited;
FMyParent.Free;
end;

procedure TChild.BeforeDestruction;
begin
inherited;
ShowMessage('Child Free');
end;

constructor TChild.Create(const AOuter: IParent);
begin
inherited Create;
FOuter := pointer(AOuter);
end;

function TChild.GetParent: IParent;
begin
Result := IParent(FOuter);
end;

procedure TParent.AfterConstruction;
begin
inherited AfterConstruction;
FChild := TChild.Create(Self);
end;

end.

Circular reference issue of IInterface Object

" class="oa" bg nowrap="nowrap" width="1%">Joanna Carter (TeamB) Jun 13 2004, 2:50 pm show options
Newsgroups: borland.public.delphi.oodesign
From: "Joanna Carter \(TeamB\)" - Find messages by this author
Date: Sun, 13 Jun 2004 19:50:12 +0100
Local: Sun,Jun 13 2004 2:50 pm
Subject: Re: Interfaces - Usage rules
Reply to Author | Forward | Print | Individual Message | Show original | Report Abuse

"Robert MIRCEA" a écrit dans le message de news:
40cc1...@newsgroups.borland.com...

> "Either always use interface references, or object references, but never
mix
> the two of them"

Unless you are very experienced or don't care aboout going insane :-)
> and I would like to know more code patterns that I should follow when I
use
> interfaces. How about some rules regarding the inter-referencing of
> interfaces/objects?

The only real gotcha with mutual references is with aggregated objects. Due
to the mutual reference, neither object can be the first to release the
other's memory, therefore you have a memory leak. However, if you use a week
reference on one of the objects, usually the inner, then the problem goes
away.

IInner = interface

end;

IOuter = interface

end;

TInner = class(TInterfacedObject, IInner)
private
fOuter: Pointer; // weak reference
protected
function GetOuter: IOuter; // access function
public
constructor Create(const Outer: IOuter);
end;

constructor TInner.Create(const Outer: IOuter);
begin
inherited Create;
fOuter := Pointer(Outer); // does not increment refcount on Outer
end;

function TInner.GetOuter: IOuter;
begin
Result := IOuter(fOuter); // increments refcount until caller either
// nils result or it goes out of
scope
end;

TOuter = class(TInterfacedObject, IOuter)
private
fInner: IInner; // normal reference
end;

> I've noticed that TInterfaceList is the "recommended" way to go whenever I
> need to hold a list of interfaces. After studying the code for this class,
> I've noticed that TInterfaceList uses internally a TThreadList. However,
it
> seems to me that iterating thru a TInterfaceList using Items property
might
> be subject of heavy locking/unlocking of the underlying list.

TInterfaceList is not only recommended, it is the only pre-written way to
go. Unless you fancy doing it yourself, just use it :-) Any penalties for
locks don't seem to be significant on most modern processors.
> Since using critical sections for synchronization is a very expensive
> operation, how would you interate efficiently thru a TInterfaceList?

Use the standard way of for i := 0 to List.Count - 1.

I have never had any problems and certainly don't notice any untoward side
effects.

> What is the inner mechanism when passing an interface as a "const"
parameter
> for a method? What is the difference between passing an interface with
> "const" and without "const"?

By specifying const for an interface parameter, you are avoiding the
extraneous _AddRef and _Release calls that would otherwise be generated on
entry and exit (this is the same as for strings). As a rule, always use
const.

Also if you are passing a newly created interface ref object into a const
param, then ensure that you cast the constructor call to the desired
interface otherwise the end of the method that creates the temporary object
will release the memory and you will get an AV.

procedure TestMethodToCall(const Thing: IThing);
begin

end;

procedure TTest.ExecuteBadly;
begin
TestMethodToCall(TThing.Create);
end; // temporary object released here

procedure TTest.ExecuteProperly;
begin
TestMethodToCall(TThing.Create as IThing);
end;

Joanna

--
Joanna Carter (TeamB)

Consultant Software Engineer
TeamBUG support for UK-BUG
TeamMM support for ModelMaker


" class="oa" bg nowrap="nowrap" width="1%">Ritchie Annand Jun 14 2004, 6:07 am show options
Newsgroups: borland.public.delphi.oodesign
From: Ritchie Annand - Find messages by this author
Date: Mon, 14 Jun 2004 04:07:33 -0600
Local: Mon,Jun 14 2004 6:07 am
Subject: Re: Interfaces - Usage rules
Reply to Author | Forward | Print | Individual Message | Show original | Report Abuse

In article <40cc1...@newsgroups.borland.com>, noki...@yahoo.com says...

> Hi everybody,

Hi Robert :)
> I am seeking some advice from some more experienced users of Delphi
> interfaces regarding the rules I should follow whenever I develop my domain
> objects with the use of interfaces. I am aware of only one rule:
> "Either always use interface references, or object references, but never mix
> the two of them"

That's a very good rule of thumb. You -can- break the rule if you
understand exactly what's going on, but by and large it's a bad idea.

-- 8< --

Here's a quick breakdown of why:

* Using interface references inserts (Object)._AddRef calls during
assignment and non-const parameter calls (this usually isn't a problem)

* Using interface references inserts (Object)._Release calls during re-
assignment, at the end of methods or while freeing an object they're
contained within. - This is the one that causes half of the problems,
because if you manually .Free an object that has an interface reference
on it, you'll get an (Invalid pointer)._Release being called.

* If you assign the result of a constructor to an object reference (X :
TMyObject; X := TMyObject.Create), it starts with a RefCount of 0 - if
you assign it to an interface reference (X : IMyInterface; X :=
TMyObject.Create;), it starts with a RefCount of 1. So, if you do the
object reference first, the first call you pass it into as an interface
reference will go (Object)._AddRef, (do stuff), (Object)._Release, which
will drop the RefCount down to 0, and say "oh, no more references" and
.Free it

-- 8< --

I just ran into some the other day where I *had* to make a TComponent
implement an interface. I got access violations when I was holding onto
one of its children in my main form when I tried loading another file or
exiting the program. And that's me "already knowing better than to do
that" :)

> and I would like to know more code patterns that I should follow when I use
> interfaces. How about some rules regarding the inter-referencing of
> interfaces/objects?

Here are a few 'rules' I go by when using interfaces:

* Keep the interfaces as technology-independent as possible (so, for
example, if you've got an IEventLogger interface, *don't* put a FileName
property in it - your TEmailLogger and TOutputDebugStringLogger won't use
it, and likewise, your TOutputFileLogger won't be using an e-mail address
:)

(ideally, you should be able to completely fake the object behind your
interface, and the other objects wouldn't know - that's one of the
principles behind unit testing, which interfaces are *very* good for)

* Put the technology-dependent items into the constructor if possible (so
for your log-to-file object, create it with the FileName, a la:
TOutputFileLogger.Create(AFileName: String) );

* Make your interfaces cohesive. If you have an object that needs to do
two different jobs, it's best to make two different interfaces and have
the object implement both than to make one giant interface

As for inter-referencing, that's always a tough question. It's always
just a matter of reference-counting.

Essentially, anywhere you may have circular references, you have two
choices:

* Make all back-references 'weak' (i.e. non-reference-counted) -
typically done by storing them as Pointer and typecasting on the spot as
necessary

* Don't just let go of the references or let them go out of scope - tell
them to go away

There are a lot of variations on that last theme. A simple means might be
something like this:

Make an interface:

IBye = interface
[guid]
procedure Goodbye;
end;

Make a Bye() procedure like this:

procedure Bye(var AInterface);
var
Intf : IInterface;
ByeIntf : IBye;
begin
Intf := IInterface(AInterface);
if Supports(Intf,IBye,ByeIntf) then
ByeIntf.Goodbye;
Intf := nil;
end;

Add IBye to any object that refers back to its parent. Implement it by
letting go of any parent, child or other references, e.g.:

type
TMyObject = class(TMyBaseObject,IBye)
private
...
public
procedure Goodbye;
end;

procedure TMyObject.Goodbye;
var
Index : Integer;
CurrentChild : IMyChild;
begin
FParent := nil;
FScreenCaptureObject := nil;
FPreferences := nil;
for Index := 0 to ChildCount-1 do
begin
CurrentChild := Children[Index];
Bye(CurrentChild);
end;
end;

> Second question:
> I've noticed that TInterfaceList is the "recommended" way to go whenever I
> need to hold a list of interfaces. After studying the code for this class,
> I've noticed that TInterfaceList uses internally a TThreadList. However, it
> seems to me that iterating thru a TInterfaceList using Items property might
> be subject of heavy locking/unlocking of the underlying list.
> Since using critical sections for synchronization is a very expensive
> operation, how would you interate efficiently thru a TInterfaceList?

Critical sections being a very expensive operation is,
by and large, a complete myth; it doesn't even rank up there with adding
two strings together :) They're not interrupts, and they don't do
anything funny to the processor - you'd be surprised how mundane they are
beneath the radar.

The actual expensive part of the critical section is the
WaitForSingleObject (bringing that thread to a standstill, but it doesn't
chew up any CPU time once the wait is started) that gets called by the
operating system if you request a critical section that's already in use.

You only get the WaitForSingleObject happening if you try to Acquire the
critical section from another thread while you've got it Acquired in one
thread. You'll never run into it in single-threaded programming, and it's
pretty easy to avoid in multi-threaded programming.

You honestly won't notice. A profiler will prove it as well ;)

That said, remember to use it as an IInterfaceList:

var
List : IInterfaceList;
begin
List := TInterfaceList.Create;

...and you can create a non-critical-section-based list yourself later,
if you see fit. (Copy the implementation, make the FList a TList and
remove all the locking calls :)

> Third question:
> What is the inner mechanism when passing an interface as a "const" parameter
> for a method? What is the difference between passing an interface with
> "const" and without "const"?

(1)
procedure A(const X: IMyInterface);
begin
...
end;

(2)
procedure A(X: IMyInterface);
begin
...
end;

The non-const (2) is exactly like the const (1) with a couple of
additions:

There's an X._AddRef during the call to A in (2).
There's an X._Release during the 'end;' in (2).

By and large, you can use const to not bother with those two calls.

And, of course, as with other 'const' versus 'non-const' parameters, you
can't reassign the local X := ; in example (1), but you can in
example (2).

There are a couple of presentations I put together that you can add to
the pile you may have discovered on your travels, at:
http://nimble.nimblebrain.net/delphi.html, which includes weak pointer
examples, and my cyclic-reference-capable TFundamental class.

My apologies in advance for my tendency towards information overload.

> Thank you,

I hope it helps, Robert!
> Robert

-- Ritchie Annand
Senior Software Architect
Malibu Engineering & Software Ltd.
http://www.malibugroup.com
http://www.nimblebrain.net

Friday, April 15, 2005

OPF.OID

var Inv: TInvoices;
I1, I2, I3: IItem;
begin

Inv := TInvoice.Create;

I1 := Inv.Items[3]; (OID: 3005)

I2 := Inv.Items[4]; (OID: 3008)
I2.Qty := 30;
( - Search Index of I2.OID, Index = 3
- Dataset.RecNo := 3,
DataSet.Findfield('qty').value := 30)

Inv.Items.Remove(3);
i := I2.Qty; (must be 30)

[ if IItem.OID <> Items.currentItem.OID then
raise Exception.Create('Coding got problem'); ]


// Swaping
I1 := Inv.Items[3];
I2 := Inv.Items[4000];

s := I1.Seq;
I1.Seq := I2.Seq;
I2.Seq := s;
end;


Items.PropStore.Remove(3);

Dataset.RecNo := 3;
DataSet.Delete;

Grid.DataController.Delete(3);


1. 3000
2. 3005
3. 3008
4. 3006
5. 3002
6. 3004
7. 3007
8. 3001
9. 3009


Wednesday, April 13, 2005

OPF.Value Type Framework.2

From: Joanna Carter \(TeamB\)
Subject: Re: Correct design ?





Date: 2004-11-22 01:17:25 PST
"Alan"  a écrit dans le message de news:
41a12948@newsgroups.borland.com...

Let me start by recommending you look at the concept of OPFs (Object
Persistence Frameworks); there are some articles on my website
www.carterconsulting.org.uk

Now for some constructive criticism :

Although you seem to be removing the concepts of database tables from the
UI, you are still linking the Business Objects very firmly to the database
tables.

Your Employee DataModule class has to know about the Employee Business
Object class and the Employee Business Object class has to know about the
Employee DataModule class. This means that both classes have to reside in
the same unit to avoid circular reference problems; not a good idea.

You also require the UI to know the ID of the object before you can get the
details; most users tend to know an employees name better than they know a
number.

Now for some suggestions :

You need to remove the knowledge of any data mechanism from your busines
objects, so you get a business class like this :

TEmployee = class
private
function GetLastName: String;
function GetFirstName: String;
procedure SetLastName(aValue: String);
procedure SetFirstName(aValue: String);
public
property LastName: String
read GetLastName
write SetLastName;
property FirstName: String
read GetFirstName
write SetFirstName;
end;

The business class can then make use of a Value Type Framework to allow you
to get at the values in the private fields.

TValueEnum = (veString, veInteger, ...);

TValueType = class
private
fName: string;
fEnum: TValueEnum;
protected
procedure SetEnum(Value: TValueEnum);
public
constructor Create(const Name: string = ''); virtual;
property Name: string
read fName;
property ValueEnum: TValueEnum
read fEnum;
end;

TStringValueType = class(TValueType)
private
fValue: string;
procedure SetValue(const Value: string);
public
constructor Create(const Name: string = ''); override;
property Value: string
read fValue
write SetValue;
end;

constructor TStringValueType.Create(const Name: string = '');
begin
inherited Create(Name);
SetEnum(veString);
end;

TIntegerValueType = class(TValueType)
private
fValue: Integer;
procedure SetValue(Value: Integer);
public
constructor Create(const Name: string = ''); override;
property Value: Integer
read fValue
write SetValue;
end;

constructor TIntegerValueType.Create(const Name: string = '');
begin
inherited Create(Name);
SetEnum(veInteger);
end;

TxxxValueType......

TObjectValueType = class(TValueType)
private
fValueTypes: TValueTypeList;
protected
procedure CreateValueTypes; virtual;
function GetValueType(const ValueTypeName: string): TValueType;
public
constructor Create(const Name: string = ''); override;
function GetValueTypes: TValueTypeList;
end;

constructor TObjectValueType.Create(const Name: string = '');
begin
inherited Create(Name);
SetEnum(veObject);
CreateValueTypes;
end;

procedure TObjectValueType.CreateValueTypes; virtual;
begin
fValueTypes := TValueTypeList.Create;
fValueTypes.Add(TIntegerValueType.Create('ID');
end;

Now you can alter your Employee class to derive from TObjectValueType :

TEmployee = class(TObjectValueType)
private
function GetLastName: String;
function GetFirstName: String;
procedure SetLastName(const Value: String);
procedure SetFirstName(const Value: String);

protected
procedure CreateValueTypes; override;
public
property LastName: String
read GetLastName
write SetLastName;
property FirstName: String
read GetFirstName
write SetFirstName;
end;

procedure TEmployee.CreateValueTypes;
begin
inherited CreateValueTypes;
fValueTypes.Add(TStringValueType.Create('FirstName');
fValueTypes.Add(TStringValueType.Create('LastName');
end;

function TEmployee.GetLastName: String;
begin
Result := TStringValueType(GetValueType('LastName')).Value;
end;

procedure TEmployee.SetLastName(const Value: String);
begin
TStringValueType(GetValueType('LastName')).Value := Value;
end;

...

Then I would suggest you look at the articles on OPFs to see how to get
objects from storage.

Essentially; the OPF will talk to the Value Types something like this :

procedure PopulateObject(Obj; TObjectValueType);
var
i: Integer;
vt: TValueType;
begin
for i := 0 to Obj.GetValueTypes.GetCount - 1 do
begin
vt := Obj.GetValueTypes[i];
case vt.ValueEnum of
veString:
TStringValueType(vt).Value := Query.FieldByName(vt.Name).AsString;
veInteger:
TIntegerValueType(vt).Value := Query.FieldByName(vt.Name).AsInteger;
veXxxx
...
end;
end;
end;

If you have any more questions, then feel free to ask them here.

Joanna

--
Joanna Carter (TeamB)

Consultant Software Engineer
TeamBUG support for UK-BUG
TeamMM support for ModelMaker

Sunday, April 10, 2005

OPF.Why MetaData

" class="oa" bg nowrap="nowrap" width="1%">Marcos Dec 11 2004, 8:51 am
Newsgroups: borland.public.delphi.oodesign
From: Marcos -
Date: Sat, 11 Dec 2004 14:51:28 -0200
Local: Sat,Dec 11 2004 8:51 am
Subject: Re: ValueType Framework help please

Bob Dawson wrote:
> Classic metadata for a TStringType, for example, might include
> Isrequired, min and max length, case sensitivity, case restriction, input mask
> should a zero-length string be submitted to datastore as is, or as null

It is this that I still didn't understand. If I have in a class:

TPerson
Name: string;
Address: string;

Both registered like TStringType in a TypeRegister (like Joanna do it).
I should have a field metadata (FMetadata: IMetadata) for each attribute
(Name and Address) or should I have in both attributes a reference to a
single object TStringMetadata?

If the affirmative answer is the first one, then I believe that max,
length, IsRequired,... they could be put directly in the interface
TStringType or your base class (TValueType) and not in an attribute
Metadata inside of TStringType. Can anybody comment on?

If the affirmative answer is the second one, then max, length,
IsRequired could not be part of the metadata because Name.IsRequired
attribute can be direferent of Address.IsRequired. Can anybody comment on?

Thank you, sry my bad english



" class="oa" bg nowrap="nowrap" width="1%">Bob Dawson Dec 11 2004, 1:31 pm
Newsgroups: borland.public.delphi.oodesign
From: "Bob Dawson"
Date: Sat, 11 Dec 2004 15:31:42 -0600
Local: Sat,Dec 11 2004 1:31 pm
Subject: Re: ValueType Framework help please

"Marcos" wrote

> I should have a field metadata (FMetadata: IMetadata) for each attribute

Yes.
> length, IsRequired,... they could be put directly in the interface

You could, but that would be a bit wasteful.

Might think of it in terms of data granularity: Metadata is a singleton for
a specific deployment of the TStringType class--so one instance of the
string metadata can support all instances of the TPerson.FirstName object.
Data layer efficiency then argues that we should not be forced to request
the underlying field metadata information everytime we need a new instance
of the field data object. If there are two underlying objects, then we
should probably have two interfaces.

From a design point of view as well, the data and metadata of a stringtype
are different; that is, 'what is the firstname' and 'how long can the first
name be' are really different orders of questions. If we separate out the
IMetadata interface (so that it is merely cited by, not incorporated into,
the data interface, then we can better contain changes to one or the other.

As your framework gets more complex, this containment can become quite
useful. I route metadata requests through a different connection than data
requests (the OPF asks questions about how to act in its own context, not in
the context of the user), so that a question like "How do I store this
object?" is never asked in the context of a business data transaction.

bobD

Friday, April 08, 2005

OPF.Value Type Framework

" class="oa" bg nowrap="nowrap" width="1%">Joanna Carter (TeamB) Aug 25 2004, 9:17 am
Newsgroups: borland.public.delphi.oodesign
From: "Joanna Carter \(TeamB\)"
Date: Wed, 25 Aug 2004 17:17:33 +0100
Local: Wed, Aug 25 2004 9:17 am
Subject: Re: Attribute Framework

"Hechicero" > a écrit dans le message de news:
412c9...@newsgroups.borland.com...

> Many times in this group I heard about the concept of an Attribute
> Framework. Unfortunatelly I don't know what that is and what it is used
for.
> Information I found in google didn't seem to clarify the concepto.
> Can anyone explain what an attribute framework is?

I now call it a Value Type framework as it avoids conflicts with the .NET
idea of Attributes.

Essentially it is a sophisticated version of RTTI that allows you to
implement metadata accessible without having to make all properties
published.

First, you need a simple metadata type...

IMetadata = interface
[GUID]
function GetName: string;
function GetType: TValueTypeType;
end;

...then you can add type specific additional data...

IStringMetadata = interface(IMetadata)
[GUID]
function GetLength: Integer;
function GetCase: TStringCase;
end;

IFloatMetadata = interface(IMetadata)
[GUID]
function GetSize: Integer;
function GetPrecision: Integer;
end;

Then comes the main Value Type types from which all business objects are
built...

IValueType = interface
[GUID]
procedure Assign(const Other: IValueType);
function Clone: IValueType;
function GetAsString: string;
function GetFormatString: string;
function GetMetadata: IMetadata;
function GetName: string;
function GetRequired: Boolean;
function IsNull: Boolean;
procedure SetAsString(const Value: string);
procedure SetFormatString(const Value: string);
procedure SetNull;
procedure SetRequired(Value: Boolean);
property AsString: string
read GetAsString
write SetAsString;
property FormatString: string
read GetFormatString
write SetFormatString;
property Required: Boolean
read GetRequired
write SetRequired;
end;

Once again, we can add type specific information to the generic case.

e.g.

IIntegerValueType = interface(IValueType)
[GUID]
function GetValue: Integer;
procedure SetValue(Value: Integer);
property Value: Integer
read GetValue
write SetValue;
end;

IStringValueType = interface(IValueType)
[GUID]
function GetValue: string;
procedure SetValue(const Value: string);
property Value: string
read GetValue
write SetValue;
end;

All Business Objects that contain properties/attributes are derived from
IObjectValueType, which is designed to contain a list of Value Types, one
for each property about which you wish to obtain metadata...

IObjectValueType = interface(IValueType)
[GUID]
function GetValueType(const Name: string): IValueType;
function GetValueTypes: IValueTypeList;
function GetType: TGUID;
function GetValue: IInterface;
property Value: IInterface
read GetValue;
end;

Finally, you can build something like a Customer interface and class...

ICustomer = interface
[GUID]
function GetCode: string;
function GetName: string;
function GetTotalOnOrder: Double;
function GetTotalShipped: Double;
procedure SetCode(const Value: string);
procedure SetName(const Value: string);
procedure SetTotalOnOrder(Value: Double);
procedure SetTotalShipped(Value: Double);
property Code: string
read GetCode
write SetCode;
property Name: string
read GetName
write SetName;
property TotalOnOrder: Double
read GetTotalOnOrder
write SetTotalOnOrder;
property TotalShipped: Double
read GetTotalShipped
write SetTotalShipped;
end;

TCustomer = class(TObjectValueType, ICustomer)
private
// ICustomer
function GetCode: string;
function GetName: string;
function GetTotalOnOrder: Double;
function GetTotalShipped: Double;
procedure SetCode(const Value: string);
procedure SetName(const Value: string);
procedure SetTotalOnOrder(Value: Double);
procedure SetTotalShipped(Value: Double);
end;

Property accessors usually look like this...

function TCustomer.GetName: string;
begin
Result := (GetValueTypes['Name'] as IStringValueType).Value;
end;

procedure TCustomer.SetName(const Value: string);
begin
(GetValueTypes['Name'] as IStringValueType).Value := Value;
end;

This structure allows you to address the properties of an ICustomer as
simple properties, but also as the sub value types of an object value type
complete with all their metadata.

Normal code uses the ICustomer interface, whilst OPFs and MVP frameworks can
make use of the Value Type information for persistence and display
purposes - much more sophisticated than 'data-aware'. :-))

Joanna

--
Joanna Carter (TeamB)

Consultant Software Engineer
TeamBUG support for UK-BUG
TeamMM support for ModelMaker

Thursday, April 07, 2005

Jason.OPF.2

SQL.com.my (3.5.78.366) says:
ask u one question
SQL.com.my (3.5.78.366) says:
about the unidirectional way of reading record u always recommend...
Jason says:
but don't know what type of data structure will be put in physical RAM and what type will be put in virtual memory
SQL.com.my (3.5.78.366) says:
if read one by one, will the speed slow?
SQL.com.my (3.5.78.366) says:
or can fetch batch by batch from DB server?
Jason says:
some DB allow batch by batch
Jason says:
but interbase/FB is one by one
SQL.com.my (3.5.78.366) says:
i see
Jason says:
but it doesn't matter
Jason says:
bcoz the difference is not big
SQL.com.my (3.5.78.366) says:
i see
Jason says:
if u read one by one, u issue more api commands, and we consider the execuation of api commands are very fast even for 1000000 times
SQL.com.my (3.5.78.366) says:
just now we try the cxgrid in unbound mode, load 100K rows with about 6 fields, takes about 4.8 seconds
Jason says:
if one record doesn't fill up the tcp packet, they will fetch the second record at the sametime
Jason says:
in IB/FB
Jason says:
one api call will result them to get as many records to fill in one tcp packet
SQL.com.my (3.5.78.366) says:
i see.
SQL.com.my (3.5.78.366) says:
so quite efficient too
Jason says:
what are the 6 fields?
Jason says:
what type?
SQL.com.my (3.5.78.366) says:
mostly string fields.
SQL.com.my (3.5.78.366) says:
all string fields.
Jason says:
ic, then it's quite efficient already
Jason says:
with unidirectional dataset u mean?
SQL.com.my (3.5.78.366) says:
tsqldataset
SQL.com.my (3.5.78.366) says:
the dbxpress
Jason says:
but even if u use clientDataSet -> provider -> TSQLDataSet, it's the same thing, they still read one by one
SQL.com.my (3.5.78.366) says:
then we try the provider mode, load the rows into a Tcollection, also 4.6 seconds too
Jason says:
unless u use IBX or IBObject then it's different
Jason says:
dbX + IB/FB always one by one
SQL.com.my (3.5.78.366) says:
so IBX more efficient?
Jason says:
no!!
SQL.com.my (3.5.78.366) says:
but the provider mode suffer from another problem.
SQL.com.my (3.5.78.366) says:
the sorting/filter , grouping all get slow
SQL.com.my (3.5.78.366) says:
very slow
SQL.com.my (3.5.78.366) says:
for the 100k rows case.
SQL.com.my (3.5.78.366) says:
the unbound mode very fast response for sort/filter.
Jason says:
IBXdataset itself is ClientDataSet + Unidirectional dataset, it will consume significant extra overhead. IBX unidirectional dataset is 2 times slower than dbX
SQL.com.my (3.5.78.366) says:
hah, i see
Jason says:
that's why I suggested u to do it in dbx + unboundmode without in memory dataset
SQL.com.my (3.5.78.366) says:
but i have another problem here.
SQL.com.my (3.5.78.366) says:
using the dbx+unboundmode can't perform OPF design
Jason says:
if u use ClientDataSet + provider + dbx to read all, then IBX read all is more efficient
Jason says:
why can't?
SQL.com.my (3.5.78.366) says:
i better avoid using the DA, because they use clientdataset too.
SQL.com.my (3.5.78.366) says:
since the row's data are keep in the grid's datacontroller.
SQL.com.my (3.5.78.366) says:
how to OPF ?
SQL.com.my (3.5.78.366) says:
the cxgrid unbound mode performance very consistent, say 100k takes n second, then 200k takes 2n second.
Jason says:
in provider mode, where are data kept?
SQL.com.my (3.5.78.366) says:
in provider mode, the data keep in my own opf data structure.
SQL.com.my (3.5.78.366) says:
but suffer the sort/filter operation
SQL.com.my (3.5.78.366) says:
i couldn't rely on dexex's for the data structure.
Jason says:
u need to do something to bridge unboundmode and ur opf data structure, it's the samething u need to do if u are using clientdataset etc
SQL.com.my (3.5.78.366) says:
it seems impossible.
SQL.com.my (3.5.78.366) says:
the bridge the devex provide is the customdatasource.
SQL.com.my (3.5.78.366) says:
if i keep two copies of data, then ram will be consume double, one for my OPF, one for cxgrid.
Jason says:
I don't really understand OPF
Jason says:
tell me
SQL.com.my (3.5.78.366) says:
there is one solution...
Jason says:
how do u read the 3rd record
Jason says:
for example?
SQL.com.my (3.5.78.366) says:
i implement my own TDataSet descendant.
SQL.com.my (3.5.78.366) says:
but still doesn't solve the problem.
SQL.com.my (3.5.78.366) says:
it will double up the ram too.
Jason says:
how?
Jason says:
let say TInvoice
Jason says:
if u one to read 3rd Invoice
Jason says:
then how?
Jason says:
Invoice.Row[3]?
Jason says:
or?
SQL.com.my (3.5.78.366) says:
yes.
SQL.com.my (3.5.78.366) says:
something like that.
SQL.com.my (3.5.78.366) says:
if we use the DB grid view, the cxgriddbtableview will cache it's own copy of data from the dataset right?
Jason says:
yes, if u use db grid view, there will be one copy in TDataSet, another copy in DataController
SQL.com.my (3.5.78.366) says:
ya, so using the customdataset solution doesn't solve the problem too.
SQL.com.my (3.5.78.366) says:
the RAM must be consume too.
SQL.com.my (3.5.78.366) says:
unless go to provider mode.
Jason says:
why don't Invoice.Row[3] return the datacontroller.row[3]? in this way u don't have to double-cache the data?
SQL.com.my (3.5.78.366) says:
yes, i think that too.
SQL.com.my (3.5.78.366) says:
but the datacontroller.rows[3] structure doesn't fit the OPF datastructure i want.
SQL.com.my (3.5.78.366) says:
we have to keep track the oldvalue and newvalue for updating later.
SQL.com.my (3.5.78.366) says:
the datacontroller don't have this function.
SQL.com.my (3.5.78.366) says:
but if i do that in browse grid, that is ok.
Jason says:
I think u just need another inner structure called Delta
SQL.com.my (3.5.78.366) says:
yes, i need that.
Jason says:
so I think u don't need to cache data twice at all
SQL.com.my (3.5.78.366) says:
i am stuck here.
SQL.com.my (3.5.78.366) says:
seems like no good solution .
SQL.com.my (3.5.78.366) says:
but the delta structure u mentioned show a hint to me...
SQL.com.my (3.5.78.366) says:
SQL.com.my (3.5.78.366) says:
i am stuck here.
SQL.com.my (3.5.78.366) says:
seems like no good solution .
SQL.com.my (3.5.78.366) says:
but the delta structure u mentioned show a hint to me...
Jason says:
so u still need to cache data twice?
SQL.com.my (3.5.78.366) says:
not so fast to commit yet.
SQL.com.my (3.5.78.366) says:
perhaps i need to think the delta structure first.
Jason says:
sorry was disconnectec
SQL.com.my (3.5.78.366) says:
it's ok.
SQL.com.my (3.5.78.366) says:
by using the datacontroller to store the data. then i need another mechanism to read/write the data to my OPF structure.
SQL.com.my (3.5.78.366) says:
the OPF is the only way to cross database. i think.
Jason says:
not necessary
Jason says:
but it's a way to cross database
Jason says:
but only well-designed OPF can cross
Jason says:
it might not cross either
SQL.com.my (3.5.78.366) says:
yes
Jason says:
uni directional is not always the fastest
Jason says:
the behavior varies among db servers too
SQL.com.my (3.5.78.366) says:
but with OPF, i don't need to worry
SQL.com.my (3.5.78.366) says:
if say the firebird + IBX give the best performance, then i can use that way to access.
Jason says:
so in OPF u still have SQL or not?
SQL.com.my (3.5.78.366) says:
yes, i still have SQL.
SQL.com.my (3.5.78.366) says:
i only pass sql string to the persistence layer.
Jason says:
then if there is difference between SQL syntax then how?
SQL.com.my (3.5.78.366) says:
the persistence layer will interpret the sql statement.
Jason says:
with QG datacontroller, u don't have to worry about the fieldtype already
SQL.com.my (3.5.78.366) says:
i don't have time for that now. if i do the data entry part, then most sql will be able to cross.
SQL.com.my (3.5.78.366) says:
unless doing report, which require diff way of calculation.
Jason says:
I think fieldtype is Delphi's weakest db design
SQL.com.my (3.5.78.366) says:
i have examine the data entry part, mostly use insert/update/delete in simple way.
SQL.com.my (3.5.78.366) says:
only the report will be diff.
SQL.com.my (3.5.78.366) says:
because the entry part do work on bulk basis.
SQL.com.my (3.5.78.366) says:
do -> don't
Jason says:
yeah entry is simple, that's how dataprovider can generate the SQL regardless of what db server used
SQL.com.my (3.5.78.366) says:
yes.
SQL.com.my (3.5.78.366) says:
but i couldn't pass the SQL string directly to the persistence layer to interpret. there is another problem that block it.
SQL.com.my (3.5.78.366) says:
it is the blob field.
Jason says:
yeah
Jason says:
u need to have parameter
SQL.com.my (3.5.78.366) says:
yes. i have to rely on that
Jason says:
and then paramter.asBlob = something
SQL.com.my (3.5.78.366) says:
yes
SQL.com.my (3.5.78.366) says:
so infact, i pass sql string + tparams to the persistent layer to interpret.
Jason says:
or u just pass it to one-record clientdataset + provider, and ask them to resolve for u
Jason says:
?
SQL.com.my (3.5.78.366) says:
can't if i use my own data structure.
SQL.com.my (3.5.78.366) says:
the clientdataset has their own delta structure
Jason says:
u can use ur own data structure for bulk
Jason says:
but when u are going to persist it, then only pass it to a clientdataset which contain one record only
SQL.com.my (3.5.78.366) says:
clientdataset suffer the slow problem if rows grow.
Jason says:
in this case the clientdataset is not going to contain every row
SQL.com.my (3.5.78.366) says:
is that a good way?
Jason says:
every row is still in datacontroller
SQL.com.my (3.5.78.366) says:
if i have 1000 rows of invoice items, i need to pass 1000 times to clientdataset to generate the sql for me
Jason says:
only when u want to resolve, pass the ROW of INTEREST into clientdataset and ask them to do the job for u
SQL.com.my (3.5.78.366) says:
i think i generate my own sql faster than clientdataset.
Jason says:
it's not the most efficient way, but can save u time on coding. And u can totally be independant of clientdataset, when u have time later to do it
Jason says:
bcoz at the moment, u just want to see if ur OPF works
Jason says:
so u can ask clientdataset to do it for u first
Jason says:
then u can enhance it later when u have more time
SQL.com.my (3.5.78.366) says:
the clientdataset need both old value and new value in order to able to generate sql.
Jason says:
if u want to optimize every small part first, then it will take u forever to finish a framework
SQL.com.my (3.5.78.366) says:
haha
SQL.com.my (3.5.78.366) says:
u r right too.
Jason says:

Jason says:
bcoz I think the SQL generation looks easy but still take certain amount of time
Jason says:
especially the BLOB part
SQL.com.my (3.5.78.366) says:
should be ok.
SQL.com.my (3.5.78.366) says:
i have do a brief study , the insert and delete is the most easy
Jason says:
but if u are confident with it, then no problem
SQL.com.my (3.5.78.366) says:
the update will need to traverse the changes field in order to generate better sql .
Jason says:
yeah
Jason says:
when u add a record to clientdataset, and mergechanges, they will all become old value, then u edit the field, then that's new values
Jason says:
then applyupdate
Jason says:
that's it
SQL.com.my (3.5.78.366) says:
yes
SQL.com.my (3.5.78.366) says:
i still doing study on the OPF... hope can come out something.
SQL.com.my (3.5.78.366) says:
i need to go now. nice talk tot u. tonight will play sport.
Jason says:
ok
Jason says:
bye
SQL.com.my (3.5.78.366) says:
bye

Wednesday, April 06, 2005

Jason.OPF.1

Jason says:
yes?
SQL.com.my (3.5.77.365) says:
can i ask u something?
Jason says:
yes?
SQL.com.my (3.5.77.365) says:
i am trying to implement a OPF framework
SQL.com.my (3.5.77.365) says:
Object Persistent Framework.
SQL.com.my (3.5.77.365) says:
i don't want to rely on remobject data abstrat.
SQL.com.my (3.5.77.365) says:
do u know what is Object persistent framework?
Jason says:
u want to implement it urself?
SQL.com.my (3.5.77.365) says:
yes
Jason says:
maybe u can use model maker for class modelling?
Jason says:
OPF is like business object rite?
SQL.com.my (3.5.77.365) says:
there are related
SQL.com.my (3.5.77.365) says:
ok. a brief explanation to u
SQL.com.my (3.5.77.365) says:
OPF 's Object means the OOP's object.
SQL.com.my (3.5.77.365) says:
Persistent meas a data storage for the object. most of the time , it refer to Database's Table. But it can be a text file, xml file too , or a string.
Jason says:
ic
SQL.com.my (3.5.77.365) says:
In the world of OO, we use object to perform tasks, and it a good design of class model could perform very complex task.
SQL.com.my (3.5.77.365) says:
the problem with OO world is not easy to persist it. It means we want to save the state of object for later use.
Jason says:
ok
SQL.com.my (3.5.77.365) says:
thus we need a OPF to both use the OO's strength but has a way to persist it.
SQL.com.my (3.5.77.365) says:
in business application, persistent layer is usual a relational database.
SQL.com.my (3.5.77.365) says:
by using the DataSet.FindField('Code').AsString is not a good OO programming.
SQL.com.my (3.5.77.365) says:
if use something like this:
var I, I1: TInvoice;
begin
I := TInvoice.Create;
I.Code := '300-A01';
I.DocAmt := 300.00;
showmessage(i.code);
I.Free;
end;
SQL.com.my (3.5.77.365) says:
that's much elegant and neat.
SQL.com.my (3.5.77.365) says:
but the problem is how to persist it with using such approach and data-aware controls in delphi.
SQL.com.my (3.5.77.365) says:
i have solved the data-aware controls problem with the above TInvoice class by introduce a IDataStore interface
SQL.com.my (3.5.77.365) says:
ok. not too much details for the IDataStore here. my problem here is how could I persist the object state to RDBMS table?
Jason says:
so u need OO-relational mapping for that?
SQL.com.my (3.5.77.365) says:
ORM , OPF are basically go hand in hand.
SQL.com.my (3.5.77.365) says:
if want to deploy true OPF , the ORM always come along.
SQL.com.my (3.5.77.365) says:
in database world, a table row has state -> inserted, updated or deleted.
SQL.com.my (3.5.77.365) says:
thus the TDataSetProvider's resolver trying to generate the proper SQL for the three status to do updating as well as generate complex sql updating operations for nested dataset.
SQL.com.my (3.5.77.365) says:
this i guess is hard for me to do...
SQL.com.my (3.5.77.365) says:
i study the TSQLResolver in provider.pas and see how the SQL was generated and pass to IProviderSupport object to execute the SQL statement.
Jason says:
why do u want to develop a OPF?
SQL.com.my (3.5.77.365) says:
in object world , we don't have things like object state of updated, inserted, deleted.
Jason says:
the existing ones are not up to ur expectation?
SQL.com.my (3.5.77.365) says:
no.
SQL.com.my (3.5.77.365) says:
nobody reviews their OPF.
SQL.com.my (3.5.77.365) says:
they just give concept only.
SQL.com.my (3.5.77.365) says:
i have studied it for week.
SQL.com.my (3.5.77.365) says:
the DA's solution is closed but still not up to expectation.
SQL.com.my (3.5.77.365) says:
DA's solution is just another midas's clone.
Jason says:
and u think u have time to develop a own one?
SQL.com.my (3.5.77.365) says:
reviews -> reveal.
SQL.com.my (3.5.77.365) says:
i guess should be able.
SQL.com.my (3.5.77.365) says:
i found if using the Tdatasetprovider to perform the database updating operations, then should be allright.
SQL.com.my (3.5.77.365) says:
that's the persistent layer.
SQL.com.my (3.5.77.365) says:
that's the most difficult part to do.
Jason says:
ic
SQL.com.my (3.5.77.365) says:
the rest i almost solve the technical problem.
Jason says:
so u think u technicals are ready for that?
SQL.com.my (3.5.77.365) says:
sort of if using the TDataSetProvider to perform the job.
SQL.com.my (3.5.77.365) says:
is the kbmmw
SQL.com.my (3.5.77.365) says:
is the kbmmw's resolver good?
SQL.com.my (3.5.77.365) says:
i meant the sql statement generator.
Jason says:
kbmmw now has not voice already
SQL.com.my (3.5.77.365) says:
the DA's sql statement generator not too good.
SQL.com.my (3.5.77.365) says:
ic.
Jason says:
how do u mean by good or bad?
SQL.com.my (3.5.77.365) says:
the sql statement generator not easy to implement, especially with the nested part.
SQL.com.my (3.5.77.365) says:
i chase the DA's newsgroup and found there still has bugs in this part.
Jason says:
yeah, guess u have to implement ur own one
SQL.com.my (3.5.77.365) says:
but tdatasetprovider we have use for years, more confident on it.
Jason says:
tdatasetprovider ok ar?
SQL.com.my (3.5.77.365) says:
so far ok...
Jason says:
and the DA one is worse that TDatasetProvider?
Jason says:
that -> than
SQL.com.my (3.5.77.365) says:
not that worst.
SQL.com.my (3.5.77.365) says:
someone report the blob field's problem. i not too sure.
Jason says:
ic
SQL.com.my (3.5.77.365) says:
and they have other ways to write sql too...
Jason says:
if the TDatasetProvider is ok, then what's ur worries?
SQL.com.my (3.5.77.365) says:
it has generator , but it can write own sql .
SQL.com.my (3.5.77.365) says:
because i trace the tsqlresolver and found not too hard to do. haha
SQL.com.my (3.5.77.365) says:
seems like sql generator also easy to write.
SQL.com.my (3.5.77.365) says:
but the nested part is another problem.
SQL.com.my (3.5.77.365) says:
the tdatasetprovider use tree concept to traverse...
SQL.com.my (3.5.77.365) says:
i feel not easy then.
Jason says:
yeah
Jason says:
their tree a lot of pointers
SQL.com.my (3.5.77.365) says:
ya
SQL.com.my (3.5.77.365) says:
i feel the tdatasetprovider doesn't implement in good OO programming.
SQL.com.my (3.5.77.365) says:
if i have time, i can do better then them.
SQL.com.my (3.5.77.365) says:
in fact my problem now is if i use the TClientDataSet to store the Object's properties, then I can have the object state, field's oldvalue and newvalue.
Jason says:
either u use it or u develop ur own one?
SQL.com.my (3.5.77.365) says:
what u meant?
Jason says:
if u don't use TClientDataSet, then what to use?
SQL.com.my (3.5.77.365) says:
i have two IDataStore's implementor, one is the TclientDataSet's data store and another one is using a collection that generate the in-memory storage from class's published RTTI.
SQL.com.my (3.5.77.365) says:
but the RTTI data store doesn't has object state, i still able to do the oldvalue and newvalue.
SQL.com.my (3.5.77.365) says:
if said i able to keep track object state , my problem here is how to generate the SQL doing the updating usnig the collection style?
SQL.com.my (3.5.77.365) says:
using the ClientDataSet style, I can use the provider to generate the sql for me.
SQL.com.my (3.5.77.365) says:
var I, I1: TInvoice;
begin
I := TInvoice.Create;
I.Code := '300-A01';
I.DocAmt := 300.00;
showmessage(i.code);
I.Free;

I1 := TInvoice.Create(TCDSDataStore.Create(ClientDataSet1));
I1.Code := '300-A02';
I1.DocAmt := 400.00;
I1.Free;
end;

SQL.com.my (3.5.77.365) says:
two objects shown above, I using the RTTIDataStore, I1 using the TClientdataset data store.
SQL.com.my (3.5.77.365) says:
I1 i can use the tdatasetprovider to persist the object's state for me.
SQL.com.my (3.5.77.365) says:
but for I is a problem.
Jason says:
so u want to use I1?
SQL.com.my (3.5.77.365) says:
both can use also
SQL.com.my (3.5.77.365) says:
but I lack of the bridge to SQL generator
SQL.com.my (3.5.77.365) says:
"I" means the I variable
Jason says:
maybe u can use I1 first
Jason says:
then later on if u have time, then do ur own SQL generator
SQL.com.my (3.5.77.365) says:
those OPF expert like Scott Ambler, Joanna Carter all propose a framework for SQL generator.
SQL.com.my (3.5.77.365) says:
but they don't give source code, just concept only.
Jason says:
ic
SQL.com.my (3.5.77.365) says:
event the tiOPF also do like that.
Jason says:
so u have to implement their concept
Jason says:
tiOPF doesn't implement it?
SQL.com.my (3.5.77.365) says:
tiOPF do something like those expert propose.
SQL.com.my (3.5.77.365) says:
nvm , just discuss the topic with you. nobody has answer for it...
SQL.com.my (3.5.77.365) says:
i guess the best way is the persistent layer still using the midas way.
Jason says:
just bcoz the exisitng accounting already is midas way?
SQL.com.my (3.5.77.365) says:
then later if i gain more understanding on SQL syntax/grammer, then i can implement my own true OPF. from then, i able to jump to java/c# easily.
SQL.com.my (3.5.77.365) says:
no. because the tools already has.
SQL.com.my (3.5.77.365) says:
even in java i think they have no sql generator like provider too.
SQL.com.my (3.5.77.365) says:
or do you heard anyone has delphi sql generator for object state?
Jason says:
never heard yet
Jason says:
JBuilder got, implemented by Borland
Jason says:
and Microsoft got build in one
SQL.com.my (3.5.77.365) says:
i see.
SQL.com.my (3.5.77.365) says:
since provider can only work be clientdataset, so i can only use clientdataset.
SQL.com.my (3.5.77.365) says:
because clientdataset support delta
SQL.com.my (3.5.77.365) says:
but the drawback is clientdataset performance not good.
SQL.com.my (3.5.77.365) says:
infact, if we have only one dataset and not using the nested dataset, then it is easy to implement own sql generator. i can easily do a framework that can cross DB.
SQL.com.my (3.5.77.365) says:
but with the nested cases, it is not easy then.
Jason says:
yeah, that's why people don't like to use nested ones
SQL.com.my (3.5.77.365) says:
but nested concept is good.
Jason says:
maybe
Jason says:
but I don't like it
SQL.com.my (3.5.77.365) says:
using a single Applyupdates, we can do the posting in a single transaction.
Jason says:
but we seldom have full control in nested one
Jason says:
difficult to understand how it works too
SQL.com.my (3.5.77.365) says:
yes, this is the drawback
Jason says:
I think in real life not many people use nested
Jason says:
it works ok for single master record
SQL.com.my (3.5.77.365) says:
ya
SQL.com.my (3.5.77.365) says:
can we live without nested dataset for the Master detail records?
Jason says:
I think so
Jason says:
but need to use different technique
Jason says:
they have techniques how to apply 2 or more deltas in one transaction
SQL.com.my (3.5.77.365) says:
i see
Jason says:
I'm going to take a shower now
Jason says:
talk to u later
SQL.com.my (3.5.77.365) says:
ok.