В своих разработках мы придерживаемся принципа разделения приложения на слои:
1. Слой доступа к данным - DAL
2. Слой бизнес-логики - BL
3. Слой представления - UI
В связи с этим большую часть модулей приложений мы разрабатываем вручную, не прибегая к использованию визуальных компонентов. Все визуальные компоненты (кнопки, гриды, мемо и т. п.) только в слое UI. Также мы поступили и со слоем DAL, разработав собственный провайдер для доступа к базе данных Oracle. Активно используем его уже лет 5 и пока он покрывает все наши нужды.
Представляю его вашему вниманию и прошу объективной критики по любому поводу.
Интерфейс провайдера:
unit OracleProviderInterface;
interface
uses
Ora, DB, Variants, SysUtils, Classes, Contnrs,
Spring.Collections
;
/// <summary>
/// Parameters data type enumeration
/// </summary>
type
TParamDataType = ( pdtDefault = 1, pdtBlob = 2, pdtTable = 3, pdtClob = 4 );
/// <summary>
/// Class for CDB database parameter
/// </summary>
type
TCDBOraParam = Class ( TOraParam )
public
{$REGION 'Fields'}
/// <summary>
/// Gets or sets parameter data type
/// </summary>
ParamDataType : TParamDataType;
{$ENDREGION}
{$REGION 'Constructors'}
/// <summary>
/// Creates TCDBOraParam instance
/// </summary>
constructor Create ();
{$ENDREGION}
end;
/// <summary>
/// Interface for database provider
/// </summary>
type
IDatabaseProvider = interface ( IInterface )
['{BF277F9C-D046-4015-8E17-6B4C67CC021B}']
{$REGION 'Methods'}
/// <summary>
/// Executes SQL query passed with parameters
/// </summary>
/// <param name="query">SQL query text</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Query execution results</returns>
function ExecuteQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : TDataSet;
/// <summary>
/// Executes non-selective (insert, update, delete) SQL passed with parameters
/// </summary>
/// <param name="query">SQL query text</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Number of rows affected</returns>
function ExecuteNonQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : Integer;
/// <summary>
/// Executes stored procedure with particular parameters
/// </summary>
/// <param name="procedureName">Stored procedure name</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Execution results</returns>
function ExecuteProcedure ( procedureName : String; const parameters : IList<TCDBOraParam> = nil ) : TOraParams;
/// <summary>
/// Begins session transaction
/// </summary>
procedure BeginTransaction ();
/// <summary>
/// Commits current session transaction
/// </summary>
procedure CommitTransaction ();
/// <summary>
/// Rollbacks current session transaction
/// </summary>
procedure RollbackTransaction ();
/// <summary>
/// Creates parameter
/// </summary>
/// <param name="name">Parameter name</param>
/// <param name="value">Parameter value</param>
/// <param name="paramType">Parameter type (e.g. input or output)</param>
/// <param name="paramDataType">Parameter data type (e.g. default or BLOB)</param>
/// <returns>Binded parameter</returns>
function CreateParameter ( name : String; value : Variant; paramType : TParamType = ptInput; paramDataType : TParamDataType = pdtDefault ) : TCDBOraParam; overload;
/// <summary>
/// Connects to the server
/// </summary>
procedure OpenSession ();
/// <summary>
/// Disconnects from the server
/// </summary>
procedure CloseSession ();
/// <summary>
/// Gets session
/// </summary>
/// <returns>Connection session</returns>
function GetSession () : TOraSession;
{$ENDREGION}
{$REGION 'Properties'}
/// <summary>
/// Connection session
/// </summary>
property Session : TOraSession read GetSession;
{$ENDREGION}
end;
implementation
{ TCDBOraParam }
/// <summary>
/// Creates TCDBOraParam instance
/// </summary>
constructor TCDBOraParam.Create ();
begin
inherited Create ( nil );
ParamDataType := pdtDefault;
end;
end.
Реализация:
unit OracleProvider;
interface
uses
Ora, DB, Variants, SysUtils, Classes, Contnrs, MemData, DBAccess,
Spring.Container,
Spring.Collections,
//BL
OracleProviderInterface
;
/// <summary>
/// Data provider to Oracle database
/// </summary>
type
TOracleProvider = class ( TInterfacedObject, IDatabaseProvider )
private
{$REGION 'Fields'}
/// <summary>
/// Database session
/// </summary>
_session : TOraSession;
/// <summary>
/// Query
/// </summary>
_query : TOraQuery;
/// <summary>
/// SQL-expression
/// </summary>
_sql : TOraSQL;
/// <summary>
/// Stored procedure
/// </summary>
_storedProcedure : TOraStoredProc;
/// <summary>
/// Flag to understand who owns session
/// </summary>
_isOwnSession : Boolean;
/// <summary>
/// Connection string
/// </summary>
_connectionString : String;
{$ENDREGION}
{$REGION 'Methods'}
/// <summary>
/// Gets parameter's field type by Variant value
/// </summary>
/// <param name="value">value</param>
/// <returns>Field type for TOraParam</returns>
function GetFieldType ( value : Variant ) : TFieldType;
/// <summary>
/// Initializes class inner objects
/// </summary>
/// <param name="isOwnSession">Flag to understand who owns session</param>
procedure Initialize ( isOwnSession : Boolean );
/// <summary>
/// Gets session
/// </summary>
/// <returns>Connection session</returns>
function GetSession () : TOraSession;
{$ENDREGION}
public
{$REGION 'Methods'}
/// <summary>
/// Executes SQL query passed with parameters
/// </summary>
/// <param name="query">SQL query text</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Query execution results</returns>
function ExecuteQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : TDataSet;
/// <summary>
/// Executes non-selective (insert, update, delete) SQL passed with parameters
/// </summary>
/// <param name="query">SQL query text</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Number of rows affected</returns>
function ExecuteNonQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : Integer;
/// <summary>
/// Executes stored procedure with particular parameters
/// </summary>
/// <param name="procedureName">Stored procedure name</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Execution results</returns>
function ExecuteProcedure ( procedureName : String; const parameters : IList<TCDBOraParam> = nil ) : TOraParams;
/// <summary>
/// Begins session transaction
/// </summary>
procedure BeginTransaction ();
/// <summary>
/// Commits current session transaction
/// </summary>
procedure CommitTransaction ();
/// <summary>
/// Rollbacks current session transaction
/// </summary>
procedure RollbackTransaction ();
/// <summary>
/// Creates parameter
/// </summary>
/// <param name="name">Parameter name</param>
/// <param name="value">Parameter value</param>
/// <param name="paramType">Parameter type (e.g. input or output)</param>
/// <param name="paramDataType">Parameter data type (e.g. default or BLOB)</param>
/// <returns>Binded parameter</returns>
function CreateParameter ( name : String; value : Variant; paramType : TParamType = ptInput; paramDataType : TParamDataType = pdtDefault ) : TCDBOraParam; overload;
/// <summary>
/// Connects to the server
/// </summary>
procedure OpenSession ();
/// <summary>
/// Disconnects from the server
/// </summary>
procedure CloseSession ();
{$ENDREGION}
{$REGION 'Constructors'}
/// <summary>
/// Creates TOracleProvider instance with particular connection string
/// </summary>
/// <param name="connectionString">Connection string</param>
constructor Create ( const connectionString : String ); overload;
/// <summary>
/// Creates TOracleProvider instance with particular session
/// </summary>
/// <param name="oracleSession">Connection session</param>
constructor Create ( const oracleSession : TOraSession ); overload;
{$ENDREGION}
{$REGION 'Destructors'}
/// <summary>
/// Safely destroys TOracleProvider instance
/// </summary>
destructor Destroy (); override;
{$ENDREGION}
{$REGION 'Properties'}
/// <summary>
/// Connection session
/// </summary>
property Session : TOraSession read GetSession;
{$ENDREGION}
end;
implementation
{TOracleProvider}
/// <summary>
/// Creates TOracleProvider instance with particular connection string
/// </summary>
/// <param name="connectionString">Connection string</param>
constructor TOracleProvider.Create ( const connectionString : String );
begin
_connectionString := connectionString;
Initialize ( true );
end;
/// <summary>
/// Creates TOracleProvider instance with particular session
/// </summary>
/// <param name="oracleSession">Connection session</param>
constructor TOracleProvider.Create ( const oracleSession : TOraSession );
begin
_session := oracleSession;
Initialize ( false );
end;
/// <summary>
/// Initializes class inner objects
/// </summary>
/// <param name="isOwnSession">Flag to understand who owns session</param>
procedure TOracleProvider.Initialize ( isOwnSession : Boolean );
begin
if isOwnSession then begin
_session := TOraSession.Create ( nil );
_session.ConnectString := _connectionString;
_session.Connected := false;
_session.LoginPrompt := false;
_session.Options.Direct := true;
_session.AutoCommit := false;
_session.Options.NeverConnect := true;
_session.Pooling := true;
end;
_query := TOraQuery.Create ( nil );
_query.Session := _session;
_query.AutoCommit := false;
_query.FetchAll := true;
_sql := TOraSQL.Create ( nil );
_sql.Session := _session;
_sql.AutoCommit := false;
_storedProcedure := TOraStoredProc.Create ( nil );
_storedProcedure.Session := _session;
_storedProcedure.AutoCommit := false;
_storedProcedure.FetchAll := true;
_isOwnSession := isOwnSession;
end;
/// <summary>
/// Safely destroys TOracleProvider instance
/// </summary>
destructor TOracleProvider.Destroy ();
begin
if _query.Active then begin
_query.Close ();
end;
_query.Free ();
if _storedProcedure.Active then begin
_storedProcedure.Close ();
end;
_storedProcedure.Free ();
if _isOwnSession then begin
if _session.Connected then begin
_session.Close ();
end;
_session.Free ();
end;
inherited;
end;
/// <summary>
/// Connects to the server
/// </summary>
procedure TOracleProvider.OpenSession ();
begin
if _session <> nil then begin
try
_session.Open ();
except
on e : Exception do begin
raise Exception.Create ( e.Message );
end;
end;
end;
end;
/// <summary>
/// Disconnects from the server
/// </summary>
procedure TOracleProvider.CloseSession ();
begin
if _isOwnSession then begin
if ( _session <> nil ) AND ( _session.Connected ) then begin
try
_session.Close ();
except
on e : Exception do begin
raise Exception.Create ( e.Message );
end;
end;
end;
end;
end;
/// <summary>
/// Begins session transaction
/// </summary>
procedure TOracleProvider.BeginTransaction ();
begin
try
_session.StartTransaction ();
except
on e : Exception do begin
raise Exception.Create ( e.Message );
end;
end;
end;
/// <summary>
/// Commits current session transaction
/// </summary>
procedure TOracleProvider.CommitTransaction ();
begin
if _session.InTransaction then begin
try
_session.Commit ();
except
on e : Exception do begin
raise Exception.Create ( e.Message );
end;
end;
end;
end;
/// <summary>
/// Rollbacks current session transaction
/// </summary>
procedure TOracleProvider.RollbackTransaction ();
begin
if _session.InTransaction then begin
try
_session.Rollback ();
except
on e : Exception do begin
raise Exception.Create ( e.Message );
end;
end;
end;
end;
/// <summary>
/// Executes SQL query passed with parameters
/// </summary>
/// <param name="query">SQL query text</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Query execution results</returns>
function TOracleProvider.ExecuteQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : TDataSet;
var
i : Integer;
param : TOraParam;
dataSet : TDataSet;
begin
dataSet := TDataSet.Create ( nil );
_query.Close ();
_query.SQL.Text := query;
if Assigned ( parameters ) then begin
_query.Params.Clear ();
for i := 0 to parameters.Count - 1 do begin
param := TOraParam.Create ( nil );
param.Name := TOraParam ( parameters[i] ).Name;
param.Value := TOraParam ( parameters[i] ).Value;
param.ParamType := TOraParam ( parameters[i] ).ParamType;
_query.Params.AddParam ( param );
end;
end;
try
if _session.Connected then begin
_query.Execute ();
dataSet := _query.Fields.DataSet;
dataSet.Active := true;
end;
except
on e : Exception do begin
raise Exception.Create ( e.Message );
end;
end;
Result := dataSet;
end;
/// <summary>
/// Executes stored procedure with particular parameters
/// </summary>
/// <param name="procedureName">Stored procedure name</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Execution results</returns>
function TOracleProvider.ExecuteProcedure ( procedureName : String; const parameters : IList<TCDBOraParam> = nil ) : TOraParams;
var
i : Integer;
j : Integer;
begin
_storedProcedure.Close ();
_storedProcedure.StoredProcName := procedureName;
_storedProcedure.Prepare ();
if Assigned ( parameters ) then begin
for i := 0 to parameters.Count - 1 do begin
for j := 0 to _storedProcedure.Params.Count - 1 do begin
if _storedProcedure.Params[j].Name = TOraParam ( parameters[i] ).Name then begin
_storedProcedure.Params[j] := TOraParam ( parameters[i] );
if TCDBOraParam ( parameters[i] ).ParamDataType = pdtBlob then begin
if ( ( TOraParam ( parameters[i] ).Value <> Null ) AND ( TOraParam ( parameters[i] ).Value <> '') ) then begin
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).DataType := ftOraBlob;
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).ParamType := TOraParam ( parameters[i] ).ParamType;
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).AsOraBlob.LoadFromFile ( TOraParam ( parameters[i] ).Value );
end
else begin
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).DataType := ftOraBlob;
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).ParamType := TOraParam ( parameters[i] ).ParamType;
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).AsOraBlob.FreeBlob ();
end;
end;
if TCDBOraParam ( parameters[i] ).ParamDataType = pdtClob then begin
if ( ( TOraParam ( parameters[i] ).Value <> Null ) AND ( TOraParam ( parameters[i] ).Value <> '') ) then begin
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).DataType := ftOraClob;
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).ParamType := TOraParam ( parameters[i] ).ParamType;
_storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).Value := TOraParam ( parameters[i] ).Value;
end;
end;
end;
end;
end;
end;
if _session.Connected then begin
try
_storedProcedure.Execute ();
Result := _storedProcedure.Params;
except
on e : Exception do begin
raise Exception.Create ( e.Message );
end;
end;
end
else begin
Result := TOraParams.Create ( nil );
end;
end;
/// <summary>
/// Executes non-selective (insert, update, delete) SQL passed with parameters
/// </summary>
/// <param name="query">SQL query text</param>
/// <param name="parameters">Binded query parameters</param>
/// <returns>Number of rows affected</returns>
function TOracleProvider.ExecuteNonQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : Integer;
var
i : Integer;
begin
_query.Close ();
_query.SQL.Clear ();
_query.SQL.Text := query;
if Assigned ( parameters ) then begin
_query.Params.Clear ();
for i := 0 to parameters.Count - 1 do begin
_query.Params.AddParam ( TOraParam ( parameters[i] ) );
end;
end;
if _session.Connected then begin
_query.ExecSQL ();
Result := _query.RowsAffected;
end
else begin
Result := 0;
end;
end;
/// <summary>
/// Creates parameter
/// </summary>
/// <param name="name">Parameter name</param>
/// <param name="value">Parameter value</param>
/// <param name="paramType">Parameter type (e.g. input or output)</param>
/// <param name="paramDataType">Parameter data type (e.g. default or BLOB)</param>
/// <returns>Binded parameter</returns>
function TOracleProvider.CreateParameter ( name : String; value : Variant; paramType : TParamType = ptInput; paramDataType : TParamDataType = pdtDefault ) : TCDBOraParam;
var
parameter : TCDBOraParam;
begin
parameter := TCDBOraParam.Create ();
parameter.Name := UpperCase ( name );
parameter.ParamType := paramType;
parameter.DataType := GetFieldType ( value );
case paramDataType of
pdtDefault:
begin
parameter.Value := value;
end;
pdtBlob:
begin
parameter.Value := value;
end;
pdtClob:
begin
parameter.Value := value;
end;
pdtTable:
begin
parameter.Table := true;
parameter.DataType := ftFloat;
parameter.Value := value;
end;
end;
parameter.ParamDataType := paramDataType;
Result := parameter;
end;
/// <summary>
/// Gets parameter's field type by Variant value
/// </summary>
/// <param name="value">value</param>
/// <returns>Field type for TOraParam</returns>
function TOracleProvider.GetFieldType ( value : Variant ) : TFieldType;
var
fieldType : TFieldType;
begin
case VarType ( value ) AND VarTypeMask of
varString : fieldType := TFieldType.ftWideString;
varUString : fieldType := TFieldType.ftWideString;
varInteger : fieldType := TFieldType.ftInteger;
varByte : fieldType := TFieldType.ftInteger;
varDate : fieldType := TFieldType.ftDate;
else
fieldType := TFieldType.ftUnknown;
end;
Result := fieldType;
end;
/// <summary>
/// Gets session
/// </summary>
/// <returns>Connection session</returns>
function TOracleProvider.GetSession () : TOraSession;
begin
Result := _session;
end;
initialization
GlobalContainer.RegisterType<TOracleProvider>.Implements<IDatabaseProvider> ( 'Oracle' );
end.
Использование:
1. Объявляем:
var _provider : IDatabaseProvider;
2. Инициализируем:
var _connectionString := login + '/' + password + '@' + HostName + ':' + Port + ':' + SID;
_provider := GlobalContainer.Resolve<IDatabaseProvider> ( 'Oracle', [connectionString] );
_provider.OpenSession ();
3. Собственно использование:
3.1 Получаем выборку (SELECT):
function TVSPHelper.GetVSPListByWellId ( wellId : Integer ) : IList<IVSP>;
var
list : IList<IVSP>;
item : IVSP;
query : String;
dataSet : TDataSet;
i : Integer;
j : Integer;
parameters: IList<TCDBOraParam>;
report : IReport;
begin
query := 'SELECT ' +
'id, ' +
'comments, ' +
'wellbore_id, ' +
'wellbore_name, ' +
'wellbore_comments, ' +
'wellbore_depth, ' +
'begin_date, ' +
'end_date, ' +
'expedition_id, ' +
'expedition_name, ' +
'organization_id, ' +
'organization_name, ' +
'subject_id, ' +
'subject_name, ' +
'well_id, ' +
'well_name, ' +
'well_comments, ' +
'well_x, ' +
'well_y, ' +
'well_altitude, ' +
'well_rotary_table_elevation ' +
' FROM ' +
'seis.v_vsp ' +
' WHERE ' +
'well_id = :ID ';
parameters := TCollections.CreateList<TCDBOraParam> ( true );
parameters.Add ( _provider.CreateParameter ( 'ID', wellId ) );
dataSet := _provider.ExecuteQuery ( query, parameters );
list := TCollections.CreateList<IVSP>;
dataSet.First ();
for i := 0 to dataSet.RecordCount - 1 do begin
item := GlobalContainer.Resolve<IVSP>;
item.Id := dataSet.FieldByName ( 'id' ).AsInteger;
item.Comments := dataSet.FieldByName ( 'comments' ).AsString;
item.BeginDate := dataSet.FieldByName ( 'begin_date' ).AsDateTime;
item.EndDate := dataSet.FieldByName ( 'end_date' ).AsDateTime;
item.Wellbore.Id := dataSet.FieldByName ( 'wellbore_id' ).AsInteger;
item.Wellbore.Name := dataSet.FieldByName ( 'wellbore_name' ).AsString;
item.Wellbore.Comments := dataSet.FieldByName ( 'wellbore_comments' ).AsString;
item.Wellbore.Depth := dataSet.FieldByName ( 'wellbore_depth' ).AsFloat;
item.Expedition.Id := dataSet.FieldByName ( 'expedition_id' ).AsInteger;
item.Expedition.Name := dataSet.FieldByName ( 'expedition_name' ).AsString;
item.Organization.Id := dataSet.FieldByName ( 'organization_id' ).AsInteger;
item.Organization.Name := dataSet.FieldByName ( 'organization_name' ).AsString;
item.Wellbore.Well.Subject.Id := dataSet.FieldByName ( 'subject_id' ).AsInteger;
item.Wellbore.Well.Subject.Name := dataSet.FieldByName ( 'subject_name' ).AsString;
item.Wellbore.Well.Id := dataSet.FieldByName ( 'well_id' ).AsInteger;
item.Wellbore.Well.Name := dataSet.FieldByName ( 'well_name' ).AsString;
item.Wellbore.Well.Comments := dataSet.FieldByName ( 'well_comments' ).AsString;
item.Wellbore.Well.X := dataSet.FieldByName ( 'well_x' ).AsFloat;
item.Wellbore.Well.Y := dataSet.FieldByName ( 'well_y' ).AsFloat;
item.Wellbore.Well.Altitude := dataSet.FieldByName ( 'well_altitude' ).AsFloat;
item.Wellbore.Well.RotaryTableElevation := dataSet.FieldByName ( 'well_rotary_table_elevation' ).AsFloat;
list.Add ( item );
dataSet.Next ();
end;
Result := list;
end;
3.2 Вызов хранимой процедуры:
function TVSPHelper.GetShotpointMap ( shotpoint : IVSPShotpoint ) : String;
var
procedureName : String;
parameters: IList<TCDBOraParam>;
returnParameters : TOraParams;
imageURL : String;
errorCode : Integer;
errorMessage : String;
begin
procedureName := 'seis.geometric_functions.p_get_shotpoint_map';
parameters := TCollections.CreateList<TCDBOraParam> ( true );
parameters.Add ( _provider.CreateParameter ( 'operation', 0 ) );
parameters.Add ( _provider.CreateParameter ( 'shotpoint_id_in', shotpoint.Id ) );
parameters.Add ( _provider.CreateParameter ( 'image_url', '', ptOutput ) );
parameters.Add ( _provider.CreateParameter ( 'error_code', 0, ptOutput ) );
parameters.Add ( _provider.CreateParameter ( 'error_message', '', ptOutput ) );
try
_provider.BeginTransaction ();
returnParameters := _provider.ExecuteProcedure ( procedureName, parameters );
imageURL := returnParameters.ParamByName ( 'image_url' ).AsString;
errorCode := returnParameters.ParamByName ( 'error_code' ).AsInteger;
errorMessage := returnParameters.ParamByName ( 'error_message' ).AsString;
if errorCode = 0 then begin
_provider.CommitTransaction ();
end
else begin
_provider.RollbackTransaction ();
end;
except
_provider.RollbackTransaction ();
raise;
end;
imageURL := AnsiReplaceStr ( imageURL, 'https', 'http' );
Result := imageURL;
end;
3.3 Выполнение операций CRUD:
procedure TAdministrationUserHelper.SetUserStatus ( userId : Integer; status : Integer );
var
query : String;
begin
query := 'UPDATE seis.users SET status_id = ' + IntToStr ( status );
try
Provider.BeginTransaction ();
Provider.ExecuteNonQuery ( query );
Provider.CommitTransaction ();
except
Provider.RollbackTransaction ();
raise;
end;
end;
Стоит заметить, что данный провайдер прекрасно подходит как для десктоп-приложений, так и для мобильных приложений. Конечно с небольшими изменениями, но принцип и структура не меняются. Кому будет интересно, могу выложить код провайдера для работы с БД SQLite, который я использовал для приложений под iOS.
Судя по тому, что ответов не было, опишу как это получилось у меня.
Получилось настроить все как я и писал выше, но пока без UI-тестов.
FinalBuilder оказался действительно мощным инструментов, который позволяет делать очень много и решать одни и те же задачи разными способами.
Итак, приступим к описанию.
У меня 17 модулей в группе проектов. Часть кода является общей для всех проектов и вынесена отдельно из проекта. Тесты созданы на каждый проект и на общий код. Пока не будут без ошибок скомпилированы и пройдены тесты, дальнейший процесс сборки пройти не может. Смысл в том, что если общий код с ошибками, то и код каждого из проектов может дать ошибки, поэтому продолжать сборку нет смысла.
Алгоритм сборки:
1. Компилирование тестов общей части кода.
2. Если компилирование без ошибок, то запускаем тесты. Если с ошибками, то прерываем весь процесс и высылаем электронное письмо с логом компилятора.
3. Если тесты пройдены без ошибок, то приступаем к компиляции проектов. Если с ошибками, то прерываем весь процесс и высылаем электронное письмо с логом тестов.
4. Тоже самое с проектами. Сначала компилируем тесты и запускаем их. Если без ошибок, то компилируем сами проекты. Если с ошибками, то весь процесс уже не прерываем, а просто пропускаем проект с ошибками и также высылаем лог компилятора или тестов на почту.
5. Сборка проходит каждую ночь в течении недели, а в воскресенье в конце сборки происходит дистрибуция бинарников на сервер обновлений. Во время дистрибуции в бинарники записывается информация о версии, последней компиляции и прочая информация.
И все это происходит автоматически из одного окна FinalBuilder.
Пока все нравится, полет две недели.
А вот как это дерево, которое я описал, выглядит в FinalBuilder. Во вложении.
Если кого-то интересуют подробности, то пишите, с удовольствием расскажу.
Мы используем атрибуты для проверки через RTTI заполнено ли поле значением или нет:
unit TopologyPoint;
interface
uses
SysUtils, Classes, Rtti,
Spring.Container,
//BL
TopologyPointInterface,
//Modules
WhatElementAttribute
;
/// <summary>
/// Class for point primitive
/// </summary>
type
TTopologyPoint = Class ( TInterfacedObject, ITopologyPoint )
private
{$REGION 'Fields'}
/// <summary>
/// Gets or sets X coordinate
/// </summary>
[TWhatElementAttribute ( TWhatElement.PublicField )]
_x : Variant;
/// <summary>
/// Gets or sets Y coordinate
/// </summary>
[TWhatElementAttribute ( TWhatElement.PublicField )]
_y : Variant;
/// <summary>
/// Gets or sets Z coordinate
/// </summary>
_z : Variant;
{$ENDREGION}
{$REGION 'Methods'}
/// <summary>
/// Gets X
/// </summary>
/// <returns>X</returns>
function GetX () : Variant;
/// <summary>
/// Sets X
/// </summary>
/// <param name="value">X</param>
procedure SetX ( const value : Variant );
/// <summary>
/// Gets Y
/// </summary>
/// <returns>Y</returns>
function GetY () : Variant;
/// <summary>
/// Sets Y
/// </summary>
/// <param name="value">y</param>
procedure SetY ( const value : Variant );
/// <summary>
/// Gets Z
/// </summary>
/// <returns>Z</returns>
function GetZ () : Variant;
/// <summary>
/// Sets Z
/// </summary>
/// <param name="value">Z</param>
procedure SetZ ( const value : Variant );
/// <summary>
/// Gets empty flag of coordinates
/// </summary>
/// <returns>True if all coordinates are 0, false otherwise</returns>
function GetEmptyFlag () : Boolean;
{$ENDREGION}
public
{$REGION 'Properties'}
/// <summary>
/// X coordinate
/// </summary>
property X : Variant read GetX write SetX;
/// <summary>
/// Y coordinate
/// </summary>
property Y : Variant read GetY write SetY;
/// <summary>
/// Z coordinate
/// </summary>
property Z : Variant read GetZ write SetZ;
/// <summary>
/// Gets empty flag of coordinates
/// </summary>
property IsEmpty : Boolean read GetEmptyFlag;
{$ENDREGION}
end;
implementation
{TTopologyPoint}
/// <summary>
/// Gets empty flag of coordinates
/// </summary>
/// <returns>True if all coordinates are 0, false otherwise</returns>
function TTopologyPoint.GetEmptyFlag () : Boolean;
var
context : TRttiContext;
objectType : TRttiType;
field : TRttiField;
attribute : TCustomAttribute;
found : Boolean;
begin
context := TRTTIContext.Create ();
objectType := context.GetType ( Self.ClassInfo );
found := false;
for field in objectType.GetFields do begin
for attribute in field.GetAttributes do begin
if attribute is TWhatElementAttribute then begin
if ( attribute as TWhatElementAttribute ).Element = TWhatElement.PublicField then begin
if field.FieldType.Handle = TypeInfo ( String ) then begin
if field.GetValue ( self ).ToString <> '' then begin
found := true;
break;
end;
end;
if field.FieldType.Handle = TypeInfo ( Integer ) then begin
if field.GetValue ( self ).ToString <> '0' then begin
found := true;
break;
end;
end;
if field.FieldType.Handle = TypeInfo ( Extended ) then begin
if field.GetValue ( self ).ToString <> '0' then begin
found := true;
break;
end;
end;
end;
end;
end;
end;
Result := not found;
end;
/// <summary>
/// Gets X
/// </summary>
/// <returns>X</returns>
function TTopologyPoint.GetX () : Variant;
begin
Result := _x;
end;
/// <summary>
/// Sets X
/// </summary>
/// <param name="value">X</param>
procedure TTopologyPoint.SetX ( const value : Variant );
begin
_x := value;
end;
/// <summary>
/// Gets Y
/// </summary>
/// <returns>Y</returns>
function TTopologyPoint.GetY () : Variant;
begin
Result := _y;
end;
/// <summary>
/// Sets Y
/// </summary>
/// <param name="value">y</param>
procedure TTopologyPoint.SetY ( const value : Variant );
begin
_y := value;
end;
/// <summary>
/// Gets Z
/// </summary>
/// <returns>Z</returns>
function TTopologyPoint.GetZ () : Variant;
begin
Result := _z;
end;
/// <summary>
/// Sets Z
/// </summary>
/// <param name="value">Z</param>
procedure TTopologyPoint.SetZ ( const value : Variant );
begin
_z := value;
end;
initialization
GlobalContainer.RegisterType<TTopologyPoint>.Implements<ITopologyPoint>;
end.
А вот сам класс TWhatElement:
unit WhatElementAttribute;
interface
uses
SysUtils, Classes;
/// <summary>
/// Attribute type enumeration
/// </summary>
type
TWhatElement = ( PublicField, PublicProperty, PublicMethod );
/// <summary>
/// Class for attribute types
/// </summary>
type
TWhatElementAttribute = class ( TCustomAttribute )
private
{$REGION 'Private fields'}
/// <summary>
/// Element type instance
/// </summary>
_element : TWhatElement;
{$ENDREGION}
public
{$REGION 'Properties'}
/// <summary>
/// Property for element type instance
/// </summary>
property Element : TWhatElement read _element write _element;
{$ENDREGION}
{$REGION 'Constructors'}
/// <summary>
/// Creates TWhatElementAttribute instance
/// </summary>
constructor Create ( const element : TWhatElement );
{$ENDREGION}
end;
implementation
/// <summary>
/// Creates TWhatElementAttribute instance
/// </summary>
constructor TWhatElementAttribute.Create ( const element : TWhatElement );
begin
_element := element;
end;
end.
Смысл в том, что когда полей очень много, то писать проверку всех полей очень затруднительно. В случае использования атрибутов, достаточно указать атрибут поля и он будет включен в проверку автоматически.
Ваша ошибка в том, что вы используете не тот список. Вы используете TList, который не удаляет свои элементы в момент разрушения списка. Поэтому ваша утечка - это не распущенные экземпляры TSurvey. Вместо этого нужно использовать TObjectList, который при установке ownsObjects = True, удаляем свои элементы.
Итого, код создания списка должен быть таким:
function GetList: IList<TSurvey>;
var
list : IList<TSurvey>;
i : Integer;
item : TSurvey;
begin
list := TCollections.CreateObjectList<TSurvey>(True); // <-- !!!
for i := 0 to 10 do
begin
item := TSurvey.Create ();
item.Id := Random(100);
item.Name := I.ToString;
list.Add ( item );
end;
Result := list;
end;
Можно поиграться с проектом в Windows MemoryLeak.zip
Хочу поделиться успешным опытом подключения аппарата HTC Desire C к Delphi XE5. Никаких сложностей с подключением не возникло. Скачал с официального сайта HTC программу HTC Sync http://www.htc.com/ru/support/content.aspx?id=6301. В ее состав входят и драйверы. После установки саму программу HTC Sync снес, она мне как таковая не нужна. Смартфон автоматически появился в дереве target-устройств проекта.
Выходные данные:
Смартфон: HTC Desire C
ОС смартфона: Android 4.0.3
ОС среды Delphi: Windows XP
Версия Delphi: XE5
UPD. Небольшая поправка для пользователей Windows 7. Почему-то, по-умолчанию, драйверы на эту ОС ставятся некорректно и устройство помечается как "Драйверы не установлены". Достаточно запустить процедуру обновления драйверов и руками указать путь к ним. Драйверы лежат в папке "C:\Program Files\HTC\HTC Driver\Driver Files\Win7_x86\" после установки вышеописанной программы.
Например так, если использовать кроссплатформенный сервис FM:
uses
FMX.Platform;
{$R *.fmx}
procedure TForm2.Button1Click(Sender: TObject);
var
LocaleService: IFMXLocaleService;
LangID: string;
begin
if TPlatformServices.Current.SupportsPlatformService(IFMXLocaleService, IInterface(LocaleService)) then
LangID := LocaleService.GetCurrentLangID;
end;
Либо, как вы заметили, можно этого добиться используя на прямую iOS API:
var
CurrentLocale: NSLocale;
LanguageISO: NSString;
begin
CurrentLocale := TNSLocale.Wrap(TNSLocale.OCClass.currentLocale);
LanguageISO := TNSString.Wrap(CurrentLocale.objectForKey((NSLocaleLanguageCode as ILocalObject).GetObjectID)); // <-- Здесь берем то, что нужно от локали
Result := UTF8ToString(LanguageISO.UTF8String);
end;
Хочу поделиться успешным опытом подключения аппарата HTC Desire C к Delphi XE5. Никаких сложностей с подключением не возникло. Скачал с официального сайта HTC программу HTC Sync http://www.htc.com/ru/support/content.aspx?id=6301. В ее состав входят и драйверы. После установки саму программу HTC Sync снес, она мне как таковая не нужна. Смартфон автоматически появился в дереве target-устройств проекта.
Выходные данные:
Смартфон: HTC Desire C
ОС смартфона: Android 4.0.3
ОС среды Delphi: Windows XP
Версия Delphi: XE5
UPD. Небольшая поправка для пользователей Windows 7. Почему-то, по-умолчанию, драйверы на эту ОС ставятся некорректно и устройство помечается как "Драйверы не установлены". Достаточно запустить процедуру обновления драйверов и руками указать путь к ним. Драйверы лежат в папке "C:\Program Files\HTC\HTC Driver\Driver Files\Win7_x86\" после установки вышеописанной программы.
Автор: Артем Ходаев (Aptem) Ссылка на приложение: AppStore
Выпустил свое первое приложения для iPhone - Страны мира.
Смысл приложения не затейлив - справочник стран мира и их столиц. Уникальность его в том, что он содержит полный список всех признанных Россией стран мира. За основу взят Общероссийский классификатор стран мира. На текущий момент найти в AppStore подобное приложение (с полным списком) мне не удалось. Может плохо искал
Также в приложении есть небольшой тест для проверки знаний столиц. Самый простой вариант - 10 вопросов по 5 вариантов ответа, в конце итог с правильными ответами.
Планы на приложение огромные, от новых списков столиц уже не только мира, но и регионов отдельных стран. Например, столица Тосканы? Или столица штата Техас? И до интеллектуального алгоритма проверки знаний. Ну и версия под Android.
Сам же нашел ответ. В мануале меленько-меленько написано
Вот здесь: http://docwiki.embarcadero.com/Libraries/XE5/en/Talk:FMX.ListView.TListViewItem.Accessory
Автор: Артем Ходаев (Aptem) Ссылка на приложение: AppStore
Выпустил свое первое приложения для iPhone - Страны мира.
Смысл приложения не затейлив - справочник стран мира и их столиц. Уникальность его в том, что он содержит полный список всех признанных Россией стран мира. За основу взят Общероссийский классификатор стран мира. На текущий момент найти в AppStore подобное приложение (с полным списком) мне не удалось. Может плохо искал
Также в приложении есть небольшой тест для проверки знаний столиц. Самый простой вариант - 10 вопросов по 5 вариантов ответа, в конце итог с правильными ответами.
Планы на приложение огромные, от новых списков столиц уже не только мира, но и регионов отдельных стран. Например, столица Тосканы? Или столица штата Техас? И до интеллектуального алгоритма проверки знаний. Ну и версия под Android.
Автор: Артем Ходаев (Aptem) Ссылка на приложение: AppStore
Выпустил свое первое приложения для iPhone - Страны мира.
Смысл приложения не затейлив - справочник стран мира и их столиц. Уникальность его в том, что он содержит полный список всех признанных Россией стран мира. За основу взят Общероссийский классификатор стран мира. На текущий момент найти в AppStore подобное приложение (с полным списком) мне не удалось. Может плохо искал
Также в приложении есть небольшой тест для проверки знаний столиц. Самый простой вариант - 10 вопросов по 5 вариантов ответа, в конце итог с правильными ответами.
Планы на приложение огромные, от новых списков столиц уже не только мира, но и регионов отдельных стран. Например, столица Тосканы? Или столица штата Техас? И до интеллектуального алгоритма проверки знаний. Ну и версия под Android.