posts - 315, comments - 268, trackbacks - 15

My Links

News

View Pietro Libro's profile on LinkedIn

DomusDotNet
   DomusDotNet

Pietro Libro

Tag Cloud

Article Categories

Archives

Post Categories

Blogs amici

Links

Expression, LambdaExpression, Entity Framework (e DDD)

Se lavoriamo con EF utilizzando l’approccio Code First, in alcuni scenari,  il mapping (ad esempio nel caso di classi di dominio già esistenti) potrebbe essere un task non banale. Riprendiamo un esempio di qualche tempo fa , aggiungendo all’Object Model l’entità Articolo, come riassunto dal Class Diagram seguente:

image

Fattura espone una collezione di oggetti RigoFattura  accessibile (dall’esterno) tramite l’IEnumerable<RigoFattura>  pertanto, l’unico modo di aggiungere nuove righe alla fattura è l’utilizzo del metodo AddRigoFattura. Ancora, la classe RigoFattura contiene una proprietà CodiceArticolo che ritorna la proprietà Codice dell’istanza della classe Articolo referenziata, ma anche in questo caso, l’unico modo per aggiungere un Articolo  è tramite un metodo : ImpostaArticolo.  A questo punto qualcuno potrebbe dire, “Si ok, ma dov’è il problema ?” Come descritto nel post precedente, durante il mapping, nell’override dell’OnModelBuilder, abbiamo qualche problema in fase di compilazione:

image

Questo perché le Fluent API, come è facile intuire, non vanno d’accordo con i membri Protected (o comunque non accessibili dall’esterno). In questi casi, la soluzione che possiamo adottare è quella di creare una serie di Extension Methods che basandosi sul nome delle Proprietà e Navigation Properties ci permettano di creare delle espressioni da poter utilizzare nel nostro scenario di mapping:

public static class Methods
{
    private static Expression<Func<T, K>> CreateExpression<T, K>(String propertyName)
    {
        Type type = typeof(T);
        PropertyInfo pi = type.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        if (pi == null) throw new ArgumentException("propertyName is not valid.");
        ParameterExpression argumentExpression = Expression.Parameter(type, "x");
        MemberExpression memberExpression = Expression.Property(argumentExpression, pi);
        LambdaExpression lambda = Expression.Lambda(memberExpression, argumentExpression);
        Expression<Func<T, K>> expression = (Expression<Func<T, K>>)lambda;

        return expression;
    }
    public static PrimitivePropertyConfiguration Property<TEntity, KPropertyType>(this EntityTypeConfiguration<TEntity> mapper, String propertyName)
        where TEntity : class
        where KPropertyType : struct
    {
        Expression<Func<TEntity, KPropertyType>> expression = CreateExpression<TEntity, KPropertyType>(propertyName);
        return mapper.Property(expression);
    }

    public static DependentNavigationPropertyConfiguration<TEntity> WithMany<TEntity, KTargetEntity>(
        this  OptionalNavigationPropertyConfiguration<TEntity, KTargetEntity> mapper, String propertyName)
        where TEntity : class
        where KTargetEntity : class
    {
        Type type = typeof(KTargetEntity);
        ParameterExpression argumentExpression = Expression.Parameter(type, "x");
        PropertyInfo pi = type.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        MemberExpression memberExpression = Expression.Property(argumentExpression, pi);
        LambdaExpression lambda = Expression.Lambda(memberExpression, argumentExpression);
        var expression = (Expression<Func<KTargetEntity, ICollection<TEntity>>>)lambda;

        return mapper.WithMany(expression);
    }

    public static RequiredNavigationPropertyConfiguration<TEntity, TTargetEntity> HasRequired<TEntity, TTargetEntity>(this EntityTypeConfiguration<TEntity> mapper, String propertyName)
        where TEntity : class
        where TTargetEntity : class
    {
        Expression<Func<TEntity, TTargetEntity>> expression = CreateExpression<TEntity, TTargetEntity>(propertyName);
        return mapper.HasRequired(expression);
    }

    public static EntityTypeConfiguration<TEntity> HasKey<TEntity, KKeyType>(this EntityTypeConfiguration<TEntity> mapper, String propertyName)
        where TEntity : class
        where KKeyType : struct
    {
        Expression<Func<TEntity, KKeyType>> expression = CreateExpression<TEntity, KKeyType>(propertyName);
        EntityTypeConfiguration<TEntity> m = mapper.HasKey<KKeyType>(expression);
        return mapper;
    }

    public static ManyNavigationPropertyConfiguration<T, U> HasMany<T, U>(this EntityTypeConfiguration<T> mapper, String propertyName)
        where T : class
        where U : class
    {
        Type type = typeof(T);
        PropertyInfo pi = type.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        if (pi == null) throw new ArgumentException("propertyName is not valid.");
        ParameterExpression parameterExpression = Expression.Parameter(type, "x");
        MemberExpression memberExpression = Expression.Property(parameterExpression, pi);
        LambdaExpression lambda = Expression.Lambda(memberExpression, parameterExpression);
        Expression<Func<T, ICollection<U>>> expression = (Expression<Func<T, ICollection<U>>>)lambda;
        
        return mapper.HasMany(expression);
    }

A questo punto il gioco è facile, definendo il modello in questo modo:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<RigoFattura>();
    modelBuilder.Entity<RigoFattura>().ToTable("RigoFattura");
    modelBuilder.Entity<RigoFattura>().Ignore(rf => rf.CodiceArticolo);

    modelBuilder.Entity<RigoFattura>().HasRequired<RigoFattura, Articolo>("Articolo");            

    modelBuilder.Entity<Fattura>().ToTable("Fattura");
    modelBuilder.Entity<Fattura>().HasKey<int>(f => f.Numero);
    modelBuilder.Entity<Fattura>().HasMany<Fattura, RigoFattura>("RigheFatture");

    modelBuilder.Entity<Articolo>().ToTable("Articoli");
    modelBuilder.Entity<Articolo>().HasKey(a => a.Codice);            
}   

Tutto funziona correttamente, e l’esecuzione del codice porta alla corretta creazione del Database come riassunto dal Database Diagram seguente:

image

Gli Extension Methods descritti in precedenza, non servono solo a coprire lo scenario descritto nel post, ma anche nel caso di : WithMany, HasKey, HasMany, Property ecc… Aggiungere altri metodi personalizzati è relativamente semplice. Cosa facciamo nel codice: 1) tramite reflection recuperiamo le proprietà che entrano in gioco durante lo scenario di mapping e che non sono visibili dall’esterno 2) Creiamo una ParameterExpression con il tipo entità (TEntity) 3) Creiamo una MemberExpression a partire dalla proprietà ottenuta via reflection e dalla ParameterExpression ottenuta al punto precedente 3) Mettiamo tutto insieme, creiamo una Lambda Expression e da questa una Expression<Func<,>> accettata dal  mapper. In tre “semplici” passi aggiungiamo uno “strato” che ci permette di coprire scenari di mapping “ostici”.

Nota bene: questo post non vuole essere in contraddizione con quanto precedentemente scritto a proposito di DDD ed EF, ma presenta solo un workaround per bypassare il problema, ma con tutte le problematiche del caso: di solito le parole “Stringhe” e “Refactoring” non vanno molto d’accordo (se ad esempio cambiamo il nome di una proprietà di una classe, la stringa associata non viene cambiata, creando una “fantastica” eccezione a runtime). Si spera che le prossime release di Entity Framework permettano di risolvere anche questo scoglio.

Print | posted on giovedì 10 novembre 2011 09:11 | Filed Under [ C# Entity Framework 4.1 Entity Framework 4.2 ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET