[EF] Obtenir automatiquement un nouveau Guid sur une clé primaire

Publié le 07 avril 2009 par Jeremy.jeanson

Quiconque à voulu un jour utiliser des Guid comme clé primaire avec Entity Framework a constaté que celui-ci ne savait pas obtenir du servir SQL un valeur par défaut. Souci qui peut sembler relativement anodin mais qui deviens vite très pénalisant quand votre BDD n’utilise que de Guid. On peut trouver un peu partout sur le web des méthodes consistant à modifier l’EDM afin d’utiliser des tableaux de bits, mais en soit rien de vraiment léger et parfois même un peu hasardeux.

Pour palier à ce souci j’ai réalisé une méthode d’extension qui a pour mission d’ajouter une entité nouvellement créé à un contexte de données et dans le cas où celle-ci contient une clé primaire sous forme de Guid, elle créé un Guid en testant que celui-ci n’est pas déjà utilisé. On se retrouve alors avec un Guid généré automatiquement et ceci sans modifier ou altérer notre model.

Voila donc un code qui va certainement servir à plus d’un ;)

Note : seule limitation existante (réalisée en fait par précaution pour mon usage), le jeu d’entité du context doit avoir un nom commençant par le nom du type de l’entité manipulée. Cette limitation peu bien entendu sauter si vous modifiez le code.

Vb

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Data.EntityClient
Imports System.Data.Objects
Imports System.Data.Objects.DataClasses
Imports System.Reflection
Imports System.Runtime.CompilerServices

Namespace mlEntity

    Public Module EntityTypeExtension

        ''' <summary>
        ''' Ajout d'un objet à un context et création automatique d'un Guid pour une PK
        ''' </summary>
        ''' <typeparam name="T"></typeparam>
        ''' <param name="context"></param>
        ''' <param name="entity"></param>
        ''' <remarks></remarks>
        <Extension()> _
        Public Sub Add(Of T As EntityObject)(ByVal context As ObjectContext, ByVal entity As T)
            ' Déterminer de la propriété qui correspond au jeux d'entités de type T
            Dim jeuProperty As PropertyInfo = context.GetType().GetProperties() _
                .FirstOrDefault(Function(c) c.PropertyType Is GetType(ObjectQuery(Of T)) _
                                    AndAlso c.Name.StartsWith(GetType(T).Name))

            If jeuProperty IsNot Nothing Then

                ' Déterminer la propriété du Type T qui est la clé GUID (si il y en a une)
                Dim keyProperty As PropertyInfo = GetType(T).GetProperties() _
                    .FirstOrDefault(Function(c) _
                        c.PropertyType Is GetType(Guid) _
                        AndAlso (CType(c.GetCustomAttributes(GetType(EdmScalarPropertyAttribute), True).FirstOrDefault(), EdmScalarPropertyAttribute).EntityKeyProperty))

                ' Déterminer si le type T a un clé de type Guid
                If keyProperty IsNot Nothing Then
                    ' Recherche du jeu d'entités qui serra manipuler pour tester le id
                    Dim jeu As ObjectQuery(Of T) = CType(jeuProperty.GetValue(context, Nothing), ObjectQuery(Of T))

                    ' Id qui serragénéré
                    Dim id As Guid

                    ' Méthode utilisée pour tester la validité de l'ID
                    Dim test As Func(Of T, Boolean)

                    Do                    
                        ' Créer un Guid
                        id = Guid.NewGuid()

                        ' Préparation du test
                        test = Function(c) New Guid(keyProperty.GetValue(c, Nothing).ToString()) = id

                        ' Tester si le guid est déjà utilisé
                    Loop While (jeu.FirstOrDefault(test) IsNot Nothing)

                    ' Utilisation de l'Id
                    keyProperty.SetValue(entity, id, Nothing)
                End If

                ' Ajout de l'objet au context
                context.AddObject(jeuProperty.Name, entity)

            Else
                Throw New Exception("Jeu d'entités introuvable dans le context de données.")
            End If

        End Sub

    End Module

End Namespace

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Reflection;

namespace mlEntity
{
    public static class EntityTypeExtension
    {
        /// <summary>
        /// Ajout d'un objet à un context et création automatique d'un Guid pour une PK
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="context"></param>
        /// <param name="entity"></param>
        public static void Add<T>(this ObjectContext context, T entity) where T : EntityObject
        {
            // Déterminer de la propriété qui correspond au jeux d'entités de type T
            PropertyInfo jeuProperty = context.GetType().GetProperties()
                .FirstOrDefault(c => c.PropertyType == typeof(ObjectQuery<T>)
                & c.Name.StartsWith(typeof(T).Name));

            if (jeuProperty != null)
            {
                // Déterminer la propriété du Type T qui est la clé GUID (si il y en a une)
                PropertyInfo keyProperty = typeof(T).GetProperties()
                    .FirstOrDefault(c =>
                        c.PropertyType == typeof(Guid)
                        & (c.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), true).FirstOrDefault() as EdmScalarPropertyAttribute).EntityKeyProperty
                        );

                // Déterminer si le type T a un clé de type Guid
                if (keyProperty != null)
                {
                    // Recherche du jeu d'entités qui serra manipuler pour tester le id
                    ObjectQuery<T> jeu = jeuProperty.GetValue(context, null) as ObjectQuery<T>;

                    // Id qui serragénéré
                    Guid id;

                    // Méthode utilisée pour tester la validité de l'ID
                    Func<T, Boolean> test;
                    do
                    {
                        // Créer un Guid
                        id = Guid.NewGuid();

                        // Préparation du test
                        test = c =>
                            new Guid(keyProperty.GetValue(c, null).ToString()) == id;

                        // Tester si le guid est déjà utilisé
                    } while (jeu.FirstOrDefault(test) != null);

                    // Utilisation de l'Id
                    keyProperty.SetValue(entity, id, null);
                }

                // Ajout de l'objet au context
                context.AddObject(jeuProperty.Name, entity);
            }
            else
            {
                throw new Exception("Jeu d'entités introuvable dans le context de données.");
            }
        }

    }
}