danieleteti / delphi-orm

DORM, the "Delphi ORM"
175 stars 65 forks source link

Attribute mapping #6

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
Support for mapping classes using attributes instead of an external json 
mapping.

Maybe there will be situations where a mapping is dificult/impossible to 
express via attributes, but for these cases the programmer can always use the 
standard json mapping. As for the most cases, I think a good set of attributes 
will do the job much better than the json mapping.

For exemple, this class:

type
  TCustomer = class
  private
    FId: Integer;
    FName: String;
    FAge: Integer;
  public
    property Id: Integer read FId write FId;
    property Name: String read FName write FName;
    property Age: Integer read FAge write FAge;
  end;

Could be mapped this way:
type
  [Table('CUSTOMER')]
  TCustomer = class
  private
    [PrimaryKey]
    [Column('ID')]
    FId: Integer;
    [Column('NAME')]
    FName: String;
    [Column('AGE')]
    FAge: Integer;
  public
    property Id: Integer read FId write FId;
    property Name: String read FName write FName;
    property Age: Integer read FAge write FAge;
  end;

And if http://code.google.com/p/delphi-orm/issues/detail?id=5 got implemented, 
the class with it's complete mapping would be reduced to:
type
  TCustomer = class
  private
    [PrimaryKey]
    FId: Integer;
    FName: String;
    FAge: Integer;
  public
    property Id: Integer read FId write FId;
    property Name: String read FName write FName;
    property Age: Integer read FAge write FAge;
  end;

Original issue reported on code.google.com by magn...@gmail.com on 17 Nov 2011 at 6:58

GoogleCodeExporter commented 9 years ago
If you have interest on put this on the project, I could contribute with some 
work

Original comment by magn...@gmail.com on 18 Nov 2011 at 1:24

GoogleCodeExporter commented 9 years ago
I have interest too, and i know magno. we can work together on this part!

Original comment by mrbar2000@gmail.com on 18 Nov 2011 at 1:28

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
If someone were to implement this feature, I was thinking of this:

type
  Table = class(TCustomAttribute)
  private
    FTableName: String;
  public
    constructor Create(const TableName: String);
    property TableName: String read FTableName;
  end;
  IMappingStrategy = interface
    // guid
    function GetTableName(const AType: TRttiType): String;
  end;

  TCoCMappingStrategy = class(TInterfacedObject, IMappingStrategy)
    { Return the classname on uppercase and without the 'T' prefix }
    function GetTableName(const AType: TRttiType): String;
  end;

  TAttributeMappingStrategy = class(TInterfacedObject, IMappingStrategy)
    { If the class has a 'Table' attribute, returns it's 'TableName' property.
      Return empty string otherwise }
    function GetTableName(const AType: TRttiType): String;
  end;

  TExternalMappingStrategy = class(TInterfacedObject, IMappingStrategy)
    { Read from the external mapping file, as DORM does today }
    function GetTableName(const AType: TRttiType): String;
  end;

  TDelegateMappingStrategy = class(TInterfacedObject, IMappingStrategy)
    { An array of the strategies that can be used by the app to solve a given mapping. }
    FStrategies: TArray<IMappingStrategy>;
    constructor Create(const Strategies: array of IMappingStrategy);
    { This will iterate over FStrategies and try all of them to resolve the table name.
      The first one that returns something will be used.
      So, the array FStrategies must contain, in this order:
      TExternalMappingStrategy
      TAttributeMappingStrategy
      TCocMappingStrategy

      So: The attributes mapping will be used only if the mapping we're looking for doesn't exist on the external file. And the CoC mapping will be used only if the mapping we're looking for doesn't exist on both the external file and on the attributes }
    function GetTableName(const AType: TRttiType): String;
  end;

At TSession creation, maybe the programmer could even supply an 
IMappingStrategy of his own, this could be useful e.g.:
1. To customize the conventions. Maybe the programmer wants TCustomer to be 
mapped to TBL_CUSTOMER instead of CUSTOMER
2. To reuse existing mapping code that were used on other framework. The 
programmer may be moving from another ORM to DORM, and want DORM to use all the 
existing mapping code

Another advantage of this approach is that it completely separate mapping 
resolution code from the framework core, allowing all the mapping strategies to 
be very easily unit tested.

After all, it will also solve Issue 5 :)

Original comment by magn...@gmail.com on 18 Nov 2011 at 10:23

GoogleCodeExporter commented 9 years ago
The new ideas for mapping are very good guys. Thanks for this suggestions. 
However, I'm currently focusing on the CORE engine of dorm to do some 
optimization and enhance the functionalities. So, if you are available to 
contribute to dorm, I'd very happy. If you (magnomp and  mrbar2000) are 
interested to become controbutors, drop me an email with your google account.
We have to identify a good way to do database generation when attribute or CoC 
mapping is used. Currently, the dormDatabaseCreator, use the mapping file to 
create the database.

Original comment by daniele....@gmail.com on 22 Nov 2011 at 5:03

GoogleCodeExporter commented 9 years ago
Issue 5 has been merged into this issue.

Original comment by daniele....@gmail.com on 22 Nov 2011 at 5:04

GoogleCodeExporter commented 9 years ago
Humm.. I haven't even noticed this database creator.

Does it realy has to be an standalone app?
Instead of having database creator as a standalone app, we could have an api 
built into DORM that exports an SQL script based on all the active mappings 
(json, coc, attributes, etc)

Original comment by magn...@gmail.com on 22 Nov 2011 at 5:30

GoogleCodeExporter commented 9 years ago
Yes, it could, but...
let's say we have a CI environment and you a need a fast and standard way to 
recreate your database before run unittests. You should create another project 
that use the same BO and the same mapping only to create the database. The 
external app should be simpler to use. Another solution could be to allows to 
DORM to generate a file mapping startring from the current run-time mapping. 
So, that file could be used to generate the database using the dorm database 
creator. In thi way you could also FREEZE the mapping using an extenral file 
generated from file/attributes/CoC. What do you think about?

Original comment by daniele....@gmail.com on 22 Nov 2011 at 6:06

GoogleCodeExporter commented 9 years ago
You mean, DORM will read the mappings (file/attributes/coc) at runtime and 
generate a mapping file similar to the mapping files we use today? And this 
generated file will be the input for the database creator app?

Original comment by magn...@gmail.com on 22 Nov 2011 at 6:37

GoogleCodeExporter commented 9 years ago
Yes, exactly.

Original comment by daniele....@gmail.com on 22 Nov 2011 at 6:44

GoogleCodeExporter commented 9 years ago
It's ok for me

Original comment by magn...@gmail.com on 22 Nov 2011 at 6:53

GoogleCodeExporter commented 9 years ago
+1 to the idea of attributes.
May be we can add a property to TableAttribute for the Table schema:

Table = class(TCustomAttribute)
  private
    FTableName: String;
    FSchemaName: String; 
  public
    constructor Create(const TableName: String);
    property TableName: String read FTableName;
    property SchemaName: String read FSchemaName;
  end;

So it's easier to generate SQL for SQLServer, because if you have multiple 
schema in the database the sql must be qualified, eg: "SELECT Name FROM 
DBO.CUSTOMER"

Original comment by crego...@gmail.com on 24 Nov 2011 at 8:04

GoogleCodeExporter commented 9 years ago
+1 on attributed mapping.
Attributed mapping would keep the mapping close to the class definition itself 
(where it belongs), but it should be defined with independent data type 
definitions. Each database has its own datatypes unfortunately. In zeoslib 
(database independent data access components) this was fixed defining data 
types and then mapping those datatypes to each database specific one (in our 
case the strategies). That being said, means that the metadata mapping should 
be done completely database independent and then let the strategies work out 
how to handle it specifically for their database. That way I can load objects 
from a FireBird database and save the same objects back to a SQL Server 
database, making database conversions really easy.
The way mapping should work should be able to be forced from the cfg file.

Attribute mapping also allows reverse engineering from existing databases. You 
can easily create class definitions from existing database tables, complete 
with attributes. (I see an IDE expert coming up that can connect to a database 
and import your classes, complete with attributed metadata mappings :)).

Adding a "tableschema" would hook the persistence too hardly into the strategy 
being used.

Let me know if you guys need help on this. I'd be happy to contribute.

Original comment by braveco...@gmail.com on 28 Dec 2011 at 6:03

GoogleCodeExporter commented 9 years ago

Original comment by daniele....@gmail.com on 3 Jan 2012 at 1:58

GoogleCodeExporter commented 9 years ago
There is a problem with the transient fields.
If you define a field as "transient" with attribues it is anyway the field is 
mapped throug the CoC.

Original comment by daniele....@gmail.com on 30 Jan 2012 at 2:29

GoogleCodeExporter commented 9 years ago

Original comment by daniele....@gmail.com on 5 Feb 2013 at 2:57