Global Filters
With Aurelius you can define filters that are applied to several entities at once.
You can specify the filters using the FilterDef
attribute once in any entity of your model, then add a Filter
attribute for each entity you want to be filtered. For example:
[Entity, Automapping]
[FilterDef('Multitenant', '{TenantId} = :tenantId')]
[FilterDefParam('Multitenant', 'tenantId', TypeInfo(string))]
[Filter('Multitenant')]
TProduct = class
private
FId: Integer;
FName: string;
FTenantId: string;
public
property Id: Integer read FId write FId;
property Name: string read FName write FName;
property TenantId: string read FTenantId write FTenantId;
end;
And finally, enable filters at the
TObjectManager level, using EnableFilter
method:
Manager.EnableFilter('Multitenant')
.SetParam('tenantId', 'microsoft');
Products := Manager.Find<TProduct>.OrderBy('Name').List;
Once you do that, the SELECT
statement built by Aurelius to retrieve the products will include the filter condition specified in the attribute. The above code would generate an SQL statement like this:
SELECT A.NAME, A.ID, A.TENANT_ID
FROM PRODUCT A
WHERE A.TENANT_ID = :p0
ORDER BY A.NAME;
:p0 = 'microsoft'
This will apply for all entities with the Multitenant
filter defined, even if they are associations.
This makes it very easy to build multitenant applications, for example. You don't have to worry about adding filters to every Aurelius query you build. Just code it with the regular business logic, and Aurelius global filter will take of adding the filters that apply globally to all entities.
Creating filter definitions
To create a filter definition, use the FilterDef
and FilterDefParam
attributes:
[FilterDef('Multitenant', '{TenantId} = :tenantId')]
[FilterDefParam('Multitenant', 'tenantId', TypeInfo(string))]
TProduct = class
The first parameter of FilterDef
attribute is the filter name. The second parameter is optional, and contains the filter condition that will be applied to all entities that don't explicitly add a filter condition in their Filter
attribute.
Note
Filter definitions are global to a model. Do not add two or more FilterDef
attributes with the same filter name, even in different entities
Filter conditions and parameters
Filter conditions are SQL condition expressions that are added to the WHERE clause of any SELECT statement used to retrieve entities. They will also be applied when the entity is being queried as an associated object.
[FilterDef('Deleted', '{Deleted} = 0')]
You should use aliases to refer to database columns, with the class field (or property) name wrapped by brackets ({ }
), in the same you will use aliases in SQL conditions .
Filter conditions can also use parameters, that are defined by prefixing the parameter name with :
(colon). In the following example, tenantId
is a parameter.
[FilterDef('Multitenant', '{Multitenant} = :tenantId')]
For each parameter in the filter condition, you must explicitly add a FilterDefParam
attribute in addition to the FilterDef
attribute, specifying the Delphi type of the parameter. In this example, tenantId
parameter is of type string
:
[FilterDefParam('Multitenant', 'tenantId', TypeInfo(string))]
First argument of FilterDefParam
is the filter name, second is the parameter name, third is the Delphi type of the parameter.
Applying filters to entities
Once a filter is defined, you can specify which entities might have such filter applied. This is as simple as adding a Filter
attribute to any entity you want the filter to be applied, specifying the filter name as the first argument:
[Filter('Multitenant')]
[Filter('Deleted')]
TProduct = class
{...}
[Filter('Multitenant')]
TCustomer = class
In the above example, filters Multitenant
and Delete
will be applied to entity Product
when they are enabled. On the other hand, TCustomer
entity will only have filter Multitenant
applied to it. Filter Deleted
will have no effect on entity Customer
.
Warning
If you are using an inheritance strategy, you should only apply filters to then entity class which is the root of the class hierarchy. Do not apply the filter in a descendant class.
If a filter is applied to a descendant class in a class hierarchy, Aurelius might bring wrong results if such classes as retrieved as associated objects.
You can also override the default filter condition specified in the filter definition, by passing a new filter condition as the second parameter:
[Filter('Multitenant', {AnotherTenantId} = :tenantId)]
TOtherClass = class
The filter condition must use the same parameters specified in the filter definition.
Enabling filters
Finally, filters are enabled at the
TObjectManager level, using EnableFilter
method.
Manager.EnableFilter('Delete');
Products := Manager.Find<TProduct>.OrderBy('Name').List;
Once you enable a filter for a specific object manager, all SQL query statements executed by that manager will have the filter condition added to the WHERE clause when searching for the involved entity(ies).
Warning
Filters are always disabled by default. If you don't call EnableFilter
to enable a specific filter, no conditions are applied to any entity using that filter!
If the filter definition includes a condition that uses parameters, you must set the value of each parameter of the filter using SetParam
method:
Manager.EnableFilter('Multitenant')
.SetParam('tenantId', 'microsoft');
You can also disable a filter using DisableFilter
method and check if a filter is enabled using FilterEnabled
:
if Manager.FilterEnabled('Multitenant') then
Manager.DisableFilter('Multitenant');
Filter enforcer
When you enable a filter, it "only" applies the SQL WHERE condition to SELECT statements, making all your data automatically filtered. But it doesn't do anything when you insert, update or delete a database record.
Fortunately, Aurelius provides mechanism named "filter enforcer" which does that for you: it makes sure that whenever you modify entity data (create, update, delete), it will be consistent with the condition specified in the filter.
In other words, considering the Multitenant
filter example: if you have enabled the Multitenant
filter to retrieve data when tenantId
is equals to microsoft
, the filter enforcer will make sure that you also don't create, update or delete an entity if TenantId
property is not microsoft
.
Use the following code to activate the filter enforcer:
uses {...}, Aurelius.Mapping.FilterEnforcer;
// variable/class field declaration
Enforcer: TFilterEnforcer;
// creating and activating filter enforcer
Enforcer := TFilterEnforcer.Create('Multitenant', 'TenantId', 'FTenantId');
Enforcer.Activate(TMappingExplorer.Default);
// deactivating and destroying filter enforcer
Enforcer.Deactivate(TMappingExplorer.Default);
Enforcer.Free;
You can see that the TFilterEnforcer
class constructor receives three parameters: The first is the filter name (Multitenant
), the second is the name of a filter condition parameter, the third is the name of the class member (field or property) that will be checked against the parameter value.
Also, when you activate a filter enforcer, you must specify to which mapping explorer (model) it will be applied. The above example is using the default model.
The enforcer will subscribe to key model events. Whenever an entity is about to be inserted, updated or deleted, the enforcer will check if:
- If the specified filter is active (
Multitenant
); - If yes, check if the value of specified class member (
FTenantId
) matches the value of filter parameter (tenantId
).
If the condition 2 above is tested and fails, an exception will be raised. Optionally you can ask the enforcer to auto comply to the value in either insert and/or update operations:
Enforcer.AutoComplyOnInsert := True;
Enforcer.AutoComplyOnUpdate := True;
When the two properties above are enabled, the enforcer will check if the class member (FTenantId
) is empty. If it is, then instead of raising an error when the filter is enabled, it will automatically set the member value using the parameter value. In other words: if a Multitenant
filter is active with tenantId
parameter set to microsoft
, if you try to insert or update a record without specifying the TenantId
property, the enforce will automatically fill it with the microsoft
value for you.