EntityDAC

Data Management

The basic unit of data that EntityDAC operates is "entity". Although entity is an ordinary Delphi class, it behavior is some different. In EntityDAC, all entities are managed by the data context, that performs entity creation, holds created entities in the cache for future use, performs entity loading and storing in the database, and carries about their destruction.

Create entity

The object model class hierarchy looks like the following.

The base class that implements entity functionality is TEntity. TEntity is an abstract class, it does not have any properties and methods. The TMappedEntity class extends the TEntity functionality and adds the data context interaction. The object model should consist of a TMappedEntity descendants, which have to implement the properties for storing entity data.

interface

type
  TEmp = class(TMappedEntity)
  private
    FEmpno: TIntegerAttribute;
    FEname: TStringAttribute;
  protected
    function GetEmpno: integer;
    procedure SetEmpno(const Value: integer);
    function GetEname: string;
    procedure SetEname(const Value: string);
  public
    constructor Create; overload; override;
    property Empno: integer read GetEmpno write SetEmpno;
    property Ename: string read GetEname write SetEname;
  end;

implementation
	
{ TEmp }

constructor TEmp.Create;
begin
  inherited Create(MetaModel['Emp']);

  FEmpno := TintegerAttribute.Create(Attributes,
    MetaModel['Emp'].MetaAttributes.Get('Empno'));
  FEname := TstringAttribute.Create(Attributes,
    MetaModel['Emp'].MetaAttributes.Get('Ename'));
end;

function TEmp.GetEmpno: Integer;
begin
  Result := FEmpno.Value;
end;

procedure TEmp.SetEmpno(const Value: Integer);
begin
  FEmpno.Value := Value;
end;

function TEmp.GetEname: String;
begin
  Result := FEname.Value;
end;

procedure TEmp.SetEname(const Value: String);
begin
  FEname.Value := Value;
end;

An entity instance can be created in the same way as the trivial class instance, using the constructor.

var
  Emp: TEmp;
begin
  // create new entity
  Emp := TEmp.Create;
end;

When creating an entity, all its properties are initialized with their default values. It can be possible to set the entity primary key value once when the entity is created. In this case, a primary key value can be specified as the constructor parameter.

var
  Emp: TEmp;
begin
  // create new entity with the specified primary key value
  Emp := TEmp.Create([1]);
end;

Also, an entity instance can be created using methods of the data context.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // create new entity with the specified primary key value
  Emp := Context.CreateEntity<TEmp>([1]);
end;

Attach entity

After an entity is created, the data context has to be notified of this entity, so that data context can place the entity into the cache and take on further functions to manage it. In order to understand the "attach" mechanism, there is need to explain the entity caching. Every used entity is stored in the entity cache implemented by the data context. When it becomes necessary to reuse the same entity, there is no need to refer to the database again to reload entity data, the entity will be initialized from the cache. Data context checks the uniqueness of entities being cached and prohibits to store two entities with the same primary key value. Therefore, it would be impossible to place an entity to the cache automatically on create, because all newly created entities have the same default primary key value.

An entity can be attached to the data context using corresponding entity method.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // create new entity
  Emp := TEmp.Create;
  // set the entity primary key
  Emp.Empno.AsInteger := 1;
  // attach the entity
  Emp.Attach(Context);
end;

Or using the data context method.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // create new entity
  Emp := TEmp.Create;
  // set the entity primary key
  Emp.Empno.AsInteger := 2;
  // attach the entity
  Context.Attach(Emp);
end;

Exception is the situation, when the entity primary key obtains unique value immediately on entity creation, for example, when the key value is exactly known when creating or when using a key generator. In this case, it is possible to create already attached entity using the following data context method.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // create attached entity with the specified primary key value
  Emp := Context.CreateAttachedEntity<TEmp>([1]);
end;

Since the entity is attached and placed into the cache, it must not be explicitly destroyed in the code, because it will be automatically destroyed by the data context. Otherwise, the "Invalid pointer operation" exception will be raised when the application closes.

Get entity

There are three main ways to get a single entity from the data context.

An entity instance can be obtained by its primary key value.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // get single entity by the primary key
  Emp := Context.GetEntity<TEmp>([1]);
end;

Or, an entity instance can be obtained by a condition. If more than one entity matched specified condition, the exception will be raised.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // get single entity by the condition
  Emp := Context.GetEntity<TEmp>('empno = 1');
end;

The last, more complex but the most multipurpose method is to obtain an entity by a LINQ query. As in the previous sample, if the query returns more than one entity, then the appropriate exception will be raised.

var
  Context: TEntityContext;
  Query: ILinqQueryable;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // create the query
  Query := Linq.From(Context['Emp'])
               .Where(Context['Emp']['Empno'] = 1)
               .Select;
  // get single entity by the query
  Emp := Context.GetEntity<TEmp>(Query);
end;

In all cases, obtained entity will be automatically attached to the data context, thus it must not be destroyed manually in the code.

Get entities

For holding a list of entities, EntityDAC provides special IEntityEnumerable interface, which is the IEnumerable descendant. It declares methods to iterate through the list and to access list items.

In the simplest case, a whole list of all entities of a given type can be obtained.

var
  Context: TEntityContext;
  List: IEntityEnumerable<TEmp>;
  Emp: TEmp;
  i: integer;
begin
  // create and initialize the data context
  // ...
  // get a whole list of TEmp entities
  List := Context.GetEntities<TEmp>;
end;

A simple condition can be specified to limit the list.

var
  Context: TEntityContext;
  List: IEntityEnumerable<TEmp>;
  Emp: TEmp;
  i: integer;
begin
  // create and initialize the data context
  // ...
  // get the list by the condition
  List := Context.GetEntities<TEmp>('empno > 1');
end;

A list can be obtained as the result of a LINQ query execution.

var
  Context: TEntityContext;
  Query: ILinqQueryable;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // create the query
  Query := Linq.From(Context['Emp'])
               .Where(Context['Emp']['Empno'] > 1)
               .Select;
  // get the list by the query
  Emp := Context.GetEntities<TEmp>(Query);
end;

After obtaining the list, each entity can be accessed with its index.

for i := 0 to List.Count - 1 do begin
  Emp := List[i];
  // do something
  // ...
end;

In Delphi 2010 and higher, it also possible to use the "for ... in ..." statement to iterate through the list.

for Emp in List do begin
  // do something
  // ...
end;

Save entity

Modifying entity in the code does not affect corresponding database objects. To reflect  in the database all changes made for the entity, it has to be saved.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // creation and initialization of the context
  // ...
  Emp := TEmp.Create;
  // set the unique value to the entity primary key
  Emp.Empno.AsInteger := 1;
  // attach the entity
  Emp.Attach(Context);
  // save the entity in the database
  Emp.Save;
end;

Or using the data context method.

var
  Context: TEntityContext;
  Emp: TEmp ;
begin
  // creation and initialization of the context
  // ...
  Emp := TEmp.Create;
  // set the unique value to the entity primary key
  Emp.Empno.AsInteger := 1;
  // attach the entity
  Context.Attach(Emp);
  // save the entity in the database
  Context.Save(Emp);
end;

Commonly, performing attach before saving an entity is not required because the Save method implicitly calls Attach. However, if an error occurs, it can be difficult to determine at what stage it occurs (when attaching or saving).

Delete entity

Deletion of an entity is a two-phase process in EntityDAC. Since all entities are stored in the cache, the entity first needs to be deleted from it. Then, to apply the deletion in the database, the entity has to be saved.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // get single entity by the primary key
  Emp := Context.GetEntity<TEmp>([1]);
  // delete entity from the cache  
  Emp.Delete;
  // apply deletion in the database
  Emp.Save;
end;

Or using the data context method.

var
  Context: TEntityContext;
  Emp: TEmp ;
begin
  // create and initialize the data context
  // ...
  // get single entity by the primary key
  Emp := Context.GetEntity<TEmp>([1]);
  // delete entity from the cache  
  Context.Delete(Emp);
  // apply deletion in the database
  Context.Save(Emp);
end;

Cancel entity changes

As it was described above, all modification operations with an entity (changing, deleting) have to be confirmed (saved) to reflect in the database. Therefore, until the object has not been saved it is possible to cancel it changes.

var
  Context: TEntityContext;
  Emp: TEmp;
begin
  // create and initialize the data context
  // ...
  // get single entity by the primary key
  Emp := Context.GetEntity<TEmp>([1]);
  // change the entity property
  Emp.Ename.AsString := 'new name';
  // cancel changes
  Emp.Cancel;
end;

Or using the data context method.

var
  Context: TEntityContext;
  Emp: TEmp ;
begin
  // create and initialize the data context
  // ...
  // get single entity by the primary key
  Emp := Context.GetEntity<TEmp>([1]);
  // change the entity property
  Emp.Ename.AsString := 'new name';
  // cancel changes
  Context.Cancel(Emp);
end;

Submit changes

When creating applications there is a quite common situation where many objects have to be saved simultaneously, and perform save for each of them is not suitable (for example, when several related objects should be stored at the same time at the completion of a dialog form). In this case, the data context has the special SubmitChanges method for applying massive changes.

var
  Context: TEntityContext;
begin
  // create and initialize the data context
  // ...
  // making changes to entities
  // ...
  // submit all changes
  Context.SubmitChanges;
end;

Executing the method is the same as the coherent execution of the Save method for each of the modified entities.

Reject changes

And in contrast to the previous method, the RejectChanges method performs opposite action. It simultaneously cancels all changes made to entities.

var
  Context: TEntityContext;
begin
  // create and initialize the data context
  // ...
  // making changes to entities
  // ...
  // reject all changes
  Context.RejectChanges;
end;

Executing the method is the same as the coherent execution of the Cancel method for each of the modified entities.

© 1997-2024 Devart. All Rights Reserved. Request Support DAC Forum Provide Feedback