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.
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;
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.
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.
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;
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).
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;
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;
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.
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.