ManyToOne associations Transient capability.
Save All saves both OneToMany and ManyToOne related classes. Of course with a method named SaveAll that is what you would expect, and so this is excellent.
But for us there are times when you want all the one to many related classes to save but not the manyToOne relations.
We use the manyToOne relations to provide lookup data into a given class but don't want it to save when we save that same class.
For instance look at the following definition: the TSubsidiaryAssociate is a ManyToOne but it is just there so that we can lookup SBSNo and SBSName and provide these fields as part of the Vendor Payload, We never want this to save when saving a vendor as Subsidiaries are lookup data that are seeded by an administration console not by a vendor being saved.
[Entity]
[Table('VENDOR', 'rps')]
[Sequence('Select GetSid as Sid from dual')]
TVendor = class
private
FSid: Int64;
FVendCode: string;
FActive: Int16;
FVendName: Nullable<string>;
FVendId: Nullable<Integer>;
FFirstName: Nullable<string>;
FLastName: Nullable<string>;
FSbsSid: Nullable<Int64>;
[ManyToOne(false, [ckCascadeAll], 'SbsSid')]
FSubsidiary : TSubsidiaryAssociate; < -- DONT WANT THIS TO SAVE
[OneToMany(False, [ckCascadeAll])]
FVendorContactAddresses: Lazy<IList<TVendorContactAddress>>;
[OneToMany(False, [ckCascadeAll])]
FVendorEmails: Lazy<IList<TVendorEmail>>;
[OneToMany(False, [ckCascadeAll])]
FVendorPhones: Lazy<IList<TVendorPhone>>;
[OneToMany(False, [ckCascadeAll])]
FVendorTerms: Lazy<IList<TVendorTerm>>;
function GetSbsName: Nullable<string>;
function GetSbsNo: Int16;
procedure SetSbsName(const Value: Nullable<string>);
procedure SetSbsNo(const Value: Int16);
public
constructor Create; virtual;
destructor Destroy; override;
[Column('SID',[cpRequired,cpNotNull,cpPrimaryKey],19,0)]
property Sid: Int64 read FSid write FSid;
[Column('VEND_CODE',[cpRequired,cpNotNull],6)]
property VendCode: string read FVendCode write FVendCode;
[Column('ACTIVE',[cpRequired,cpNotNull],1,0)]
property Active: Int16 read FActive write FActive;
[Column('VEND_NAME',[],25)]
property VendName: Nullable<string> read FVendName write FVendName;
[Column('VEND_ID',[],10,0)]
property VendId: Nullable<Integer> read FVendId write FVendId;
[Column('FIRST_NAME',[],30)]
property FirstName: Nullable<string> read FFirstName write FFirstName;
[Column('LAST_NAME',[],30)]
property LastName: Nullable<string> read FLastName write FLastName;
[Column('SBS_SID',[],19,0)]
property SbsSid: Nullable<Int64> read FSbsSid write FSbsSid;
property SbsNo: Int16 read GetSbsNo write SetSbsNo;
property SbsName: Nullable<string> read GetSbsName write SetSbsName;
property VendorContactAddresses: IList<TVendorContactAddress> read getVendorContactAddresses;
property VendorEmails: IList<TVendorEmail> read getVendorEmails;
property VendorPhones: IList<TVendorPhone> read getVendorPhones;
property VendorTerms: IList<TVendorTerm> read getVendorTerms;
end;
Therefore in order to Mark certain ralationships as not savable I would like to suggest a TransientAttribute that can be added to those related classes that you don't want to save, even when calling Save All.
I have tried this and it works great. I Made the following two changes.
In Spring.Persistence.Mapping Attributes I added a transient Attribute based on TCustomAttribute
TransientAttribute = class(TCustomAttribute);
In Spring.Persistence.Mapping.RttiExplorer I modified the GetRelationsOf method to ignore those relations marked as Transient. This method is currently only being used by the SaveAll process so for me it made sense to add it here. Maybe not the right place if you plan to use this method in other places. In that case maybe something similar to this that would accomplish this same goal. of ignoring relations marked transient.
class function TRttiExplorer.GetRelationsOf(AEntity: TObject; relationAttributeClass: TAttributeClass): IList<TObject>;
var
LType: TRttiType;
LField: TRttiField;
LProperty: TRttiProperty;
LEntities: IList<TObject>;
begin
Result := TCollections.CreateList<TObject>;
LType := FRttiCache.GetType(AEntity.ClassType);
for LField in LType.GetFields do
begin
if (LField.HasCustomAttribute(relationAttributeClass)) and
(not LField.HasCustomAttribute(TransientAttribute)) then <-- Added this Line
begin
LEntities := GetSubEntityFromMemberDeep(AEntity, LField);
if LEntities.Any then
Result.AddRange(LEntities);
end;
end;
for LProperty in LType.GetProperties do
begin
if LProperty.HasCustomAttribute(relationAttributeClass) then
begin
LEntities := GetSubEntityFromMemberDeep(AEntity, LProperty);
if LEntities.Any then
Result.AddRange(LEntities);
end;
end;
end;
So now with this new Attribute I can do the following and the Subsidiary is not saved when I call SaveAll
[Transient]
[ManyToOne(false, [ckCascadeAll], 'SbsSid')]
FSubsidiary : TSubsidiaryAssociate;
I think this is a feature that others would also use, and so I would like to suggest it as an enhancement. What do you think?
Comments (3)
-
reporter -
repo owner - changed milestone to 1.2
-
- changed status to resolved
fixes issue
#94Introduced Transient attribute.→ <<cset 0a7fa957b640>>
- Log in to comment
BTW. If there is a better way to include lookup data in a class from another class, I would greatly appreciate knowing how you would suggest doing this.