IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Dotnet Discussion :

XSD / XML : string vers XmlEnumAttribute


Sujet :

Dotnet

  1. #1
    Modérateur

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Avril 2007
    Messages
    1 996
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Service public

    Informations forums :
    Inscription : Avril 2007
    Messages : 1 996
    Points : 3 102
    Points
    3 102
    Par défaut XSD / XML : string vers XmlEnumAttribute
    Bonjour à tous,

    Je suis bloqué sur un problème que je ne parviens pas à résoudre. Je fais donc appel à vous :

    J'ai généré un classe à partir d'une XSD via xsd.exe.
    La XSD contient des listes transformées en énumérations de ce type :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1087.0")]
        [System.SerializableAttribute()]
        public enum BlocsListe
        {
            /// <remarks/>
            [System.Xml.Serialization.XmlEnumAttribute("2 blocs")]
            Item2blocs,
            /// <remarks/>
            [System.Xml.Serialization.XmlEnumAttribute("4 blocs")]
            Item4blocs,
            /// <remarks/>
            ND,
        }
    La property qui utilise cette énumération :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    private BlocsListe _typeBlocs;
    [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
            public BlocsListe TypeBlocs
            {
                get
                {
                    return this._typeBlocs;
                }
                set
                {
                    this._typeBlocs = value;
                }
            }
    J'extraie des éléments de ma base de données via un DataReader et j'essaye ensuite d'affecter la valeur contenue en base de données à la propriété TypeBlocs de mon objet.

    Et c'est là que je suis bloqué.
    En base de données, c'est le contenu du XmlEnumAttribute qui est stocké.
    J'ai donc des cas où je n'ai pas de valeurs en base de données, d'autre où j'ai bien une valeur correspondant à un item de l'énumération et des cas où la valeur stockée dans la base ne correspond à aucun item de l'énumération.

    Je cherche à parser le contenu de mon DataReader pour trouver la correspondance avec un des XmlEnumAttribute pour ensuite affecter ma propriété.
    Et je bloque sur ce point.

    Pour simplifier la chose, certaines autres énumérations ne contiennent pas de XmlEnumAttribute (cf. le "ND" de l'énumération ci-dessus).

    Si vous avez des conseils, je suis preneur.

    Merci d'avance

  2. #2
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 757
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 757
    Points : 10 697
    Points
    10 697
    Billets dans le blog
    21
    Par défaut
    Bonjour,

    Voici l'approche que je suivrais :
    1. Créer un Dictionary<string, BlocsList> permettant de faire le mapping entre les valeurs stockées en BD et l'énumération ;
    2. Utiliser le mapping


    Pour créer le mapping, il faut utiliser la réflexion :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    Dictionary<string, BlocsList> mapping = new Dictionary<string, BlocsList>;
    Type type = typeof(BlocsList);
     
    foreach(BlocsList b in Enum.GetValues(typeof(BlocsList)))
    {
       MemberInfo[] memberInfo = type.GetMember(b.ToString());
       object[] attributes = memberInfo [0].GetCustomAttributes(typeof(System.Xml.Serialization.XmlEnumAttribute), false);
     
       if (attributes.Length == 1) 
       {
          System.Xml.Serialization.XmlEnumAttribute attribute = attributes[0] as System.Xml.Serialization.XmlEnumAttribute;
          mapping.Add(attribute.Name, b);
       }
       else 
       {
          mapping.Add(b.ToString(), b);
       }
     
    }

    Via un foreach, on parcourt l'ensemble des valeurs de l'énumération. Pour chaque valeur, on essaie, via la réflexion, d'accéder à l'attribut XmlEnumAttribute. S'il existe, on utilise la valeur de cette attribut pour ajouter une clé dans le dictionnaire de mapping. Sinon, on utilise la valeur de l'énum.

    PS : code pas testé, donc potentiellement avec des coquilles
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  3. #3
    Modérateur

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Avril 2007
    Messages
    1 996
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Service public

    Informations forums :
    Inscription : Avril 2007
    Messages : 1 996
    Points : 3 102
    Points
    3 102
    Par défaut
    François,
    merci pour cette réponse.

    En adaptant le code pour le factoriser et le rendre utilisable avec toutes mes Enum, en effet, cela fait quasiment ce dont j'ai besoin :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    public static Dictionary<string,T> CheckEnumValue<T>(string value)
            {
                Dictionary<string,T> DicEnum = new Dictionary<string, T>();
                if (!string.IsNullOrEmpty(value))
                {
                    Type type = typeof(T);
                    MemberInfo[] memberInfo;
     
                    foreach (T b in Enum.GetValues(typeof(T)))
                    {
                        memberInfo = type.GetMember(b.ToString());
                        object[] attributes = memberInfo[0].GetCustomAttributes(typeof(System.Xml.Serialization.XmlEnumAttribute), false);
     
                        if (attributes.Length == 1)
                        {
                            XmlEnumAttribute attribute = attributes[0] as System.Xml.Serialization.XmlEnumAttribute;
                            if (attribute != null && !string.IsNullOrEmpty(attribute.Name) && attribute.Name.Equals(value))
                                DicEnum.Add(attribute.Name, b);                            
                        }
                        else
                        {
                            if (b.ToString().Equals(value))
                                DicEnum.Add(b.ToString(), b);
                        }
                    }
                    return DicEnum;
                }
                return null;
            }
    Maintenant, il va falloir gérer l'appel sur la totalité des Enums concernées... Galère en perspective, il y en a plus de 200.

    Et désormais, je galère sur la factorisation de l'appel.
    Il faut que je fasse ceci, pour les 200 Enums :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
                         Dictionary<string, BlocsListe> dicEnum = CheckEnumValue<BlocsListe>(rd["BLOC"].ToString());
                                    if (dicEnum != null && dicEnum.Count() > 0)
                                    {
                                        monObjet.TypeBloc = dicEnum[rd["BLOC"].ToString()];
                                        monObjet.TypeBlocSpecified = true;
                                    }
                                    else
                                        monObjet.TypeBlocSpecified = false;
    J'aimerais factoriser le tout dans une méthode qui ferait cette opération en prenant en paramètres la valeur du DataReader et les propriétés à assigner.
    Ca me paraît un peu compliqué cette histoire.

  4. #4
    Modérateur

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Avril 2007
    Messages
    1 996
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Service public

    Informations forums :
    Inscription : Avril 2007
    Messages : 1 996
    Points : 3 102
    Points
    3 102
    Par défaut
    Je m'auto-répond avec une solution qui ne me convient qu'à moitié :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public static void SetEnumField<E,T>(string drValue, T target, string outExpr, string outExprSpecified)
            {
                Dictionary<string, E> dicEnum = CheckEnumValue<E>(drValue);
     
                if (dicEnum != null && dicEnum.Count() > 0)
                {
                    var prop = target.GetType().GetProperty(outExpr);
                    prop.SetValue(target, dicEnum[drValue], null);
     
                    prop = target.GetType().GetProperty(outExprSpecified);
                    prop.SetValue(target, true, null);
                }
                else
                {
                    if (outExprSpecified != null)
                    {
                        var prop = target.GetType().GetProperty(outExprSpecified);
                        prop.SetValue(target, false, null);
                    }
                }
            }
    et l'appel :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    SetEnumField<BlocsListe, monObjet>(rd["BLOC"].ToString(), monObjet, "TypeBloc", "TypeBlocSpecified");
    via la Reflection, je parviens à affecter les propriétés de mon objet.
    Seulement, ça m'embête de les passer à ma méthode SetEnumField via le nom de la propriété sous forme de string car en cas de changement du nom de la propriété, je devrais la changer manuellement.

    Si vous avez une solution plus adaptée, je suis vraiment preneur.

  5. #5
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 757
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 757
    Points : 10 697
    Points
    10 697
    Billets dans le blog
    21
    Par défaut
    Citation Envoyé par calagan99 Voir le message
    Seulement, ça m'embête de les passer à ma méthode SetEnumField via le nom de la propriété sous forme de string car en cas de changement du nom de la propriété, je devrais la changer manuellement.

    Si vous avez une solution plus adaptée, je suis vraiment preneur.
    Oui ! l'opérateur nameof est justement fait pour ça

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    SetEnumField<BlocsListe, monObjet>(rd["BLOC"].ToString(), monObjet, nameof(monObjet.TypeBloc), nameof(monObjet.TypeBlocSpecified));

    Ensuite, il y a une possibilité d'amélioration. Ici, le dictionnaire est créé à chaque fois lors d'un appel à CheckEnumValue. On peut éventuellement penser à faire une mise en cache (par exemple, CheckEnumValue étant statique, elle peut être définie dans une classe statique avec un constructeur statique s'occupant d'initialiser les dictionnaires pour toutes les énumérations). Ce n'est pas une obligation, mais si tu rencontres des problèmes de performance, tu peux déjà agir là-dessus. La réflexion est connue pour ne pas être très rapide...
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  6. #6
    Modérateur

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Avril 2007
    Messages
    1 996
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Service public

    Informations forums :
    Inscription : Avril 2007
    Messages : 1 996
    Points : 3 102
    Points
    3 102
    Par défaut
    Effectivement, un simple nameof.
    Je n'y avais pas pensé.
    Merci.

  7. #7
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 757
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 757
    Points : 10 697
    Points
    10 697
    Billets dans le blog
    21
    Par défaut
    Bonjour,

    Ce problème m'a hanté cette nuit. J'y ai pas mal réfléchis et le soucis trouve ça source dans le fait qu'il n'est pas possible, en C#, d'accéder directement aux accesseurs cachés derrière une propriété (il ne faut pas oublier qu'une propriété X c'est tout simplement un get_X et/ou unset_X).

    Aussi, j'ai fais un petit bout de code qui permet de récupérer de tels accesseurs !

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    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
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace PropertyAndDelegate
    {
        public class Program
        {
     
            public delegate void SetterDelegate<TValue>(TValue value);
            public delegate TValue GetterDelegate<TValue>();
     
            public static class PropertyAccessor<T>
            {
                public static PropertyInfo GetProperty<TValue>(Expression<Func<T, TValue>> expr)
                {
                    Expression body = expr.Body;                
                    if (body.NodeType == ExpressionType.MemberAccess)
                    {
                        MemberExpression member = body as MemberExpression;
                        return member.Member as PropertyInfo;
                    }
                    else
                    {
                        throw new ArgumentException("expr");
                    }
                }
     
                public static SetterDelegate<TValue> Setter<TValue>(T o, Expression<Func<T, TValue>> expr)
                {
                    PropertyInfo property = GetProperty(expr);
                    return new SetterDelegate<TValue>(x => property.SetValue(o, x));
                }
     
                public static GetterDelegate<TValue> Getter<TValue>(T o, Expression<Func<T, TValue>> expr)
                {
                    PropertyInfo property = GetProperty(expr);
                    return new GetterDelegate<TValue>(() => { return (TValue)(property.GetValue(o)); });
                }
            }
     
            static void Main(string[] args)
            {
                Program program = new Program();
                SetterDelegate<int> d = PropertyAccessor<Program>.Setter<int>(program, x => x.MyProperty);
                d(5);
                Console.WriteLine(program.MyProperty);
                Console.ReadLine();
            }
     
            public int MyProperty { get; set; }
        }
    }

    En utilisant la classe ainsi définie, il est possible de réécrire la méthode SetEnumField :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    public static void SetEnumField<E, T>(string drValue, T target, Expression<Func<T, E>> expr)
            {
                Dictionary<string, E> dicEnum = CheckEnumValue<E>(drValue);
                var prop = PropertyAccessor<T>.GetProperty<E>(expr);
                var propSpecified = target.GetType().GetProperty(prop.Name + "Specified");
     
                if (dicEnum != null && dicEnum.Count() > 0)
                {                
                    prop.SetValue(target, dicEnum[drValue], null);                
                    propSpecified.SetValue(target, true, null);
                }
                else
                {
                    propSpecified.SetValue(target, false, null);             
                }
            }

    Et l'appel se fera donc ainsi : SetEnumField<BlocsListe, monObjet>(rd["BLOC"].ToString(), monObjet, x => x.TypeBloc);A noter que l'accès à la propriété xxxSpecified se déduit directement au sein de la méthode, au lieu de devoir le passer en paramètre.
    Le code est sans doute à consolider un peu (notamment au niveau de la gestion des erreurs), mais cela donne une autre approche tout aussi robuste, voir plus, que la première car elle force l'appelant à utiliser une lambda alors que rien n'oblige, dans la première version, l'appelant à utiliser l'opérateur nameof
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  8. #8
    Modérateur

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Avril 2007
    Messages
    1 996
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Service public

    Informations forums :
    Inscription : Avril 2007
    Messages : 1 996
    Points : 3 102
    Points
    3 102
    Par défaut
    Merci beaucoup François,

    c'est en effet beaucoup plus propre et rend l'écriture plus simple et secure.

    J'ai apporté quelques modifications pour n'affecter la propriété "Specified" que lorsqu'elle est accessible en écriture et un appel à une fonction qui me ramène la valeur par défaut de mon Enum quand le champ est obligatoire en sortie mais vide dans ma bdd :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public static void SetEnumField2<E, T>(string drValue, T target, Expression<Func<T, E>> expr)
            {
                Dictionary<string, E> dicEnum = CheckEnumValue<E>(drValue);
                var prop = Utils.PropertyAccessor<T>.GetProperty<E>(expr);
                var propSpecified = target.GetType().GetProperty(prop.Name + "Specified");
     
                if(propSpecified != null && propSpecified.CanWrite)
                {
                    if (dicEnum != null && dicEnum.Count() > 0)
                    {
                        prop.SetValue(target, dicEnum[drValue], null);
                        propSpecified.SetValue(target, true, null);
                    }
                    else
                        propSpecified.SetValue(target, false, null);
                }
                else
                {
                    if (dicEnum != null && dicEnum.Count() > 0)
                        prop.SetValue(target, dicEnum[drValue], null);
                    else
                        prop.SetValue(target, Utils.GetEnumDefaultValue<E>(), null);                        
                }            
            }

  9. #9
    Expert éminent sénior

    Avatar de François DORIN
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Juillet 2016
    Messages
    2 757
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2016
    Messages : 2 757
    Points : 10 697
    Points
    10 697
    Billets dans le blog
    21
    Par défaut
    Citation Envoyé par calagan99 Voir le message
    Merci beaucoup François
    Mais de rien

    Citation Envoyé par calagan99 Voir le message
    J'ai apporté quelques modifications pour n'affecter la propriété "Specified" que lorsqu'elle est accessible en écriture
    Très bonne idée ! Le code est effectivement a adapté en fonction des besoins de chacun. Il ne fait que donner l'idée de base qui est loin d'être triviale...
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [Débutant] convertir .xls vers .xsd/xml 2013
    Par grisan29 dans le forum VB.NET
    Réponses: 7
    Dernier message: 25/08/2015, 12h56
  2. String vers un xml
    Par nadou114 dans le forum Android
    Réponses: 1
    Dernier message: 11/04/2011, 14h39
  3. Valider un XML string avec XSD
    Par ForumsGalaxy dans le forum Général Dotnet
    Réponses: 1
    Dernier message: 26/10/2010, 13h54
  4. [XSD] XML Schema
    Par sleepy2002 dans le forum Valider
    Réponses: 3
    Dernier message: 15/09/2003, 09h33

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo