Перейти к содержанию
Fire Monkey от А до Я

[Делюсь опытом] Разработка собственного провайдера для БД


Aptem

Рекомендуемые сообщения

Здравствуйте, коллеги.

 

В своих разработках мы придерживаемся принципа разделения приложения на слои:

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.

Изменено пользователем Aptem
Ссылка на комментарий

Интересно. Но все же, чем готовые провайдеры не устроили? написать свой провайдер - работа довольно трудоемкая, а готовые стоят от пары сотен долларов....

Ссылка на комментарий

Интересно. Но все же, чем готовые провайдеры не устроили? написать свой провайдер - работа довольно трудоемкая, а готовые стоят от пары сотен долларов....

 

А какие готовые провайдеры вы имеете ввиду?

Ссылка на комментарий

Поучительно... Для SQLite будет интересно увидеть провайдер.

 

Он менее причесан, чем провайдер для Oracle, но тем не менее отлично работает.

 

Интерфейс:

unit SQLLiteProviderInterface;

interface

uses
      Classes, DB;

/// <summary>
/// Interface for database provider
/// </summary>
type

      IDatabaseProvider<T1> = interface ( IInterface )

      {$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 : T1 ) : 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 : T1 ) : Integer;

      /// <summary>
      ///   Begins session transaction
      /// </summary>
      procedure BeginTransaction ();

      /// <summary>
      ///   Commits current session transaction
      /// </summary>
      procedure CommitTransaction ();

      /// <summary>
      ///   Rollbacks current session transaction
      /// </summary>
      procedure RollbackTransaction ();

      {$ENDREGION}

end;

implementation

end.

Реализация:

unit SQLLiteProvider;

interface

uses
      Classes, FireDAC.Comp.Client, DB, FireDAC.Stan.Param,
      FireDAC.Stan.Intf, FireDAC.Phys, FireDAC.Phys.SQLite, FireDAC.Stan.ExprFuncs,
      FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf,
      FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.FMXUI.Wait,
      FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Comp.DataSet, System.Generics.Collections,

      //BL
      SQLLiteProviderInterface
      ;

/// <summary>
///   Data provider parameter
/// </summary>
type
      TSQLLiteParam = class

      private

            {$REGION 'Private fields'}

            /// <summary>
            ///   Name
            /// </summary>
            _name : String;

            /// <summary>
            ///   Value
            /// </summary>
            _value : Variant;

            /// <summary>
            ///   Type
            /// </summary>
            _paramType : TParamType;

            {$ENDREGION}

      public

            {$REGION 'Properties'}

            /// <summary>
            ///   Gets or sets name
            /// </summary>
            property Name : String read _name write _name;

            /// <summary>
            ///   Gets or sets value
            /// </summary>
            property Value : Variant read _value write _value;

            /// <summary>
            ///   Gets or sets type
            /// </summary>
            property ParamType : TParamType read _paramType write _paramType;

            {$ENDREGION}

end;

/// <summary>
///   Data provider to SQL Lite database
/// </summary>
type
      TSQLLiteProvider = class ( TInterfacedObject, IDatabaseProvider<TObjectList<TSQLLiteParam>> )

      private

            {$REGION 'Private fields'}

            /// <summary>
            ///   Database session
            /// </summary>
            _connection : TFDConnection;

            /// <summary>
            ///   Query
            /// </summary>
            _query : TFDQuery;

            {$ENDREGION}

      public

            {$REGION 'Implementation of IDatabaseProvider'}

            /// <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 : TObjectList<TSQLLiteParam> = 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 : TObjectList<TSQLLiteParam> = nil ) : Integer;

            /// <summary>
            ///   Begins session transaction
            /// </summary>
            procedure BeginTransaction ();

            /// <summary>
            ///   Commits current session transaction
            /// </summary>
            procedure CommitTransaction ();

            /// <summary>
            ///   Rollbacks current session transaction
            /// </summary>
            procedure RollbackTransaction ();

            {$ENDREGION}

            {$REGION 'Constructors'}

            /// <summary>
            ///   Creates TSQLLiteProvider instance
            /// </summary>
            /// <param name="databaseFile">Database file path</param>
            constructor Create ( databaseFilePath : String );

            {$ENDREGION}

            {$REGION 'Destructors'}

            /// <summary>
            ///   Safely destroys TSQLLiteProvider instance
            /// </summary>
            destructor Destroy (); override;

            {$ENDREGION}

            {$REGION 'Public methods'}

            /// <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>
            /// <returns>Binded parameter</returns>
            function CreateParameter ( name : String; value : Variant; paramType : TParamType = ptInput ) : TSQLLiteParam;

            /// <summary>
            ///   Open connection
            /// </summary>
            procedure OpenConnection ();

            /// <summary>
            ///   Close connection
            /// </summary>
            procedure CloseConnection ();

            {$ENDREGION}

      end;

implementation

{TSQLLiteProvider}

/// <summary>
///   Creates TSQLLiteProvider instance
/// </summary>
/// <param name="databaseFile">Database file path</param>
constructor TSQLLiteProvider.Create ( databaseFilePath : String );
begin

      _connection := TFDConnection.Create ( nil );
      _connection.DriverName := 'SQLite';
      _connection.Params.Values['Database'] := databaseFilePath;
      _connection.FetchOptions.Mode := fmAll;

      _query := TFDQuery.Create ( nil );
      _query.Connection := _connection;

end;

/// <summary>
///   Safely destroys TSQLLiteProvider instance
/// </summary>
destructor TSQLLiteProvider.Destroy ();
begin

      if _query.Active then begin
            _query.Close;
      end;

      _query.Free ();
      _query := nil;

      if _connection.Connected then begin
            _connection.Close ();
      end;

      _connection.Free ();
      _connection := nil;

      inherited;

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 TSQLLiteProvider.ExecuteQuery ( query : String; const parameters : TObjectList<TSQLLiteParam> = nil ) : TDataSet;

      var
            i : Integer;
            dataSet : TDataSet;

begin

      dataSet := TDataSet.Create ( nil );

      _query.Close ();
      _query.SQL.Text := query;

      if parameters <> nil then begin
            with _query.Params do begin
                  Clear ();

                  for i := 0 to parameters.Count - 1 do begin
                        with Add do begin
                              Name := TSQLLiteParam ( parameters[i] ).Name;
                              Value := TSQLLiteParam ( parameters[i] ).Value;
                              ParamType := TSQLLiteParam ( parameters[i] ).ParamType;
                        end;
                  end;
            end;
      end;

      if _connection.Connected then begin
            _query.Open ();
            dataSet := _query.Fields.DataSet;
            dataSet.Active := true;
      end;

      Result := dataSet;

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 TSQLLiteProvider.ExecuteNonQuery ( query : String; const parameters : TObjectList<TSQLLiteParam> = nil ) : Integer;

      var
            i : Integer;

begin

      _query.Close ();
      _query.SQL.Text := query;

      if parameters <> nil then begin
            with _query.Params do begin
                  Clear ();

                  for i := 0 to parameters.Count - 1 do begin
                        with Add do begin
                              Name := TSQLLiteParam ( parameters[i] ).Name;
                              Value := TSQLLiteParam ( parameters[i] ).Value;
                              ParamType := TSQLLiteParam ( parameters[i] ).ParamType;
                        end;
                  end;
            end;
      end;

      if _connection.Connected then begin
            _query.ExecSQL ();
            Result := _query.RowsAffected;
      end
      else begin
            Result := 0;
      end;

end;

/// <summary>
///   Open connection
/// </summary>
procedure TSQLLiteProvider.OpenConnection ();
begin

      try
            if ( _connection <> nil ) AND ( not _connection.Connected ) then begin
                  _connection.Connected := true;
            end;
      except
            raise;
      end;

end;

/// <summary>
///   Close connection
/// </summary>
procedure TSQLLiteProvider.CloseConnection ();
begin

      try
            if ( _connection <> nil ) AND (  _connection.Connected ) then begin
                  _connection.Connected := false;
            end;
      except
            raise;
      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>
/// <returns>Binded parameter</returns>
function TSQLLiteProvider.CreateParameter ( name : String; value : Variant; paramType : TParamType = ptInput ) : TSQLLiteParam;

      var
            parameter : TSQLLiteParam;

begin

      parameter := TSQLLiteParam.Create ();
      parameter.Name := name;
      parameter.ParamType := paramType;
      parameter.Value := value;

      Result := parameter;

end;

/// <summary>
///   Begins session transaction
/// </summary>
procedure TSQLLiteProvider.BeginTransaction ();
begin

      _connection.StartTransaction ();

end;

/// <summary>
///   Commits current session transaction
/// </summary>
procedure TSQLLiteProvider.CommitTransaction ();
begin

      if _connection.InTransaction then begin
            _connection.Commit ();
      end;

end;

/// <summary>
///   Rollbacks current session transaction
/// </summary>
procedure TSQLLiteProvider.RollbackTransaction ();
begin

      if _connection.InTransaction then begin
            _connection.Rollback ();
      end;

end;

end.
Ссылка на комментарий

FireDAC? Либо я что то не понимаю что такое провайдер, либо что то ещё. Вы используете стандартные компоненты и рассчитывает, что это выгоднее по ресурсам?

Ссылка на комментарий

FireDAC? Либо я что то не понимаю что такое провайдер, либо что то ещё. Вы используете стандартные компоненты и рассчитывает, что это выгоднее по ресурсам?

 

Провайдером в данном случае я называю обертку для стандартных компонентов доступа к данным с целью выдержать изоляцию слоев DAL, BL и UI. Для приложения не должно быть разницы какой именно компонент используется для доступа к данным. Если вместо FireDAC использовать что-то другое, то достаточно будет лишь заменить "мой" провайдер (обертку) на другой, а все остальное (BL и UI) останется неизменным.

 

Если хотите, то это логический провайдер.

 

И я не писал, что это выгоднее по ресурсам :)

Ссылка на комментарий

Не все ваши аббревиатуры мне понятны, ну да ладно. Скажу лично своё мнение. Не вижу смысла менять что ли бо в приложении. Если я уже выбрал базу данных и доступ к ней ( ну, по крайней мере, за 16 лет опыта, такого не было). Более 2-ух лет использую не очень много стоящие компоненты от devart и все устраивает.

Ссылка на комментарий

Не все ваши аббревиатуры мне понятны, ну да ладно. Скажу лично своё мнение. Не вижу смысла менять что ли бо в приложении. Если я уже выбрал базу данных и доступ к ней ( ну, по крайней мере, за 16 лет опыта, такого не было). Более 2-ух лет использую не очень много стоящие компоненты от devart и все устраивает.

 

DAL - data access layer - слой доступа к данным. Это модули, которые непосредственно передают запрос в БД и возвращают результат выше.

BL - business layer - слой бизнес-логики. Это, по сути, вся логика приложения. Управляет потоками данных, их обработкой.

UI - user interface - пользовательский интерфейс. Формы, кнопки, мемо, гриды и прочее.

 

Все слои по максимуму изолированы друг от друга. Каждый слой знает о существовании только нижнего слоя, но не наоборот. Все это всего лишь один из архитектурных подходов, коих десятки.

 

Разные бывают ситуации. Могу привести свой пример. Есть у нас приложение для работы со СКУД (система контроля и управления доступом), турникет на дверях в здании, грубо говоря. База на MS SQL, все отлично работало 5 лет, а потом в одном из зданий заменили старое оборудование на новое, ну и софт соответственно (системный). А там база вообще на Firebird. Что мы сделали - написали новый "провайдер" и подсунули его приложению. Оно (приложение) и не почувствовало разницы. Сигнатура методов не поменялась, поменялась только внутренняя реализация модулей DAL - слоя доступа к данным.

Ссылка на комментарий

 

Не все ваши аббревиатуры мне понятны, ну да ладно. Скажу лично своё мнение. Не вижу смысла менять что ли бо в приложении. Если я уже выбрал базу данных и доступ к ней ( ну, по крайней мере, за 16 лет опыта, такого не было). Более 2-ух лет использую не очень много стоящие компоненты от devart и все устраивает.

 

DAL - data access layer - слой доступа к данным. Это модули, которые непосредственно передают запрос в БД и возвращают результат выше.

BL - business layer - слой бизнес-логики. Это, по сути, вся логика приложения. Управляет потоками данных, их обработкой.

UI - user interface - пользовательский интерфейс. Формы, кнопки, мемо, гриды и прочее.

 

Все слои по максимуму изолированы друг от друга. Каждый слой знает о существовании только нижнего слоя, но не наоборот. Все это всего лишь один из архитектурных подходов, коих десятки.

 

Разные бывают ситуации. Могу привести свой пример. Есть у нас приложение для работы со СКУД (система контроля и управления доступом), турникет на дверях в здании, грубо говоря. База на MS SQL, все отлично работало 5 лет, а потом в одном из зданий заменили старое оборудование на новое, ну и софт соответственно (системный). А там база вообще на Firebird. Что мы сделали - написали новый "провайдер" и подсунули его приложению. Оно (приложение) и не почувствовало разницы. Сигнатура методов не поменялась, поменялась только внутренняя реализация модулей DAL - слоя доступа к данным.

 

Скажу честно, разрабатываю исключительно для себя или для своего учереждения, поэтому таких глобальных вопросов не  решал. Поэтому, мой вопрос покажется вам глупый, но я его все таки хочу задать. Как решаются проблемы синтаксиса языка программирования? например с моим 20 летним знанием о MySQl всегда делал Limit 10, 10, и на днях случайно оказался в положении, что такое же нужно было сделать и для MsSQl, так вот, там это делается через TOP. Или в некоторых БД нет функций, триггеров и тд.

Как решается такое несоответствие синтаксисов?

Ссылка на комментарий

 

 

Не все ваши аббревиатуры мне понятны, ну да ладно. Скажу лично своё мнение. Не вижу смысла менять что ли бо в приложении. Если я уже выбрал базу данных и доступ к ней ( ну, по крайней мере, за 16 лет опыта, такого не было). Более 2-ух лет использую не очень много стоящие компоненты от devart и все устраивает.

 

DAL - data access layer - слой доступа к данным. Это модули, которые непосредственно передают запрос в БД и возвращают результат выше.

BL - business layer - слой бизнес-логики. Это, по сути, вся логика приложения. Управляет потоками данных, их обработкой.

UI - user interface - пользовательский интерфейс. Формы, кнопки, мемо, гриды и прочее.

 

Все слои по максимуму изолированы друг от друга. Каждый слой знает о существовании только нижнего слоя, но не наоборот. Все это всего лишь один из архитектурных подходов, коих десятки.

 

Разные бывают ситуации. Могу привести свой пример. Есть у нас приложение для работы со СКУД (система контроля и управления доступом), турникет на дверях в здании, грубо говоря. База на MS SQL, все отлично работало 5 лет, а потом в одном из зданий заменили старое оборудование на новое, ну и софт соответственно (системный). А там база вообще на Firebird. Что мы сделали - написали новый "провайдер" и подсунули его приложению. Оно (приложение) и не почувствовало разницы. Сигнатура методов не поменялась, поменялась только внутренняя реализация модулей DAL - слоя доступа к данным.

 

Скажу честно, разрабатываю исключительно для себя или для своего учереждения, поэтому таких глобальных вопросов не  решал. Поэтому, мой вопрос покажется вам глупый, но я его все таки хочу задать. Как решаются проблемы синтаксиса языка программирования? например с моим 20 летним знанием о MySQl всегда делал Limit 10, 10, и на днях случайно оказался в положении, что такое же нужно было сделать и для MsSQl, так вот, там это делается через TOP. Или в некоторых БД нет функций, триггеров и тд.

Как решается такое несоответствие синтаксисов?

 

 

Вопрос очень нетривиальный. Все зависит от конкретного случая. Бывают ситуации когда нельзя полностью заменить конструкции одного языка на другой. Если в случае с MSSQL вы смогли воспользоваться TOP, то в том же Oracle все иначе. Как таковой команды на получения первых n-строк там нет. Есть конечно ROWCOUNT, но это немного другое и его нельзя назвать аналогом TOP или LIMIT. Иногда подобные несоответствия решаются с помощью хранимых процедур, иногда усложнением текста запроса. Повторюсь, все индивидуально и зависит от возникшей проблемы. Миграция на другую платформу вообще дело неблагодарное и непредсказуемое.

Ссылка на комментарий
  • Модераторы

Aptem, а не проще было бы сделать сервак(web например) и тогда ваши провайдеры просто не нужны.

разработать протокол обмена данными по средствам json\xml и тогда не нужно было бы заботить о клиентских приложениях и какая бд используется

вся работа перекладывается на сервак, хоть каждый месяц меняйте БД, просто переписать сервак и все клиенты снова получают и отправляют актуальную информацию

Ссылка на комментарий

Aptem, а не проще было бы сделать сервак(web например) и тогда ваши провайдеры просто не нужны.

разработать протокол обмена данными по средствам json\xml и тогда не нужно было бы заботить о клиентских приложениях и какая бд используется

вся работа перекладывается на сервак, хоть каждый месяц меняйте БД, просто переписать сервак и все клиенты снова получают и отправляют актуальную информацию

 

По трудозатратам это примерно одинаково. По сути ничего не меняется - появилась новая БД - перепиши провайдер или перепиши сервак. Одно и тоже.

 

В JSON/XML меня отпугивает работа с BLOB-полями.

Ссылка на комментарий
  • Модераторы

Разница в том что сервак один нужно поменять и все клиенты тут же работают правильно, а так вам придётся обновить каждого клиента самостоятельно и трудность заключается в доступности этих клиентов(расстояние)

Что именно с BLoB полями не ясно, есть много способов передать blob данные

Ссылка на комментарий

Разница в том что сервак один нужно поменять и все клиенты тут же работают правильно, а так вам придётся обновить каждого клиента самостоятельно и трудность заключается в доступности этих клиентов(расстояние)

Что именно с BLoB полями не ясно, есть много способов передать blob данные

 

Если есть коннект до БД, значит есть возможность и приложение обновить.

 

В моем случае бизнес-логика размазана между приложением и БД и разработка прослоек для преобразования XML-данных только снизит скорость. Ваш вариант безусловно тоже хороший, и у нас некоторые приложения примерно так и работают.

Ссылка на комментарий

В моем случае бизнес-логика размазана между приложением и БД и разработка прослоек для преобразования XML-данных только снизит скорость. Ваш вариант безусловно тоже хороший, и у нас некоторые приложения примерно так и работают.

 

то у вас "слои" приложения разделены, то размазаны между приложением и БД!

непонятно что то ничего!

Ссылка на комментарий
  • Модераторы

 

Разница в том что сервак один нужно поменять и все клиенты тут же работают правильно, а так вам придётся обновить каждого клиента самостоятельно и трудность заключается в доступности этих клиентов(расстояние)

Что именно с BLoB полями не ясно, есть много способов передать blob данные

 

Если есть коннект до БД, значит есть возможность и приложение обновить.

 

В моем случае бизнес-логика размазана между приложением и БД и разработка прослоек для преобразования XML-данных только снизит скорость. Ваш вариант безусловно тоже хороший, и у нас некоторые приложения примерно так и работают.

 

каждый клиент имеет прямой коннект к базе?

Ссылка на комментарий

 

 

Разница в том что сервак один нужно поменять и все клиенты тут же работают правильно, а так вам придётся обновить каждого клиента самостоятельно и трудность заключается в доступности этих клиентов(расстояние)

Что именно с BLoB полями не ясно, есть много способов передать blob данные

 

Если есть коннект до БД, значит есть возможность и приложение обновить.

 

В моем случае бизнес-логика размазана между приложением и БД и разработка прослоек для преобразования XML-данных только снизит скорость. Ваш вариант безусловно тоже хороший, и у нас некоторые приложения примерно так и работают.

 

каждый клиент имеет прямой коннект к базе?

 

 

 

Все десктопные приложения - да. На тонком клиенте - по запросу.

Ссылка на комментарий
  • Модераторы

 

Все десктопные приложения - да. На тонком клиенте - по запросу.

а клиенты работают по локалке или через инет?

Ссылка на комментарий

 

 

Все десктопные приложения - да. На тонком клиенте - по запросу.

а клиенты работают по локалке или через инет?

 

 

Декстопные по локалке, на тонком клиенте через глобальную корпоративную сеть, считай через инет.

Ссылка на комментарий
  • Модераторы

 

 

 

Все десктопные приложения - да. На тонком клиенте - по запросу.

а клиенты работают по локалке или через инет?

 

 

Декстопные по локалке, на тонком клиенте через глобальную корпоративную сеть, считай через инет.

 

т.е. в любом случае логин\пароль записан в клиенте и любой кто занимается или разбирается во взломах может получить вашу пару и полный доступ к БД?

правильно я понимаю?

Ссылка на комментарий

 

 

 

 

Все десктопные приложения - да. На тонком клиенте - по запросу.

а клиенты работают по локалке или через инет?

 

 

Декстопные по локалке, на тонком клиенте через глобальную корпоративную сеть, считай через инет.

 

т.е. в любом случае логин\пароль записан в клиенте и любой кто занимается или разбирается во взломах может получить вашу пару и полный доступ к БД?

правильно я понимаю?

 

 

 

Логин/пароль знает только пользователь и вводит его при запуске приложения. Доступ можно получить только к объектам определенной пользовательской роли, а не ко всей БД. Приложения функционируют в выделенной демилитаризованной зоне, IP-адреса АРМ-ов прописаны в межсетевом экране, а сам линк идете через ключ ПКЗИ.

 

В тонком клиенте полноценная трехзвенка.

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...