EDM : comment utiliser l’Horizontal Entity Splitting
Une des raisons pour lesquelles j’adore l’Entity Framework est la puissance de son mapping.
Beaucoup de développeurs pour ne pas dire la plus part n’en n’ont pas conscience.
Pour rappel, j’ai réalisé des videos (en anglais) sur le mapping.
Certains scenarii sont très souvent très utiles. D’autre le sont moins fréquemment. L’Horizontal Entity Splitting fait parti de ceux-ci. Cependant je vais essayer de démontrer dans ce post qu’il peut malgré tout être très utile dans certains cas.
Prenons Northwind : Products, Orders, OrderDetails. Les tables Orders et Products ont une primary key de type identity. La PK de OrderDetails correspond au couple ProductID, OrderID.
![image_thumb[1] image_thumb[1]](http://blogs.developpeur.org/blogs/matthieu/image_thumb1_063A039C.png)
Maintenant, imaginons que pour des raisons de traçabilité, on décide d’implémenter une suppression logique à la place d’une suppression physique.
Dans les tables Orders et Products, il me suffit de rajouter une colone IsDeleted de type bit non nullable.
Dans mon modèle, j’ajoute une condition IsDeleted = false pour filtrer automatiquement les entités.
![image_thumb[4] image_thumb[4]](http://blogs.developpeur.org/blogs/matthieu/image_thumb4_4FF83FB2.png)
Maintenant on a un problème pour OrderDetails. En effet, le couple OrderID, ProductID doit maintenant être unique… seulement si IsDeleted est faux.
Pour cela, j’ajoute une nouvelle colone OrderID de type uniqueidentifier non nullable avec une default value égale à NewId() puis je définis la Primary Key sur celle-ci.
![image_thumb[7] image_thumb[7]](http://blogs.developpeur.org/blogs/matthieu/image_thumb7_6E525ACE.png)
Ensuite j’ajoute une contrainte d’unicité sur mon ancienne PK en utilisant un index de type Unique Key:
![image_thumb[14] image_thumb[14]](http://blogs.developpeur.org/blogs/matthieu/image_thumb14_2A1EC6F7.png)
Ensuite, j’utilise le script de création de la table pour créer une nouvelle table DeletedOrderDetails sans contrainte (à l’exception des foreign keys):
![image_thumb[9] image_thumb[9]](http://blogs.developpeur.org/blogs/matthieu/image_thumb9_018FBF16.png)
CREATE TABLE [dbo].[DeletedOrderDetails](
[OrderDetailID] [uniqueidentifier] NOT NULL,
[OrderID] [int] NOT NULL,
[ProductID] [int] NOT NULL,
[UnitPrice] [money] NOT NULL,
[Quantity] [smallint] NOT NULL,
[Discount] [real] NOT NULL,
CONSTRAINT [PK_DeletedOrderDetails] PRIMARY KEY CLUSTERED
(
[OrderDetailID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DeletedOrderDetails] WITH NOCHECK ADD CONSTRAINT [FK_DeletedOrderDetails_Orders] FOREIGN KEY([OrderID])
REFERENCES [dbo].[Orders] ([OrderID])
GO
ALTER TABLE [dbo].[DeletedOrderDetails] CHECK CONSTRAINT [FK_DeletedOrderDetails_Orders]
GO
ALTER TABLE [dbo].[DeletedOrderDetails] WITH NOCHECK ADD CONSTRAINT [FK_DeletedOrderDetails_Products] FOREIGN KEY([ProductID])
REFERENCES [dbo].[Products] ([ProductID])
GO
ALTER TABLE [dbo].[DeletedOrderDetails] CHECK CONSTRAINT [FK_DeletedOrderDetails_Products]
GO
Ensuite, je mets à jour mon edmx à partir de ma base afin de changer l’entité OrderDetail, la table [Order Details] dans le SSDL et le MSL entre eux.
Puis, je réduis l’Entity Key de OrderDetail à ma seule propriété OrderDetailID.
![image_thumb[17] image_thumb[17]](http://blogs.developpeur.org/blogs/matthieu/image_thumb17_0CC8AD86.png)
Maintenant, du fait que j’ai une default value différente pour chaque DataRow, je peux affecter la metadata StoreGeneratedPattern à Identity sur cette propriété et cette colonne de façon à laisser la base générer la valeur (A noter que seule celui sur la colonne est utilisé dans ce cas). Je peux le faire avec le designer pour cette propriété mais seulement en modifiant le xml à la main pour la column.
![image_thumb[19] image_thumb[19]](http://blogs.developpeur.org/blogs/matthieu/image_thumb19_5CAE3637.png)
![image_thumb[21] image_thumb[21]](http://blogs.developpeur.org/blogs/matthieu/image_thumb21_7599E0AF.png)
Ce que j’affectionne particulièrement avec cette approche est le fait qu’à l’exception des méthodes Delete je n’ai rien à changer dans mon code.
Maintenant, je vais m’occuper de la suppression.
Je créé un nouveau edmx NorthwindDeletion à partir de la base avec mes quatre tables.
Je veux avoir ce modèle :
![image[44]_thumb image[44]_thumb](http://blogs.developpeur.org/blogs/matthieu/image44_thumb_09834D79.png)
Au niveau de mes entités, je n’ai que faire des contraintes de la base. Je veux une façon simple de supprimer et restorer logiquement une entité et utiliser une propriété booléenne me parait idéal.
Pour avoir ce modèle, après avoir créé mon edmx, je vais supprimer l’entité DeletedOrderDetail en faisant attention à ne pas supprimer la table SSDL mappée dans le SSDL.
Ensuite, après avoir renommé les entités et mapper OrderDetail sur les deux tables d’OrderDetail, je me retrouve avec trois entités : ProductDeletion mappée sur la table Products, OrderDeletion mappée sur Orders et OrderDetailDeletion mappée sur [Order Details] ET DeletedOrderDetails.
Ensuite je vais ajouter une nouvelle propriété booléenne non nullable IsDeleted sur l’entité OrderDetailDeletion.
Maintenant je dois utiliser la propriété OrderDetailDeletion.IsDeleted pour identifier la table. C’est ce qu’on appelle l’Horizontal Entity Splitting. Malheureusement, ce scenario n’est toujours pas couvert par le designer de VS. Je vais donc le faire en modifiant le xml à la main :
![image_thumb[26] image_thumb[26]](http://blogs.developpeur.org/blogs/matthieu/image_thumb26_181DD391.png)
Voilà. Si OrderDetailDeletion.IsDeleted passe à true, ça impliquera un Delete From [Order Details] et un insert sur DeletedOrderDetails (et inversement si OrderDetailDeletion.IsDeleted passe à false) lors de l’appel à la méthode SaveChanges du context.
EF rocks!
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :