Personnaliser les colonnes d'un DataGrid en Silverlight : 3ème partie, utilisation du ValueConverter#

Dans les deux première parties de cette série d’articles nous avons utilisé une DataGrid pour afficher des données provenant d’un service WCF en utilisant l’option de génération automatique de colonnes. Ensuite nous avons personnalisé ces colonnes dans un premier temps à l’aide du DataGridTextColumn et du DataGridCheckBoxColumn, et dans un deuxième temps avec le DataGridTemplateColumn. Voici le résultat obtenu :

Nous allons maintenant ajouter l’affichage de la date de réalisation de la tâche mais comme la date est de type DateTime lorsqu’on va la binder dans une colonne de type Text, nous allons voir la date et heure complète alors que ce qui nous intéresse c’est uniquement la date. Dans cet article nous allons donc utiliser un ValueConverter pour afficher la date au format attendu.

Pour suivre cet exemple vous pouvez télécharger les sources joints à cet article.

Commençons d’abord par rajouter l’affichage de la date, dans le projet TasksManager.View (l’application Silverlight), répertoire Model, ouvrez la classe Task et rajouter la propriété date de réalisation comme suit :

public DateTime? RealizationDate { get; set; }

Dans le même projet, le répertoire ViewModel ouvrez la classe TasksViewModel et localisez la méthode taskServiceClient_GetListCompleted et rajoutez la ligne d’affectation de la date de réalisation comme suit :

void taskServiceClient_GetListCompleted(object sender, TasksManager.View.TasksService.GetListCompletedEventArgs e)
{
       List<Task> resultTasks = new List<Task>();
       foreach (TasksService.Task t in e.Result)
       {
           Task mt = new Task();
           mt.Id = t.id;
           mt.Name = t.name;
           mt.RealizationDate = t.realizationDate;
           if (t.realized.HasValue)
               mt.Realized = t.realized.Value;
           resultTasks.Add(mt);
       }
       this.Tasks = resultTasks;
}

Et enfin, dans le même projet, répertoire Views, ouvrez le fichier Tasks.xaml et dans <Data:DataGrid.Columns>, rajoutez la colonne suivante :

<Data:DataGridTextColumn Binding="{Binding RealizationDate}" Header="Date de réalisation"></Data:DataGridTextColumn>

Lancez maintenant le projet, voici ce que vous devriez obtenir cela :

image

La date de réalisation s’affiche pour les tâches pour les quelles la date est renseignée.

Nous voyons bien, comme prévu, que la date d’affiche avec un format long. Ceci parce que le ToString() d’une date convertit la date en chaîne de caractère avec ce format. Nous allons maintenant mettre en place un converter pour afficher uniquement la date.

Dans le projet TasksManager.View, répertoire Views, ajoutez un nouveau répertoire et appelez le “Converters”. Dans ce répertoire ajoutez une nouvelle classe et nommez la “ShortDateFormatConverter”. Ensuite, implémentez l’interface IValueConverter en ajoutant l’espace de noms System.Windows.Data

image

Pour implémenter l’interface IValueConverter nous devons définir deux méthodes :

- La méthode Convert : qui convertit l’objet à binder vers l’objet que nous voulons afficher. Dans notre cas cette méthode prend en paramètre la date de réalisation de type DateTime et nous allons faire en sorte qu’elle renvoie une chaine de caractère représentant la date au format court.

- La méthode ConvertBack : qui fait le chemin inverse, c’est à dire qu’elle prend en paramètre la chaine de caractère affichée et représentant la date au format court pour la convertir au format d’origine (dans notre cas DateTime). Cette méthode est utile lorsqu’on met en place un Binding TwoWay, mais dans notre cas nous n’allons pas l’implémenter parce que notre but est uniquement l’affichage.

Voici le code de la classe ShortDateFormatConverter

public class ShortDateFormatConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        DateTime? date = value as DateTime?;

        if (date != null)
            return date.Value.ToShortDateString();

        return date;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Pour utiliser ce converter dans notre vue nous devons tout d’abord l’enregistrer comme ressource dans notre UserControl, ouvrez le fichier Tasks.xaml et placez vous juste après la déclaration du UserControl

<UserControl.Resources>
    <Converters:ShortDateFormatConverter x:Key="ShortDateFormatter"></Converters:ShortDateFormatConverter>
</UserControl.Resources>

Et enfin remplacez la ligne que nous avons ajouté au tout début de cet article

<Data:DataGridTextColumn Binding="{Binding RealizationDate}" Header="Date de réalisation"></Data:DataGridTextColumn>

Par celle – ci:

<Data:DataGridTextColumn Binding="{Binding RealizationDate, Converter={StaticResource ShortDateFormatter}}" Header="Date de réalisation"></Data:DataGridTextColumn>

Lancez maintenant votre application, vous devriez avoir ce résultat :

image

Tuesday, March 30, 2010 9:52:35 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

SqlFunctions dans Entity Framework 4#

Dans les versions précédentes d’Entity Framework, faire un calcul de différence entre deux dates avec linq to entities était assez laborieux, les fonctions c# n’étaient pas prise en compte dans les requêtes linq to entities et nous étions obligé soit de passer par une colonne calculée au sein de la Base de données soit modifier les fichiers de mapping pour écrire la requête sql à la main et utiliser la fonction DATEDIFF de SQL Server.

Avec Entity Framework 4 et l’apparition de la classe SqlFunctions, tout ça c’est de l’histoire ancienne. Cette classe contient un bon nombre de fonctions Sql Server qui peuvent être utilisées directement dans nos requêtes Linq To Entities et qui vont être traduites vers les fonctions SQL dans un contexte de requête à la BDD. Ces fonctions sont exposées directement par SQL Server et sont donc propres au Provider Sql Server. Parmis ces fonctions nous trouvons le DateDiff qui va nous permettre de calculer la différence entre 2 dates.

image

Soit cette requête linq to entites :

var requete = from s in context.Person
            select new { Nom = s.FirstName + " " + s.LastName, 
                                 s.BirthDate, 
                                 Age = System.Data.Objects.SqlClient.SqlFunctions.DateDiff("year", s.BirthDate, DateTime.Now) 
};

Du côté de profiler, voici la requête générée :

image

Nous remarquons que notre fonction DATEDIFF a été traduite littéralement.

Faisons maintenant un petit tour sur Reflector, voici ce que nous pouvons trouver

image

En effet, cette fonction est une fonction Sql Server mappée tout simplement. Donc en ce qui concerne les SqlFunctions, il vaut mieux se référer directement à la documentation Transact – SQL.

Friday, March 19, 2010 10:29:31 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Exposer les POCO Entity Framework 4 via un service WCF, résolution du problème de référence circulaire : 3ème partie#

Le problème de référence circulaire n’est pas propre à Entity Framework mais plutôt un problème de sérialisation WCF, dans cet article nous allons expliquer le problème et proposer une solution.

Soit le modèle edmx suivant :

image

Ce modèle a été très bien conçu et reflète bien la réalité du métier, par contre pour le développeur ce n’est pas vraiment la joie parce que s’il a envie de savoir à quel StudentGroup appartient un étudiant (Person_Student) il devra passer par 4 tables alors que c’est censé être l’opération métier la plus élémentaire. Passer par ces 4 tables est non seulement pénible pour le développeur mais en plus catastrophique au niveau perf pour au final une opération très simple. Après une discussion houleuse entre le Développeur et le DBA, le DBA accepte (une fois n’est pas coutume:)) d’ajouter un raccourci matérialisé en une table de lien entre Student et StudentGroup permettant de déterminer directement l’appartenance d’un étudiant à un groupe d’étudiants. Voici ce qu’est devenu le modèle :

image

Si le DBA était retissant au rajout de ce raccourci c’est à cause de ce cycle (entouré en rouge) introduit au sein du modèle. Ce cycle est sans conteste une mauvaise pratique en design mais dans quelques cas ça s’avère bien pratique.

Tout content (juste parce qu’il a eu gain de cause:)), le développeur se met à développer son service WCF en créant une méthode qui renvoie un étudiant en embarquant quelques données liées. Voici le corps de la méthode :

public Person_Student GetById(int id) 
{
       SuiviScolaireEntities context = new SuiviScolaireEntities();
       return context.Person_Student.Include("Pass")
                                  .Include("Pass.Exam")
                                  .Include("Pass.Exam.Teach")
                                  .Where(s => s.PersonId == id)
                                  .FirstOrDefault<Person_Student>();               
}

Il crée ensuite la partie client pour appeler le service et en exécutant il obtient l’erreur suivante :

image

Bon je vous l’accorde ce n’est pas très explicite comme erreur, pour y voir plus clair nous allons activer la trace WCF, voici la vrai erreur qui se cache derrière ce message :

There was an error while trying to serialize parameter http://tempuri.org/:GetStudentByIdResult. The InnerException message was 'Object graph for type 'xxxxxxx' contains cycles and cannot be serialized if reference tracking is disabled.'.  Please see InnerException for more details.

L’erreur dit que le modèle comporte un cycle et qu’il ne peut être sérialisé via WCF. Le développeur la ramène moins d’un coup mais se dit qu’il faut absolument résoudre ce problème pour ne pas avoir l’air con et que le DBA lui dise : je t’avais dit que ce n’est pas bien!

Analyse du problème

Le problème est que le DataContractSerializer va vouloir sérialiser explicitement les objets à renvoyer au client au lieu de travailler avec des références, supposons que l’on ai ce modèle :

public class Person
{
    public int Id { get; set; }

    public int FirstName { get; set; }

    public int LastName { get; set; }

    public List<Person> Enfants { get; set; }
}

Et que j’ai une List<Person> dans laquelle j’ai inséré des données de la manière suivante :

Person pEnfant1 = new Person { Id = 1, FirstName = "Thierry", LastName = "BOB" }; //Enfant
Person pEnfant2 = new Person { Id = 2, FirstName = "Marcus", LastName = "TIBH" }; //Enfant
Person pEnfant3 = new Person { Id = 3, FirstName = "Adrien", LastName = "MOLBE" }; //Enfant
Person pEnfant4 = new Person { Id = 4, FirstName = "Yam", LastName = "BOB" }; //Enfant
Person pParent1 = new Person
{
    Id = 5,
    FirstName = "Julien",
    LastName = "BOB",
    Enfants = new List<Person> { pEnfant1, pEnfant4 }
}; //Parent

Person pParent2 = new Person
{
    Id = 6,
    FirstName = "Gilian",
    LastName = "TIBH",
    Enfants = new List<Person> { pEnfant2 }
}; //Parent

Par défaut, le DataContractSerializer instancié par WCF sérialise les données de la manière suivante :

<ArrayOfPerson xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Person>
    <Enfants i:nil="true"/>
    <FirstName>Thierry</FirstName>
    <Id>1</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person>
    <Enfants i:nil="true"/>
    <FirstName>Marcus</FirstName>
    <Id>2</Id>
    <LastName>TIBH</LastName>
  </Person>
  <Person>
    <Enfants i:nil="true"/>
    <FirstName>Adrien</FirstName>
    <Id>3</Id>
    <LastName>MOLBE</LastName>
  </Person>
  <Person>
    <Enfants i:nil="true"/>
    <FirstName>Yam</FirstName>
    <Id>4</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person>
    <Enfants>
      <Person>
        <Enfants i:nil="true"/>
        <FirstName>Thierry</FirstName>
        <Id>1</Id>
        <LastName>BOB</LastName>
      </Person>
      <Person>
        <Enfants i:nil="true"/>
        <FirstName>Yam</FirstName>
        <Id>4</Id>
        <LastName>BOB</LastName>
      </Person>
    </Enfants>
    <FirstName>Julien</FirstName>
    <Id>5</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person>
    <Enfants>
      <Person>
        <Enfants i:nil="true"/>
        <FirstName>Marcus</FirstName>
        <Id>2</Id>
        <LastName>TIBH</LastName>
      </Person>
    </Enfants>
    <FirstName>Gilian</FirstName>
    <Id>6</Id>
    <LastName>TIBH</LastName>
  </Person>
</ArrayOfPerson>

Remarquez dans ce flux XML généré par le DataContractSerializer que les personnes peuvent se répétées plusieurs fois dans le flux et que leur description entière est reprise à chaque fois qu’elles apparaissent. Marcus TIBH apparaît dans le flux 2 fois : Une fois comme “Person” sans enfants et ses informations sont reprises complètement en tant que "Enfant” de Gilian TIBH.

En effet, par défaut, WCF instancie le DataContractSerializer avec la propriété preserveObjectReferences = false. Cette propriété permet de spécifier de ne pas préserver les références d’objets lors de la sérialisation et du coup répéter à chaque fois entièrement les données.

WCF n’autorise pas les références circulaires avec ce mode de sérialisation et l’attribut preserveObjectReferences = false.

1ère Solution

La première solution est décrite dans le blog de sowmy et qui consiste à modifier le fonctionnement par défaut de WCF en instanciant le DataContractSerializer avec le preserveObjectReferences = true. Ceci est possible en héritant de DataContractSerializerOperationBehavior et en surchargeant sa méthode CreateSerializer qui renvoie le Serializer au moteur WCF. Nous pourrons par la suite faire de notre classe un attribut avec lequel nous allons décorer toutes les méthodes qui vont avoir besoin de ce mode de fonctionnement.

2ème solution

La deuxième méthode consiste à définir la propriété IsReference à True sur les classes qui vont être sérialisés de cette manière :

[DataContract(IsReference=true)]
public class Person
{
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public string FirstName { get; set; }

    [DataMember]
    public string LastName { get; set; }

    [DataMember]
    public List<Person> Enfants { get; set; }
}

De cette manière le DataContractSerializer va travailler avec les références à chaque fois qu’il s’agit de sérialiser un objet du type de cette classe.

Pour ces 2 méthodes, voici le XML résultant :

<ArrayOfPerson xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Person z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants i:nil="true"/>
    <FirstName>Thierry</FirstName>
    <Id>1</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants i:nil="true"/>
    <FirstName>Marcus</FirstName>
    <Id>2</Id>
    <LastName>TIBH</LastName>
  </Person>
  <Person z:Id="i3" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants i:nil="true"/>
    <FirstName>Adrien</FirstName>
    <Id>3</Id>
    <LastName>MOLBE</LastName>
  </Person>
  <Person z:Id="i4" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants i:nil="true"/>
    <FirstName>Yam</FirstName>
    <Id>4</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person z:Id="i5" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants>
      <Person z:Ref="i1"/>
      <Person z:Ref="i4"/>
    </Enfants>
    <FirstName>Julien</FirstName>
    <Id>5</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person z:Id="i6" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants>
      <Person z:Ref="i2"/>
    </Enfants>
    <FirstName>Gilian</FirstName>
    <Id>6</Id>
    <LastName>TIBH</LastName>
  </Person>
</ArrayOfPerson>

Vous avez certainement remarqué les attributs z:Id et z:ref qui permettent de ne plus reprendre toutes les données d’une instance et de la référencer via son Id.

Enjoy ;)

Monday, March 15, 2010 8:13:00 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Webcast des Techdays 2010 en ligne#

ça y est!!! tous ceux qui n’ont pas eu la chance d’être au Palais des congrès du 8 au 10 février vont pouvoir prendre leur revanche puisque les webcast de toutes les sessions des Techdays 2010 sont en ligne depuis ce matin à cette adresse. Je vous conseille bien évidemment celle que j’ai animé avec Arnaud AUROUX intitulée Silverlight 4 : Cas pratique du mode déconnecté. Vous trouverez attaché à cet article la solution que nous avons développé pendant la session.

Friday, March 12, 2010 12:11:06 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Debugger un service WCF#

Nous pouvons debugger notre code au sein d’un service WCF de la même manière que tout autre code .NET, en définissant des points d’arrêts au niveau du code que  nous voulons débugger et si nous utilisons le serveur web IIS nous devons alors nous attacher au processus de ce dernier. Mais il arrive que notre code s’exécute bien et ne lève aucune exception alors que côté client nous récupérons une erreur qui ressemble à ça :

image

“The remote server returned an error: NotFound” pas très explicite comme erreur surtout lorsque vous savez que le code du service WCF a bien été atteint et qu’il s’est bien exécuté sans problème. Le problème s’est donc passé au sein de WCF après l’exécution de votre code du service. Pour avoir une erreur plus explicite et comprendre ce qui se passe réellement il suffit d’activer le tracing côté WCF en ajoutant cette entrée dans le web.config

  <system.diagnostics>
    <sources>
      <source name="System.ServiceModel"
              switchValue="Information, ActivityTracing"
              propagateActivity="true">
        <listeners>
          <add name="WCFListener"
              type="System.Diagnostics.XmlWriterTraceListener"
              initializeData= "c:\wcflog\WCFTraces.svclog" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>

Après un nouvel appel de votre service, rendez vous au répertoire c:\wcflog et double cliquez sur le fichier WCFTraces.svclog, vous allez pouvoir découvrir la vraie exception

image

D’un coup, tout devient plus clair, dans mon cas j’ai un cycle dans mes classes.

Sunday, March 07, 2010 11:02:06 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Exposer les POCO Entity Framework via un service WCF : 2ème partie#

Dans la première partie de cet article nous avons vu qu’Entity Framework 4 génère des Proxys qui héritent de nos classes POCO afin de leur ajouter des facultés de “Change Tracking” et de “Lazy loading” et nous renvoyait ces proxys. Nous avons vu que ces proxys posaient un problème lorsque nous avons voulu les sérialiser pour les exposer via un service WCF et nous avons contourné ce problème en mappant ce proxy en notre type original pendant la phase de sérialisation, de cette manière nous travaillons avec des POCO en bénéficiant de tous les apports d’Entity Framework comme le “Tracking” ou encore le “Lazy Loading” qui ne sont sincèrement pas un luxe.

Cette idée est néanmoins un “Work around” et j’ai tout de suite vu ces limites lorsque j’ai essayé de renvoyer un objet avec des entités liées => retour à la case départ problème de dé sérialisation … Il existe un autre moyen de travailler avec Entity Framework et des POCO que nous allons illustrer dans cet article.

En effet, nous pouvons choisir de dire au context que je ne veux pas qu’il génère des proxys de cette manière

context.ContextOptions.ProxyCreationEnabled = false;

Mais dans ce cas il faut assumer ces responsabilités :) Entity Framework ne fait plus de Tracking de changement sur les entités ni de Lazy Loading, les entités doivent donc accomplir ces fonctionnalités toutes seules, et c’est qui qui s’y colle? Bien évidemment ce n’est pas nous :). Je ne sais pas si vous vous en souvenez mais nous avons parlé dans cet article d’un Template T4 fournit par l’équipe ADO.NET qui génère les entités POCO à partir du modèle edmx, ce template nous génère avec nos POCO la partie qui s’occupe du Tracking de changements et les entités deviennent alors des STE : Self Tracking Entities, c’est à dire qu’elles embarquent la logique de Tracking. Et qu’en est il du lazy loading? Là par contre, si le lazy loading est un point crucial pour vous alors cette solution n’est pas faite pour vous, ou bien vous allez vous embarquer dans un développement custom.

Comment tester que le “Self Tracking” marche?

Pour tester il suffit de charger une entité de la base de données, modifier une de ses propriétés et appeler le SaveChanges du context en cours, si la modification est sauvegardée en base de données c’est que le Tracking de changement a bien fonctionné.

Récapitulons

Nous avons trois manières de travailler avec Entity Framework dans une applications n-tiers:

1 – Utiliser les entités Entity Framework (non POCO) dans toutes les couches de notre application :

- C’est mal !!! pour diverses raisons que nous avons parcouru dans un précédent article 

- Pas de problème de sérialisation des entités via un service WCF

- Nous Bénéficions du tracking de changements et du Lazy Loading

2 -  Utiliser des entités POCOs avec génération de proxy et le Template T4 C# POCO Entity Generator :

- Problème de sérialisation des entités via un service WCF

- Nous Bénéficions du tracking de changements et du Lazy Loading

3 -  Utiliser des entités POCOs sans génération de proxy et le Template T4 C# POCO Entity Generator :

- Pas de problème de sérialisation des entités via un service WCF

- Nous Bénéficions du Self Tracking

- Pas de Lazy loading

Faites votre choix selon vos besoins et vos exigences :)

Entity framework | POCO | T4 | WCF
Thursday, March 04, 2010 11:26:05 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

All content © 2012, Zied Nemili