-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdata-filters.html
184 lines (150 loc) · 14 KB
/
data-filters.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<link type="text/css" rel="stylesheet" href="bootstrap.min.css" />
<style type="text/css">
.auto-style1 {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="document-contents">
<h3 id="DocIntro">Introduction</h3>
<p>Il est courant d'utiliser le mécanisme de <a href="/Pages/Documents/Entities#DocSoftDelete">
<strong>soft-delete</strong></a> qui ne supprime pas une entité mais la marque simplement comme "effacée". Si une entité porte ce marqueur, elle ne devrait donc jamais apparaître par accident dans l'application. Pour s'assurer de ça, nous pourrions ajouter, pour chaque select, une restriction de type "<strong>where</strong> IsDeleted = false". C'est ennuyant mais surtout risqué en cas d'oubli. Il faut donc gérer cela de manière automatique.</p>
<p>ASP.NET Boilerplate fournit des <strong>filtres sur les données</strong> qui peuvent filtrer automatiquement les entités en respectant quelques conventions. Il existe des filtres prédéfinis, mais vous pouvez créez facilement des filtres personnalisés.</p>
<h3 id="DocPredefinedFilters">Filtres prédéfinis</h3>
<h4 id="DocSoftDelete">ISoftDelete</h4>
<p>La filtre Soft-delete est utilisé pour filtrer automatiquement (enlever des résultats) les entités marquées comme effacées lors des requêtes en base de données. Si une <a href="/Pages/Documents/Entities">entité</a> souhaite bénéficier du "soft-deleted", elle doit alors implémenter l'interface <strong>ISoftDelete</strong> qui définit uniquement une propriété <strong>IsDeleted</strong>. Exemple:</p>
<pre lang="cs">public class Person : Entity, ISoftDelete
{
public virtual string Name { get; set; }
public virtual bool IsDeleted { get; set; }
}</pre>
<p>L'entité <strong>Person</strong> n'est pas effacée actuellement de la base de données, sauf si la propriété <strong>IsDeleted </strong>est définie à vrai. Cette affectation est réalisée automatiquement par ASP.NET Boilerplate lorsque vous utilisez la méthode <strong>
<a href="/Pages/Documents/Repositories#DocDeleteEntity">IRepository.Delete</a></strong> (vous pouvez affecter manuellement sa valeur à vrai, mais la méthode Delete est l'usage préconisé et c'est plus naturel)</p>
<p>En implémentant ISoftDelete, les requêtes ne ramèneront plus les résultats marqués comme effacés. Voici un exemple d'une classe qui utilise l'entrepôt Person pour obtenir la liste de toutes les personnes:</p>
<pre lang="cs">public class MyService
{
private readonly IRepository<Person> _personRepository;
public MyService(IRepository<Person> personRepository)
{
_personRepository = personRepository;
}
public List<Person> GetPeople()
{
return _personRepository.GetAllList();
}
}</pre>
<p>La méthode GetPeople récupère uniquement les personnes qui ont IsDeleted à false (non supprimées). Toutes les méthodes de l'entrepôt mais aussi les propriétés de parcours fonctionneront proprement. Nous pouvons ajouter d'autres restrictions, d'autres jointures, etc. La condition IsDeleted = false sera toujours ajoutée proprement dans la requête SQL générée.</p>
<div class="bs-callout bs-callout-warning">
<h4>Quand est-ce activé ?</h4>
<p>Le filtre ISoftDelete est toujours activé à moins que vous le désactiviez explicitement.</p>
</div>
<p><span class="auto-style1">nb</span>: Si vous implémentez <a href="/Pages/Documents/Entities#DocSoftDelete">IDeletionAudited</a> (qui étend SoftDelete) le moment de la suppression et son auteur seront alors automatiquement gérés par ASP.NET Boilerplate.</p>
<h4 id="DocMustHaveTenant">IMustHaveTenant</h4>
<p>Si vous concevez des applications multi-clients ("multi-tenants" = stockage de données de différents clients dans une même bases de données), vous ne souhaitez surtout pas qu'un client puisse voir les données d'un autre, accidentellement. Vous pouvez alors implémenter <strong>IMustHaveTenant</strong> dans ce cas. Exemple:</p>
<pre lang="cs">public class Product : Entity, <strong>IMustHaveTenant</strong>
{
<strong>public int TenantId { get; set; }</strong>
public string Name { get; set; }
}</pre>
<p><strong>IMustHaveTenant</strong> définit <strong>TenantId</strong> qui permet de faire la distinctions entre les entités des différentes clients. ASP.NET Boilerplate utilise
<a href="/Pages/Documents/Abp-Session">IAbpSession</a> pour obtenir l'identifiant TenantId
courant et filtre automatiquement les requêtes en fonction du client courant.</p>
<div class="bs-callout bs-callout-warning">
<h4>Quand est-ce activé?</h4>
<p>IMustHaveTenant est toujours activé par défault.</p>
<p>Si l'utilisateur courant n'est pas authentifié dans l'application ou si l'utilisateur courant est un utilisateur <strong>hôte</strong> (L'utilisateur hôte est un utilisateur ayant pour rôle d'administrer les clients et leurs différentes données), ASP.NET Boilerplate <strong>
désactive</strong> automatiquement le filtre IMustHaveTenant dans ce cas. Toutes les données seront alors récupérées par l'application. Rappelez vous bien qu'il ne s'agit pas là d'une question de sécurité ; vous devez continuer de gérer spécifiquement <a href="/Pages/Documents/Authorization">les autorisations</a> permettant à un utilisateur d'accéder à des données sensibles.</p>
</div>
<h4 id="DocMayHaveTenant">IMayHaveTenant</h4>
<p>Si une entité est partagée par différents clients et par l'hôte (cela signifie que l'entité doit être la propriété d'un client ou de l'hôte), vous pouvez utiliser le filtre IMayHaveTenant. L'interface <strong>
IMayHaveTenant</strong> féfinit <strong>TenantId</strong> mais cette propriété est <strong>
nullable</strong>.</p>
<pre lang="cs">public class Role : Entity, <strong>IMayHaveTenant</strong>
{
<strong>public int? TenantId { get; set; }</strong>
public string RoleName { get; set; }
}</pre>
<p>Une valeur <strong>nule</strong> signifie que c'est une entité propriété d'un <strong>hôte</strong>, alors qu'une valeur <strong>non-nule</strong> implique qu'elle appartient à un <strong>client</strong> en rapport avec l'id spécifié. ASP.NET Boilerplate utilise <a href="/Pages/Documents/AbpSession">IAbpSession</a> pour obtenir la valeur TenantId courante.
Le filtre IMayHaveTenant n'est pas aussi strict que IMustHaveTenant. Mais vous pouvez en avoir besoin pour des structures potentiellement communes entre hôtes et clients.</p>
<div class="bs-callout bs-callout-warning">
<h4>Quant est-ce activé?</h4>
<p>IMayHaveTenant est toujours activé à moins que vous ne le désactiviez explicitement.</p>
</div>
<h3 id="DocDisableFilters">Désactiver les filtres</h3>
<p>Vous pouvez désactiver un filtre au sein d'une <a href="/Pages/Documents/Unit-Of-Work">unité de travail</a> en utilisant la méthode <strong>DisableFilter</strong> comme indiqué ci-dessous:</p>
<pre lang="cs">var people1 = _personRepository.GetAllList();
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
{
var people2 = _personRepository.GetAllList();
}
var people3 = _personRepository.GetAllList();</pre>
<p>La méthode DisableFilter attend un ou plusieurs noms de filtre en paramètres.
AbpDataFilters.SoftDelete est une constante de type string qui contient le nom du filtre standard "soft delete" d'ASP.NET Boilerplate.</p>
<p><strong>people2</strong> récupère les instances supprimées de people alors que people1 et people3 ne les récupèrent pas. L'usage du mot clef <strong>using </strong>permet de désactiver un filtre au sein d'un <strong>bloc de code</strong>.
Si vous n'utilisez pas le mot clef "using", le filtre sera désactivé jusqu'à la fin de l'unité de travail, à moins que vous ne le réactiviez explicitement.</p>
<p>Vous pouvez injecter <strong>IUnitOfWorkManager</strong> et l'utiliser comme dans l'exemple. Vous pouvez aussi utiliser la propriété <strong>CurrentUnitOfWork</strong> comme un raccourci dans le service applicatif (qui dérive de la classe ApplicationService).</p>
<div class="bs-callout bs-callout-warning">
<h4>A propos du mot clef "using"</h4>
<p>Si un filtre est activé lorsque vous appelez la méthode DisableFilter avec le mot clef "using", le filtre est désactivé, et réactivé juste après le bloc using. Mais si le filtre était déjà désactivé avoir le bloc using, DisableFilter ne fera rien et le filtre restera désactivé, même après le bloc using.</p>
</div>
<h3 id="DocEnableFilters">Activation des filtres</h3>
<p>Vous pouvez activer un filtre au sein d'une unité de travail en utilisant la méthode <strong>EnableFilter</strong>, comme avec la méthode (à l'effet inverse) DisableFilter. EnableFilter libère automatiquement la désactivation du filtre.</p>
<h3 id="DocSetFilterParams">Paramétrer un filtre</h3>
<p>Un filtre peut être <strong>parametré</strong>. Le filtre IMustHaveTenant en est un exemple car l'Id du client courant est déterminé au fil de l'exécution. Cela signifie qu'il est possible de changer la valeur du filtre lorsque cela est requis. Exemple:</p>
<pre lang="cs">CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);</pre>
<p>Un autre exemple: Pour affecter la valeur de tenantId au niveau du filtre IMayHaveTenant:</p>
<pre lang="cs">CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);</pre>
<p>La méthode SetFilterParameter fournit aussi un IDisposable. Nous pouvons donc l'utiliser au sein d'un bloc <strong>using</strong> et <strong>restorer l'ancienne valeur</strong> après le bloc using.</p>
<h3 id="DocDefineCustomFilters">Définir des filtres personnalisés</h3>
<p>Pour créer un filtre personnalisé et l'intégrer à ASP.NET Boilerplate, nous devons d'abord définir une interface qui sera implémentée par les entités souhaitant utiliser ce filtre. Imaginons que nous voulons filtrer automatiquement les entités par un identifiant PersonId.
Voici une interface possible:</p>
<pre lang="cs">public interface IHasPerson
{
int PersonId { get; set; }
}</pre>
<p>Ensuite, nous l'implémentons dans nos entités. Exemple:</p>
<pre lang="cs">public class Phone : Entity, IHasPerson
{
[ForeignKey("PersonId")]
public virtual Person Person { get; set; }
public virtual int PersonId { get; set; }
public virtual string Number { get; set; }
}</pre>
<p>Comme ASP.NET Boilerplate utilise <strong>
<a href="https://github.com/jcachat/EntityFramework.DynamicFilters" target="_blank">EntityFramework.DynamicFilters</a></strong>, nous pouvons utiliser ses fonctionnalités pour définir le filtre. Dans notre classe <strong>DbContext</strong>, nous surchargeons<strong> OnModelCreating </strong> et définissons le filtre comme ci-dessous:</p>
<pre lang="cs">protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Filter("PersonFilter", (IHasPerson entity, int personId) => entity.PersonId == personId, 0);
}</pre>
<p>"PersonFilter" est le nom d'identification du filtre mentionné ici. Le second paramètre définit l'interface du filtre et le paramètre sur lequel porte le filtre (inutile si le filtre n'a pas de paramètre), le dernier paramètre est la valeur par défaut de "personId".</p>
<p>Une dernière chose, nous devons enregistrer ce filtre au sein de l'unité de travail d'ASP.NET Boilerplate, et plus précisémment dans la méthode PreInitialize de notre
<a href="/Pages/Documents/Module-System">module</a>:</p>
<pre lang="cs">Configuration.UnitOfWork.RegisterFilter("PersonFilter", false);</pre>
<p>Le premier paramètre est le nom, unique, que nous avons défini précédemment. Le second paramètre indique si le filtre est activé ou désactivé par défaut. Après avoir déclaré ce paramètre, nous pouvons l'utiliser et récupérer ainsi sa valeur courante dans le fil de l'exécution du programme.</p>
<pre lang="cs">using (CurrentUnitOfWork.EnableFilter("PersonFilter"))
{
CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
var phones = _phoneRepository.GetAllList();
//...
}</pre>
<p>Nous pourrions obtenir la valeur courante de personId avec un peu de code au lieu de le coder en dur. L'exemple ci-dessus était pour les filtres paramétrables. Un filtre qui peut attendre zéro ou n paramètres. S'il n'a pas de paramètres, il n'est pas utile d'affecter la valeur au filtre. S'il est activé par défaut, il n'y a donc pas nécessité de l'activer manuellement (nous pouvons le désactiver sans souci).</p>
<div class="bs-callout bs-callout-warning">
<h4>Documentation relative à EntityFramework.DynamicFilters</h4>
<p>Pour plus d'informations sur les filtres dynamiques, reportez vous à la documentation suivante:
<a href="https://github.com/jcachat/EntityFramework.DynamicFilters">https://github.com/jcachat/EntityFramework.DynamicFilters</a>
</p>
</div>
<p>Nous pouvons créer des filtres personnalisés pour gérer la sécurité, les entités d'état actif/archivé et autres.</p>
<h3 id="DicOtherORMs">Autres ORMs</h3>
<p>Le filtrage de données d'ASP.NET Boilerplate implémente EntityFramework et
NHibernate.
Les autres ORMs ne sont pas encore gérés. Cepdendant, vous pouvez imiter ce comportement si vous utilisez systématiquement les classes d'<strong>entrepôts</strong> pour récupérer les données. Dans ce cas, vous pouvez créer une surcharge personnalisée de<strong> GetAll</strong> et des autres méthodes ayant le même but (la récupération de données).</p>
</div>
</body>
</html>