Accueil

EF5 : Accéder au DBContext depuis une entité

by Admin 28. juin 2013 10:09

Entity Framework, et les ORM en général, nous apportent une simplicité d'accès à notre de base de donnée époustouflante. Pourtant depuis la version 4, les décisions prisent par Microsoft concernant la génération des entités et l'accès à leur état en BDD (aka EntityState) me laissent un peu perplexe ... Faire le choix de ne plus faire dériver les entités métier d'une classe de base est certe conceptuellement plus propre, mais c'est surtout moins pratique pour nous autres, pauvres petits développeurs !

Le problème se situe donc dès que l'on a besoin d'avoir des informations de la base de donnée alors que nous sommes dans une entité. Par exemple son état, mais aussi simplement lors d'une routine métier où il serait nécessaire d'aller interroger une table qui serait en dehors de notre graphe d'objet.

Actuellement nous avons 2 choix :

  1. Passer le context de base de donnée en paramètre de la méthode
  2. Réouvir un nouveau context à la demande avec un using
Rien de très intéressant donc, dans le premier cas, le paramètre devient vite lourd si il y a empilement de méthodes et que fait-on dans les getter ? Dans le second cas, si vous souhaitez rattacher l'entité nouvellement chargée à votre graphe d'objet, vous vous heurterez à des problèmes cross-context ...

Si vous cherchez un peu sur le net, vous trouverez des solutions à base de IEntityWithRelationships par exemple ici ou avec des attach/detach mais rien de très convainquant. J'ai donc mis en place un système tout simple grâce au fameux évènement OnEntityMaterialized dont je vous ai déjà parlé dans ce post. L'idée est de mémoriser le context dans chaque entité, pour celà j'utilise une classe de base qui est ajouté automatique avec le T4 :

public abstract partial class BaseEntitie 
          : INotifyPropertyChanged, INotifyDataErrorInfo, IEntityId
{
    /// <summary>
    /// Permet de savoir si l'entité a completement fini de se charger depuis la BDD
    /// </summary>
    public bool IsMaterialized { get; private set; }

    /// <summary>
    /// Permet de se placer juste après que l'entité ait été chargée depuis la BDD
    /// </summary>
    virtual protected void AfterEntitieMaterialized() {}

    /// <summary>
    /// Donne le context de BDD avec lequel l'entité a été chargée
    /// </summary>
    public DbContext BddContext { get; private set; }

    /// <summary>
    /// Marque l'entité "Materlialized" qui assure le fait qu'elle a finie se charger
    /// </summary>
    public void Materialize(DbContext context)
    {
        this.BddContext = context;
        this.IsMaterialized = true;
        this.AfterEntitieMaterialized();
    }
}

Le pointeur "BddContext" me servira donc à récupérer le context de BDD dès que j'en aurais besoin. Il suffit juste d'appeler ma méthode Materialize() à 2 endroits : après le chargement depuis la bdd, et après la sauvegarde pour les nouvelles entités :

public partial class BddOpyContext
{
    partial void ApresConstructeur()
    {
        // Evenement indiquant la fin de changement de l'entité
        ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized+=OnEntityMaterialized;
    }

    /// <summary>
    /// Lorsque l'entité a fini de se charger en bdd, on la maque IsMaterialized
    /// Ainsi la validation et les propertiesChanged peuvent être déclanchés
    /// </summary>
    private void OnEntityMaterialized(object sender, ObjectMaterializedEventArgs e)
    {
        var entity = e.Entity as BaseEntitie;
        if (entity != null)
            entity.Materialize(this);
    }

    /// <summary>
    /// Override du SaveChanges
    /// </summary>
    public override int SaveChanges()
    {
        int retval = base.SaveChanges();
        // Dans le cas des entités nouvelles (new par le développeur) 
        // on place le IsMaterialized à la main
        this.ChangeTracker.Entries()
            .Select(e => e.Entity)
            .OfType<BaseEntitie>()
            .Where(e => e.IsMaterialized == false)
            .ToList()
            .ForEach(s => s.Materialize(this));
        return retval;
    }
}

Voilà qui lève un bon nombre de limitation dans le développement des modèles métiers qui sont très liés à la base de donnée (Data-Driven).