27 novembre 2008

Peut-on utiliser des classes métiers n'héritant pas de "EntityObject" ?

La réponse est oui ! Au minimum, il est nécessaire que vos classes héritent des interfaces suivantes :

  • IEntityWithChangeTracker : Active le suivi des modifications.

  • IEntityWithKey. Facultatif : Expose une clé d'entité.

  • IEntityWithRelationships : Obligatoire pour les entités présentant des associations.



Pour plus d'informations, voici la documentation officielle.

Implémenter ces 3 interfaces sous entend que vos classes métiers doivent être modifiées pour utiliser la persistance avec Entity Framework. En d'autres termes, le principe de faible couplage / forte cohérence n'est pas respecté puisque les classes métiers contiennent à la fois de la logique métier et de la logique de persistance. De plus, dans certains scénarios, il n'est pas possible de modifier ses classes métiers.

On peut en tirer la conclusion suivante : Entity Framework ne permet pas d'utiliser des classes POCO (pour Plain Old Code Object). D'autres outils le permettent comme NHibernate avec l'utilisation d'objets proxy. Danny Simmons, développeur de l'équipe d'Entity Framework s'explique à ce sujet. Souhaitons que ce manque soit comblé dans les prochaines versions de l'outil.

Cependant, sachez qu'il est possible de contourner ce manque avec l'utilisation de la programmation orientée aspect. Sans rentrer dans les détails, PostSharp permet d'injecter du code lors d'une 2ème phase de compilation et donc d'implémenter les 3 interfaces nécessaires sur les classes POCO. Un projet a d'ailleurs été réalisé à ce sujet. Vous trouverez plus d'information sur son blog.

25 novembre 2008

Lecture des données

Entity Framework offre 3 types de possibilité pour requêter la source de données :

  • LinQ To Entities

  • Entity SQL

  • Les méthodes génératrices de requête de "ObjectQuery<T>"
LinQ To Entities permet d'écrire des requêtes syntaxiquement proche du SQL, et cela directement en C# ou VB.Net. L'avantage majeur vient du fait que les requêtes produites sont vérifiées à la compilation et non plus à l'exécution. Comme pour chaque implémentation de LinQ, chaque requête est transformée en un arbre d'expression (créé par l'objet ObjectContext) qui est traduit en SQL par le fournisseur de données final. Le résultat d'une requête LinQ to Entities est de type "IQueryable<T>" où T est le type spécifié dans la clause SELECT de la requête.
Voici donc un exemple de requête LinQ to Entities :

var requete = from publication in bdd.Publication
select publication;

Le résultat est de type "IQueryable<Publication>". Il est possible d'itérer sur les résultats avec une simple boucle "foreach". Vu que "IQueryable<T>" implémente un énumérateur, sachez que la requête vers la source de données est uniquement exécutée lors du parcours des résultats. Pour plus d'information, veuillez vous référencer à la documentation officielle de l'objet "IQueryable<Publication>".
Linq to Entities supporte un ensemble de méthodes pour requêter la source de données dont voici une liste non exhaustive (url de la liste complète) :

  • Select, SelectMany

  • Where

  • GroupJoin, Join

  • All, Any

  • Distinct

  • Except

  • Intersect

  • Union

  • OrderBy, OrderByDescending, ThenBy, ThenByDescending

  • GroupBy

  • Average

  • Count, LongCount

  • Max, Min, Sum

  • OfType, Cast

  • First, FirstOrDefault

  • Skip, Take


Voici l'exemple d'une requête sélectionnant seulement les publications qui contiennent le mot "C#" dans leur titre :

var requete = from publication in bdd.Publication
where publication.Titre.Contains("C#")
select publication;

Pour plus d'informations sur les requêtes LinQ, je vous invite à lire l'article de Thomas Lebrun.
Il est aussi possible de requêter la source de données grâce à Entity SQL. C'est un langage héritant du vocabulaire et de la grammaire de type SQL. Ce langage permet de récupérer des produits scalaires, des ensembles de résultat et des graphes d'objet. Pour une description du langage, veuillez vous référer à la documentation officielle.
Voici l'exemple d'une requête récupérant toutes les publications stockées en base :


string chaineRequete = "SELECT VALUE p FROM Publication AS p";

// Définition de la requête de type ObjectQuery<Publication>
ObjectQuery<Publication> requete = new ObjectQuery<Publication>(chaineRequete, bdd, MergeOption.NoTracking);

En dernier choix, vous pouvez utiliser les méthodes du générateur de requêtes de la classe "ObjectQuery<T>". En voici un exemple :

// Définition de la requête de type ObjectQuery<Publication>
var requete = bdd.Publication.SelectValue<Publication>("it");

La suite de l'article fera uniquement référence à LinQ to Entities, mais toutes les requêtes faites sur le modèle peuvent être réalisées avec Entity SQL ou grâce aux méthodes du générateur de requêtes de la classe "ObjectQuery<T>".
Chaque entité en relation avec une autre possède une collection d'entités connexes. Cependant, celle-ci n'est pas initialisée par défaut. Pour naviguer vers ces entités, il est nécessaire de les charger explicitement :

  • soit en utilisant la méthode "Include" de "ObjectQuery<T>",

  • soit en utilisant la méthode "Load" appartenant à la classe "EntityReference<T>" ou à "EntityCollection<T>".


L'exemple suivant utilise la méthode "Include" pour récupérer explicitement, lors de la requête, l'éditeur et les auteurs associés aux publications :

using (Modele bdd = new Modele())
{
var requete = from publication in bdd.Publication.Include("Auteur").Include("Editeur") select publication;

requete.ToList().ForEach((Publication p) =>
{
Console.WriteLine("Publication : ID = {0} Titre = {1}", p.Id, p.Titre);

Console.WriteLine(" Editeur : ID = {0} Nom = {1}", p.Editeur.Id, p.Editeur.Nom);

p.Auteur.ToList().ForEach(a => Console.WriteLine(" Auteur : ID = {0} Nom = {1} {2}",a.Id,a.Prenom,a.Nom));
});
}

Voici un autre exemple avec l'utilisation de la méthode "Load". Remarquez que chaque collection ou chaque référence liée à une entité possède une propriété "IsLoaded" permettant de savoir si la ou les entités connexes sont chargées.

using (Modele bdd = new Modele())
{
var requete = from publication in bdd.Publication select publication;

requete.ToList().ForEach((Publication p) =>
{
if (!p.EditeurReference.IsLoaded)
p.EditeurReference.Load();

Console.WriteLine(" Editeur : ID = {0} Nom = {1}", p.Editeur.Id, p.Editeur.Nom);

if (!p.Auteur.IsLoaded)
p.Auteur.Load();

p.Auteur.ToList().ForEach(a => Console.WriteLine(" Auteur : ID = {0} Nom = {1} {2}", a.Id, a.Prenom, a.Nom));
});
}

23 novembre 2008

Quels sont les SGBDR supportés ?

La couche "Object Services" d'Entity Framework s'appuie sur le fournisseur de données "EntityClient" pour qu'il exécute les requêtes LinQ transformées au préalable en arbres d'expression. "EntityClient" a pour but d'accéder aux données définies dans le modèle EDM décrits par les 3 fichiers CSDL, MSL et SSDL. Il génère ensuite un arbre d'expressions relatif aux données du schéma logique et l'envoie à un fournisseur de données spécialisées, afin que celui-ci transforme l'arbre en requêtes SQL.

Ce dernier fournisseur de données est spécifique à la base de données utilisée. Le fournisseur utilisé est défini par la chaine de connexion utilisée par l'objet "EntityConnection", qui a la responsable d'ouvrir la connexion de la source de données.

Voici un exemple de chaine de connexion, définit par l'assistant de Visual Studio 2008 :
metadata=res://*/modele.csdlres://*/modele.ssdlres://*/modele.msl;provider=System.Data.SqlClient;provider
connection string="Data Source=TLS-LOG-PO-PMO\SQLEXPRESS;Initial Catalog=helloentityfx;Integrated Security=True;MultipleActiveResultSets=True"


La 1ère partie concerne les métadonnées de la connexion. On y voit la référence aux 3 schémas du modèle EDM, et du fournisseur de données utilisé pour la base de données. Ici, c'est la classe "System.Data.SqlClient" qui permet de communiquer avec une base de type SQL Serveur 2005.

Il est donc possible de spécifier un autre fournisseur de données pour utiliser, par exemple, une base Oracle, MySQL ou PostgreSQL. Voici une liste non exhaustive des fournisseurs de données compatibles avec Entity Framework :

Il est aussi possible de créer son propre fournisseur de données. Un projet sur Codeplex fournit le code nécessaire.

20 novembre 2008

Concept d'Entity Framework

Entity Framework est la solution de mapping objet relationnel proposée par Microsoft. Son but est de fournir la couche d'abstraction nécessaire aux développeurs pour qu'ils n'accèdent plus directement à la base de données, mais par l'intermédiaire d'entités définies par un modèle appelé EDM (Entity Data Model).

Ce modèle est divisé en 3 parties :

  • Le schéma conceptuel : Il regroupe la définition des entités, des ensembles d'entités et des fonctions utilisées. Ces éléments sont définis dans un fichier XML appelé CSDL (Conceptual Schema Definition Language).

  • Le schéma logique : Celui-ci correspond à la définition des tables, vues et procédures stockées déclarées dans la base de données. Toutes ces informations sont regroupées dans un fichier XML appelé SSDL (Store Schema Definition Language).

  • Schéma de liaison : Il définit les liaisons entre le schéma conceptuel et le schéma logique. Il associe entre autre les entités déclarées dans le fichier CSDL aux tables définies dans le fichier SSDL. Ce mapping est inscrit dans le fichier XML MSL (Mapping Specification Language).



Visual Studio 2008 SP1 intègre aussi un assistant permettant de générer un modèle EDM, et ce de manière graphique. Il ressemble assez à l'assistant LinQ to SQL pour les connaisseurs et il offre un confort d'utilisation bien meilleur que l'outil en ligne de commande, au détriment d'un certain manque de fonctionnalités.

Par exemple, il n'est pas possible de définir de type complexe avec l'assistant, alors que "EdmGen.exe" le permet. Rassurez-vous, vous pouvez commencer à utiliser l'assistant (gain de temps évident) et utiliser par la suite l'outil en ligne de commande. Néanmoins, il s'avère regrettable d'avoir recours à ce genre de manipulation. La prochaine version de l'assistant devrait corriger cela; et bien d'autres points.

10 novembre 2008

Afficher correctement du code dans vos blogs

Dernièrement, j'ai cherché une manière d'afficher correctement du code sur mon blog. En cherchant un petit peu, j'ai trouvé la balise HTML <pre> qui garde l'indentation des lignes. Rien qu'avec cela, le code publié est déjà beaucoup plus lisible. En voici un exemple :

using System.Collections.Generic;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();};
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};


Malheureusement, je n'ai pas trouvé de script ou d'addin fournissant une solution "clé en main" de mise en forme du code, supportant la colorisation de mots clés et l'indentation. Je vous propose donc un petit bout de code Javascript permettant de faire cela.

Voici les objectifs du script de mise en forme:

  • Rechercher toutes les balises ayant l'attribut name comportant la valeur code et appliquer la fonction formatterElement

  • La fonction formatterElement recherche l'ensemble des mots clés fournit par le tableau motCles et rajoute autour la balise <span class="motcle"> mot clé </span>

Pour installer le script sur un blog de type Blogspot, copiez les styles CSS et les fonctions Javascript suivants quelque part dans la balise head de votre modèle.

<style type="text/css">
div.code
{
background : #F0F0F0;
border: 1px solid black;
padding:5px;
margin:5px;
}
span.motcle
{
color:Blue;
}
</style>
<script type="text/javascript">
var regexpEchampement = unescape("([ \(\)\{\},\t\n\r%3C%3E\[;&])");
var motsCles = [
"public",
"private",
"protected",
"sealed",
"partial",
"static",
"class",
"struct",
"interface",
"using",
"namespace",
"object",
"string",
"int",
"bool",
"float",
"double",
"decimal",
"void",
"in",
"out",
"extern",
"true",
"false",
"new",
"if",
"for",
"foreach",
"throw",
"while",
"return",
"case",
"break",
"try",
"catch",
"finally",
"get",
"set",
"typeof",
"delegate"
];

function formatterElements(nameElement) {
var codeElements = document.getElementsByName(nameElement);
eval(unescape("for (var j = 0; j %3C codeElements.length; j++) { formatterElement(codeElements[j]); }"));
}

function formatterElement(element) {
element.className = "code";

var code = null;
if ("outerHTML" in element) {
}
else {
code = element.innerHTML;

var loop = "for ( var i = 0 ; i %3C motsCles.length ; i++) { var reg = new RegExp(regexpEchampement + \"(\" + motsCles[i] + \")\" + regexpEchampement, \"g\"); code = code.replace(reg, unescape(\"%241%3Cspan%20class%3D%27motcle%27%3E%242%3C/span%3E%243\"));}";
eval(unescape(loop));

element.innerHTML = code;
}
}
</script>


Remarquez que le tableau motsCles contient pas mal de mots clés du langage C#. Libre à vous d'ajouter/supprimer des mots clés. De même, vous pouvez aussi modifier les 2 styles CSS.

Ensuite, ajoutez ce petit bout de javascript juste avant la fermeture de la balise body (</body>):
<script type="text/javascript">
formatterElements("code");
</script>

Voila le script est installé.
Pour l'utiliser, il suffit juste d'ajouter une balise pre et une balise div ou autre avec l'attribut name (ainsi que id="code", à cause de la méthode getElementByName d'IE qui regarde uniquement l'id des éléments) contenant la valeur code. Exemple :

<pre><div name="code" id="code" ></pre>
public Impersonation(string pIdentifiant, string pMotDePasse, string pDomaine )
{
_identifiant = pIdentifiant;
_motDePasse = pMotDePasse;
_domaine = pDomaine;
}
</div></pre>

Ce qui donne :

public Impersonation(string pIdentifiant, string pMotDePasse, string pDomaine )
{
_identifiant = pIdentifiant;
_motDePasse = pMotDePasse;
_domaine = pDomaine;
}


EDIT : Pour l'instant, le script fonctionne à merveille sous Firefox, mais ne fonctionne pas vraiment sous IE 7/8. Cela vient du fait que toute affectation de la variable innerHtml d'un élément sous Internet Explorer normalise la chaine de caractères, et enlève l'ensemble tous les retours à la ligne et les espaces de plus de 2 caractères. Pour l'instant, je n'ai pas trouvé de contournement ...

6 novembre 2008

1er article sur Developpez.com

Ca y est !

Mon 1er article sur Developpez.com vient d'être publié hier.
Il traite de la génération de code dans Visual Studio 2008 et plus particulièrement de la création d'un Custom Tool. Celui-ci créé à partir d'un fichier XML (ici, un fichier de configuration Sharepoint) une classe associée. Cette dernière peut être utilisée pour récupérer facilement des GUID, chose très utile dans la programmation Sharepoint.

A noter que la classe est générée à l'aide de l'API CodeDom. Une autre alternative aurait été de générer la classe à partir d'une feuille XSLT.

En tout cas, merci à l'équipe de Developpez et surtout à Louis-Guillaume Morand, responsable de la section .Net, qui m'a aidé dans la publication et mon intégration dans l'équipe de rédaction du site.

5 novembre 2008

Droits insuffisants, pensez Impersonation !

Bien souvent, lorsque l'on développe une application web ou même parfois une application Windows (Winforms, WPF, ...), le compte Windows utilisé par l'application ne possède pas les droits suffisants pour effectuer des actions sur le système de fichier, le domaine, la base de données ou sur tout autre système.

Vous avez toujours la possibilité de modifier le compte utilisé. Par exemple, pour une application ASP.Net hébergé dans IIS, il est possible de changer le compte dans les paramètres du pool d'application. Cependant, ce n'est pas forcément une bonne solution. Cela peut même casser votre système d'authentification si celui-ci est de type Windows.

Il est aussi possible d'ajouter les droits nécessaires au compte utilisé par l'application. Mais cela est bien souvent maladroit. Le compte utilisé pour une application ASP.Net est par défaut "Service réseau". Il peut paraître indélicat d'ajouter des droits de d'ajout ou de suppression de dossier à ce compte. Une autre application web utilisant ce compte pourrait être détournée intentionnellement pour supprimer, modifier ou récupérer des données importantes ...

Une bonne solution est d'utiliser l'impersonation. Cela consiste à changer le compte utilisé pour exécuter la logique de votre application par un compte qui possède les droits nécessaires, c'est à dire un compte de service. Pour réaliser ceci, voici une petite classe utilitaire :


using System;
using System.Runtime.InteropServices;
using System.Security.Principal;

public class Impersonation
{
[DllImport("advapi32", SetLastError = true)]
private static extern bool LogonUser(string pUserName, string pDomaine, string pMotDePasse,
int pLogonType, int pLogonProvider, out IntPtr pJeton);
[DllImport("advapi32", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr pHandle);
private string _identifiant;
private string _motDePasse;
private string _domaine;
private WindowsImpersonationContext _impContext;

public Impersonation(string pIdentifiant, string pMotDePasse, string pDomaine )
{
_identifiant = pIdentifiant;
_motDePasse = pMotDePasse;
_domaine = pDomaine;
}

public void Impersonifier()
{
IntPtr handle = new IntPtr(0);
if (!LogonUser(_identifiant, _domaine, _motDePasse, 3, 0, out handle))
throw new Exception("Erreur d'authentification");
_impContext = new WindowsIdentity(handle).Impersonate();
CloseHandle(handle);
}

public void Fermer()
{
_impContext.Undo();
}
}


Tout est articulé autour de la fonction LogonUser qui permet de récupérer un jeton d'authentification. Elle prend entre en paramètre l'identifiant, le mot de passe et le domaine d'un compte de domaine ou local (SAM).

Voici maintenant un petit exemple d'utilisation :


try
{
Impersonation imp = new Impersonation("identifiant", "domaine.fr", "motdepasse}");
imp.Impersonifier();
try
{
// Votre code ncessitant des droits suprieurs
}
catch { }
finally
{
imp.Fermer();
}
}
catch { }