Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Abonnements

Entity cloner

La méthode ApplyPropertyChanges d'ObjectContext est très pratique, elle permet d'enregistrer des modifications apportées sur une entité sans que celle-ci soit rattaché à un ObjectContext. Cette méthode prend deux paramètres : l'entité dans son état initial et l'entité dans son état courant.

Pour conserver l'état initial, il serait intéressant d'avoir une méthode Clone.

ApplyPropertyChanges ne prend pas en compte les propriétés relationnelle, cependant, nous verrons dans un post à venir comment contourner cela, au moins pour les EntityReference.

Donc il va fallois cloner nos entités. Immédiatement, cela me fait penser à la reflection. Le problème de la reflection c'est que c'est lent. Ceux qui ont l'habitude de lire mon blog ont dû comprendre, je vais générer un délégué à la volée afin de ne faire de la reflection qu'une seule fois et d'utiliser du code fortement typé par la suite.

Pour des raisons évidentes de facilité, nous allons définir une extension method Clone :

public static class EntityObjectExtension

{

    public static T Clone<T>(this T entityObject) where T : EntityObject, new()

    {

        return EntityCloner<T>.Clone(entityObject);

    }

}

public static class EntityCloner<T>

    where T : EntityObject, new()

{

    private static bool AllowCloneProperty(PropertyInfo pi)

    {

        return !(typeof(EntityObject).IsAssignableFrom(pi.PropertyType) ||

            pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof(EntityCollection<>));

    }

    public static T Clone(T entityObject)

    {

        Cloner<T>.ClonePropertyDelegate = (pi) => AllowCloneProperty(pi);

        return Cloner<T>.Clone(entityObject);

    }

}

public static class Cloner<T>

    where T : class, new()

{

    private static Func<T, T> _cloneDelegate;

    private static Func<PropertyInfo, bool> _clonePropertyDelegate;

 

    private static Func<T, T> CloneDelegate

    {

        get

        {

            if (_cloneDelegate == null)

                _cloneDelegate = GenerateCloneMethod().CreateDelegate(typeof(Func<T, T>)) as Func<T, T>;

            return _cloneDelegate;

        }

    }

 

    public static Func<PropertyInfo, bool> ClonePropertyDelegate

    {

        get { return _clonePropertyDelegate; }

        set

        {

            if (_clonePropertyDelegate != value)

            {

                _clonePropertyDelegate = value;

                _cloneDelegate = null;

            }

        }

    }

 

    private static DynamicMethod GenerateCloneMethod()

    {

        var dynamicMethod = new DynamicMethod("Clone", typeof(T), new Type[] { typeof(T) });

        var cloneIlGenerator = dynamicMethod.GetILGenerator();

        var value = cloneIlGenerator.DeclareLocal(typeof(T));

        cloneIlGenerator.Emit(OpCodes.Ldarg_0);

        var argNotNullLabel = cloneIlGenerator.DefineLabel();

        cloneIlGenerator.Emit(OpCodes.Brtrue_S, argNotNullLabel);

        cloneIlGenerator.Emit(OpCodes.Ldnull);

        cloneIlGenerator.Emit(OpCodes.Ret);

        cloneIlGenerator.MarkLabel(argNotNullLabel);

        cloneIlGenerator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(new Type[0]));

        cloneIlGenerator.Emit(OpCodes.Stloc_S, value);

        CopyProps(cloneIlGenerator, value, typeof(T), () => cloneIlGenerator.Emit(OpCodes.Ldarg_0));

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, value);

        cloneIlGenerator.Emit(OpCodes.Ret);

        return dynamicMethod;

    }

 

    private static void CopyProps(ILGenerator cloneIlGenerator, LocalBuilder lb, Type typeT, Action getSource)

    {

        foreach (var prop in typeT.GetProperties().Where(p => p.CanRead && p.CanWrite && (ClonePropertyDelegate == null || ClonePropertyDelegate(p))))

        {

            if (typeof(ComplexObject).IsAssignableFrom(prop.PropertyType))

            {

                ConstructorInfo complexObjectCtor = prop.PropertyType.GetConstructor(new Type[0]);

                if (complexObjectCtor != null)

                {

                    cloneIlGenerator.Emit(OpCodes.Ldloc_S, lb);

                    var sourceComplexTypeProp = cloneIlGenerator.DeclareLocal(prop.PropertyType);

                    var valueComplexTypeProp = cloneIlGenerator.DeclareLocal(prop.PropertyType);

                    getSource();

                    cloneIlGenerator.Emit(OpCodes.Callvirt, typeT.GetMethod("get_" + prop.Name));

                    cloneIlGenerator.Emit(OpCodes.Stloc_S, sourceComplexTypeProp);

                    cloneIlGenerator.Emit(OpCodes.Newobj, complexObjectCtor);

                    cloneIlGenerator.Emit(OpCodes.Stloc_S, valueComplexTypeProp);

                    cloneIlGenerator.Emit(OpCodes.Ldloc_S, valueComplexTypeProp);

                    cloneIlGenerator.Emit(OpCodes.Callvirt, typeT.GetMethod("set_" + prop.Name));

                    CopyProps(cloneIlGenerator, valueComplexTypeProp, prop.PropertyType, () => cloneIlGenerator.Emit(OpCodes.Ldloc, sourceComplexTypeProp));

                }

            }

            else if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(EntityReference<>))

                CopyEntityReference(cloneIlGenerator, () => cloneIlGenerator.Emit(OpCodes.Ldloc, lb), typeT, getSource, prop, false);

            else

            {

                cloneIlGenerator.Emit(OpCodes.Ldloc_S, lb);

                getSource();

                cloneIlGenerator.Emit(OpCodes.Callvirt, typeT.GetMethod("get_" + prop.Name));

                cloneIlGenerator.Emit(OpCodes.Callvirt, typeT.GetMethod("set_" + prop.Name));

            }

        }

    }

 

    internal static void CopyEntityReference(ILGenerator cloneIlGenerator, Action loadNewObject, Type typeT, Action getSource, PropertyInfo prop, bool copyNull)

    {

        var sourceEntityReferenceEntityKeyProp = cloneIlGenerator.DeclareLocal(typeof(EntityKey));

        var sourceEntityReferenceEntityKeyMemberProp = cloneIlGenerator.DeclareLocal(typeof(EntityKeyMember));

        var sourceEntityReferenceEntityKeysMemberProp = cloneIlGenerator.DeclareLocal(typeof(EntityKeyMember[]));

        var sourceEntityReferenceEntityKeysMemberLength = cloneIlGenerator.DeclareLocal(typeof(int));

        var valueEntityReferenceProp = cloneIlGenerator.DeclareLocal(prop.PropertyType);

        var valueEntityReferenceEntityKeyProp = cloneIlGenerator.DeclareLocal(typeof(EntityKey));

        var valueEntityReferenceEntityKeysMemberProp = cloneIlGenerator.DeclareLocal(typeof(EntityKeyMember[]));

        var loopIndex = cloneIlGenerator.DeclareLocal(typeof(int));

        loadNewObject();

        cloneIlGenerator.Emit(OpCodes.Newobj, prop.PropertyType.GetConstructor(new Type[0]));

        cloneIlGenerator.Emit(OpCodes.Stloc_S, valueEntityReferenceProp);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, valueEntityReferenceProp);

        cloneIlGenerator.Emit(OpCodes.Callvirt, typeT.GetMethod("set_" + prop.Name));

        getSource();

        cloneIlGenerator.Emit(OpCodes.Callvirt, typeT.GetMethod("get_" + prop.Name));

        cloneIlGenerator.Emit(OpCodes.Callvirt, prop.PropertyType.GetMethod("get_EntityKey"));

        cloneIlGenerator.Emit(OpCodes.Stloc_S, sourceEntityReferenceEntityKeyProp);

        var entityKeyNullLabel = cloneIlGenerator.DefineLabel();

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeyProp);

        cloneIlGenerator.Emit(OpCodes.Brfalse, entityKeyNullLabel);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, valueEntityReferenceProp);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeyProp);

        cloneIlGenerator.Emit(OpCodes.Callvirt, typeof(EntityKey).GetMethod("get_EntityKeyValues")); // We suppose EntityKeyValues is not null

        cloneIlGenerator.Emit(OpCodes.Stloc_S, sourceEntityReferenceEntityKeysMemberProp);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeysMemberProp);

        cloneIlGenerator.Emit(OpCodes.Ldlen);

        cloneIlGenerator.Emit(OpCodes.Conv_I4);

        cloneIlGenerator.Emit(OpCodes.Stloc_S, sourceEntityReferenceEntityKeysMemberLength);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeysMemberLength);

        cloneIlGenerator.Emit(OpCodes.Newarr, typeof(EntityKeyMember));

        cloneIlGenerator.Emit(OpCodes.Stloc_S, valueEntityReferenceEntityKeysMemberProp);

        var noEntityKeyValues = cloneIlGenerator.DefineLabel();

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeysMemberLength);

        cloneIlGenerator.Emit(OpCodes.Brfalse_S, noEntityKeyValues);

        cloneIlGenerator.Emit(OpCodes.Ldc_I4_0);

        cloneIlGenerator.Emit(OpCodes.Stloc_S, loopIndex);

        var startLoopLabel = cloneIlGenerator.DefineLabel();

        cloneIlGenerator.MarkLabel(startLoopLabel);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, loopIndex);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeysMemberLength);

        cloneIlGenerator.Emit(OpCodes.Ceq);

        var endOfLoopLable = cloneIlGenerator.DefineLabel();

        cloneIlGenerator.Emit(OpCodes.Brtrue_S, endOfLoopLable);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeysMemberProp);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, loopIndex);

        cloneIlGenerator.Emit(OpCodes.Ldelem_Ref);

        cloneIlGenerator.Emit(OpCodes.Stloc_S, sourceEntityReferenceEntityKeyMemberProp);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, valueEntityReferenceEntityKeysMemberProp);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, loopIndex);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeyMemberProp);

        cloneIlGenerator.Emit(OpCodes.Callvirt, typeof(EntityKeyMember).GetMethod("get_Key"));

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeyMemberProp);

        cloneIlGenerator.Emit(OpCodes.Callvirt, typeof(EntityKeyMember).GetMethod("get_Value"));

        cloneIlGenerator.Emit(OpCodes.Newobj, typeof(EntityKeyMember).GetConstructor(new Type[] { typeof(string), typeof(object) }));

        cloneIlGenerator.Emit(OpCodes.Stelem_Ref);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, loopIndex);

        cloneIlGenerator.Emit(OpCodes.Ldc_I4_1);

        cloneIlGenerator.Emit(OpCodes.Add);

        cloneIlGenerator.Emit(OpCodes.Stloc_S, loopIndex);

        cloneIlGenerator.Emit(OpCodes.Br, startLoopLabel);

        cloneIlGenerator.MarkLabel(endOfLoopLable);

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, sourceEntityReferenceEntityKeyProp);

        cloneIlGenerator.Emit(OpCodes.Callvirt, typeof(EntityKey).GetMethod("get_EntityContainerName"));

        cloneIlGenerator.Emit(OpCodes.Ldstr, ".");

        cloneIlGenerator.Emit(OpCodes.Ldloc, sourceEntityReferenceEntityKeyProp);

        cloneIlGenerator.Emit(OpCodes.Callvirt, typeof(EntityKey).GetMethod("get_EntitySetName"));

        cloneIlGenerator.Emit(OpCodes.Call, typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string), typeof(string) })); // We suppose EntityContainerName and EntitySetName aren't null

        cloneIlGenerator.Emit(OpCodes.Ldloc_S, valueEntityReferenceEntityKeysMemberProp);

        cloneIlGenerator.Emit(OpCodes.Newobj, typeof(EntityKey).GetConstructor(new Type[] { typeof(string), typeof(IEnumerable<EntityKeyMember>) }));

        var noEntityKeyValuesEnd = cloneIlGenerator.DefineLabel();

        cloneIlGenerator.Emit(OpCodes.Br_S, noEntityKeyValuesEnd);

        cloneIlGenerator.MarkLabel(noEntityKeyValues);

        cloneIlGenerator.Emit(OpCodes.Newobj, typeof(EntityKey).GetConstructor(new Type[0]));

        cloneIlGenerator.MarkLabel(noEntityKeyValuesEnd);

        cloneIlGenerator.Emit(OpCodes.Callvirt, prop.PropertyType.GetMethod("set_EntityKey"));

        if (copyNull)

        {

            var endCopyReferenceLabel = cloneIlGenerator.DefineLabel();

            cloneIlGenerator.Emit(OpCodes.Br_S, endCopyReferenceLabel);

            cloneIlGenerator.MarkLabel(entityKeyNullLabel);

            cloneIlGenerator.Emit(OpCodes.Ldloc, valueEntityReferenceProp);

            cloneIlGenerator.Emit(OpCodes.Ldnull);

            cloneIlGenerator.Emit(OpCodes.Callvirt, typeof(T).GetMethod("set_" + prop.Name));

            cloneIlGenerator.MarkLabel(endCopyReferenceLabel);

        }

        else

            cloneIlGenerator.MarkLabel(entityKeyNullLabel);

    }

 

    public static T Clone(T obj)

    {

        return CloneDelegate(obj);

    }

}

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 :

Publié vendredi 16 mai 2008 00:17 par Matthieu MEZIL

Classé sous : , , ,

Commentaires

Pas de commentaires

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Les actualités de la semaine sur c2i.fr (14 mai - 20 mai) par Richard Clark le il y a 5 heures et 6 minutes

- Reactive Extensions : Consommer des services avec Rx Partie 3, les pièges à éviter par Léonard Labat le il y a 14 heures et 11 minutes

- SharePoint Blog Site, problème d’archives par Le Blog (Vert) d'Arnaud JUND le 05-20-2012, 13:09

- Soirée ALT.NET Mai - 3 présentations par #Rui le 05-18-2012, 11:59

- [ #SharePoint 2010][ #SQLServer 2012] AlwaysOn pour SharePoint (2/4) : Configuration (2e partie)… par Le blog de Patrick [MVP SharePoint] le 05-18-2012, 11:31

- Team Foundation Server 11: tous les trésors cachés du site d’équipe par Philess le 05-16-2012, 19:01

- [PowerShell 3] Télécharger et installer la documentation en ligne par Blog de SPBrouillet (Pierrick BROUILLET) le 05-16-2012, 17:36

- [#SharePoint 2010][#SQLServer 2012] AlwaysOn pour SharePoint (1/4) : Configuration (1ère partie)… par Le blog de Patrick [MVP SharePoint] le 05-16-2012, 12:10

- Job Day @MIC Brussels - .Net Developers on Mobile applications par Le Blog (Vert) d'Arnaud JUND le 05-15-2012, 20:26

- [SharePoint 2010] – SharePoint 2010, Windows (Server) 8 et des erreurs IIS sont dans une VM… par Blog de SPBrouillet (Pierrick BROUILLET) le 05-14-2012, 12:10