-1

I'm trying to build an API, but I'm stopped at the following problem:

Here is all the code to simulate the problem

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Rtti;

type
  TForm1 = class(TForm)
    btnWorking: TButton;
    btnNotWorking: TButton;
    procedure btnWorkingClick(Sender: TObject);
    procedure btnNotWorkingClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TEntityManagerWhereExpression = record
    FCondition: Boolean;
  end;

  TEntityManagerWhereExpressionFnc = reference to function(): TEntityManagerWhereExpression;

  TEntityExpressionOperation = record
  private
    FCondition: Boolean;
  public
    class operator Implicit(A: TEntityExpressionOperation): TEntityManagerWhereExpression;
    class operator Equal(A, B: TEntityExpressionOperation): TEntityExpressionOperation;
    class operator BitwiseAnd(A, B: TEntityExpressionOperation): TEntityExpressionOperation;
  end;

  TEntityExpression<T> = record
  private
    FValue: TValue;
  public
    class operator Implicit(A: string): TEntityExpression<T>;
    class operator Implicit(A: TEntityExpression<T>): string;

    class operator Equal(A, B: TEntityExpression<T>): TEntityExpressionOperation;
  end;

  IMyEntity = interface
    ['{BE3EE808-47BB-4E83-94BA-ABC9B3D861F2}']
    function GetNome: TEntityExpression<string>;
    property Nome: TEntityExpression<string> read GetNome;
  end;

  TMyEntity = class(TInterfacedObject, IMyEntity)
    function GetNome: TEntityExpression<string>;
  end;

  IManager = interface
    ['{DE38CC69-069F-48F9-A9A4-E93BA0A87E5F}']
    function produce(out myEntity: IMyEntity): IManager;
    procedure Show(const exp: TEntityManagerWhereExpression);
  end;

  TMyManager = class(TInterfacedObject, IManager)
  public
    function produce(out myEntity: IMyEntity): IManager;
    procedure Show(const exp: TEntityManagerWhereExpression);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnNotWorkingClick(Sender: TObject);
var
  my: IMyEntity;
begin
  TMyManager.Create().produce(my).Show(my.Nome = 'Paulo');
end;

procedure TForm1.btnWorkingClick(Sender: TObject);
var
  my: IMyEntity;
  MyManager: IManager;
begin
  MyManager := TMyManager.Create().produce(my) as IManager;
  MyManager.Show(my.Nome = 'Paulo 2');
end;

{ TMyManager }

function TMyManager.produce(out myEntity: IMyEntity): IManager;
begin
  Result := Self;
  myEntity := TMyEntity.Create;
end;

procedure TMyManager.Show(const exp: TEntityManagerWhereExpression);
begin
  if exp.FCondition then
    ShowMessage('true')
  else
    ShowMessage('false');
end;

{ TEntityExpressionOperation }

class operator TEntityExpressionOperation.BitwiseAnd(A, B: TEntityExpressionOperation): TEntityExpressionOperation;
begin
  Result.FCondition := A.FCondition and B.FCondition;
end;

class operator TEntityExpressionOperation.Equal(A, B: TEntityExpressionOperation): TEntityExpressionOperation;
begin
  Result.FCondition := False;
end;

class operator TEntityExpressionOperation.Implicit(A: TEntityExpressionOperation): TEntityManagerWhereExpression;
begin
  Result.FCondition := A.FCondition;
end;

{ TEntityExpression<T> }

class operator TEntityExpression<T>.Equal(A, B: TEntityExpression<T>): TEntityExpressionOperation;
begin
  Result.FCondition := A.FValue.AsString = B.FValue.AsString;
end;

class operator TEntityExpression<T>.Implicit(A: string): TEntityExpression<T>;
begin
  Result.FValue := A;
end;

class operator TEntityExpression<T>.Implicit(A: TEntityExpression<T>): string;
begin
  Result := A.FValue.AsString;
end;

{ TMyEntity }

function TMyEntity.GetNome: TEntityExpression<string>;
begin
  Result := 'Paulo';
end;

end.

The problem is in the code snippet

TMyManager.Create (). Produces (my) .Show (my.Name = 'Paul');

where the variable my was not initialized, since the code snippet

my.name = 'Paulo'

is checked before

produce

the problem does not occur when I call it this way:

var
   my: IMyEntity;
   MyManager: IManager;
begin
   MyManager: = TMyManager.Create (). Produces (my) as IManager;
   MyManager.Show (my.Name = 'Paul 2');

because the variable my is initialized before calling the Show method

how can I prevent the variable my from being used before it is initialized?

Can someone help me solve this problem? thank you so much

Passella
  • 640
  • 1
  • 8
  • 23
  • Which ORM are you using? Did you ask on their website for support?I even wonder how the "working user code" may work since the entityManagerQueryBuilder variable is never set before use... I'm confused by so much confusion in your samples... – Arnaud Bouchez Mar 18 '18 at 19:13
  • @ArnaudBouchez I'm not using any third-party API, I'm developing my API from scratch – Passella Mar 18 '18 at 19:39
  • @DavidHeffernan What's missing in the code I have not placed? the code I put in is enough to reproduce the error, I'm not using third-party API or something, if the code is not enough, what's missing? thank you – Passella Mar 18 '18 at 19:43
  • @ArnaudBouchez I changed the code – Passella Mar 18 '18 at 19:45
  • 1
    @Passella: It may be enough **for you** to reproduce the error, but **not for us**. A lot of stuff is unknown, so if you want people to be able to help you, make a [MCVE]. – Rudy Velthuis Mar 18 '18 at 19:50
  • @Passella - Paste the code in the question into a brand new project and then run it. You'll find out what is missing. – Sertac Akyuz Mar 18 '18 at 19:51
  • @SertacAkyuz the demo project only has this code, I have done another, this does not change, the problem is that the variable p is nil, and the problem is that the compiler checks p.name before anything in the first code snippet, the second works perfectly, because the variable p has already been initialized, I want to know if I can avoid this – Passella Mar 18 '18 at 19:54
  • @RudyVelthuis, ok, i will put all the code – Passella Mar 18 '18 at 19:59
  • @DavidHeffernan I changed the question by putting the codes and showing where the problem is, can anyone now help? – Passella Mar 18 '18 at 20:07
  • @RudyVelthuis I changed the question by putting the codes and showing where the problem is, can anyone now help? – Passella Mar 18 '18 at 20:08
  • [mcve] please. This isn't it. Still, fluent interfaces aren't as great as you think. – David Heffernan Mar 18 '18 at 20:15
  • wait a moment, I'll make another example – Passella Mar 18 '18 at 20:19
  • What you posted is not a [MCVE]. It should be something other people can copy to their editor and compile and run. Even after your edits, this isn't. It is not minimal, *complete* nor *verifiable*. – Rudy Velthuis Mar 18 '18 at 20:20
  • @RudyVelthuis and now? thank you – Passella Mar 18 '18 at 20:49
  • @DavidHeffernan and now? thank you – Passella Mar 18 '18 at 20:50
  • Not minimal. Keep trying. Thanks. – David Heffernan Mar 18 '18 at 20:57
  • @DavidHeffernan I really not know what to do, just put this code in a new project and compile, what's missing that I'm not seeing? Can you tell me? – Passella Mar 18 '18 at 20:59
  • @Passella, much better, but still too much that distracts from the problem and is not required to demonstrate it. As David said: not minimal. – Rudy Velthuis Mar 18 '18 at 21:09
  • 1
    Did you follow the link? [MCVE]. Needs to be both minimal and complete. – David Heffernan Mar 18 '18 at 21:12
  • @DavidHeffernan the code is Minimal, if i remove any line, the coe don't compile, the code need the class operator to compile, The code is Complete, juste paste in your delphi e hit F9, no changes are needed, The code is Verifiable, one button is working, the other not. Where is the fault in my question? – Passella Mar 18 '18 at 21:19
  • 2
    Code is not minimal. We don't need a form. – David Heffernan Mar 18 '18 at 21:30
  • @DavidHeffernan and now? – Passella Mar 18 '18 at 21:31
  • This isn't going anywhere. I'm interested in trying to help you learn. It's not working. – David Heffernan Mar 18 '18 at 21:34
  • @RudyVelthuis already solved my problem, thank you – Passella Mar 18 '18 at 21:44
  • 1
    But you've not learned how to present a problem which disappoints me – David Heffernan Mar 18 '18 at 22:49

1 Answers1

4

The code is still far too complex, but the main problem is that

TMyManager.Create

produces an object (not an interface reference!) with a reference count of 0. If you cast it to the proper interface type, it will get a reference count of 1:

(TMyManager.Create as IManager)

So now this works:

procedure TForm1.btnNotWorkingClick(Sender: TObject);
var
  my: IMyEntity;
begin
  (TMyManager.Create as IManager).produce(my).Show(my.Nome = 'Paulo');
end;

and produces the result 'true'.

That is why your modified code works. You assign the result of Create to an IManager before you call Show, and produce obviously doesn't change the reference count for the manager, while Show (indirectly) does. I didn't try to find out how -- your code is pretty convoluted.

Note that Show is not called, nor its parameter evaluated, before produce gets the chance to initialize my. That is not the problem at all.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • Wonderful, really works, except that it leaves the user's final code a little more verbose. this is just the first day of my study, I believe this is just the beginning, I want something like: people: = TEntityManagerFactory .CreateEntityManager .From(p) .Where((p.Nome = 'Paulo') and (p.Idade = 30))      .AsList ; – Passella Mar 18 '18 at 21:38
  • 1
    So just define `TEntityManagerFactory .CreateEntityManager` not as a constructor, but a class function returning an `IEntityManager` interface. – Arnaud Bouchez Mar 21 '18 at 13:11