Newsgroup: it.comp.dotnet
Titolo: Specification + Linq: considerazioni e richiesta consigli.

Ciao ragazzi,
 
in questi giorni mi sono trovato a lavorare ad un progetto in cui avevo 
bisogno di implementare una specification. Per puro sollazzo ho iniziato 
a fare esperimenti "speculativi" e non arrivato a una considerazione: 
perché non provare a creare un sistema di specification componibili, 
parametrizzabili, ecc ecc... che possa anche fungere da generatore di 
clausole di selezione in query Linq?
 
Raccattando un po' di materiale qua e là, sono arrivato alla definizione 
di questa interfaccia:
 
interface ISpecification<T>
{
	ISpecification<T> And(ISpecification<T> s); //and logico;
	ISpecification<T> Or(ISpecification<T> s); //or logico
	ISpecification<T> Not(); //not logico;
	Expression<Func<T, bool>> AsPredicate(); //il nome del metodo fa pena...
	bool IsSatisfiedBy(T item);
}
 
definiamo la classe Foo in maniera molto semplice come:
 
class Foo
{
	public int Id { get; set; }
	public string Label { get; set; }
}
 
così, all'occorrenza, posso fare una cosa del tipo:
 
NHibernate.ISession s = ...;
ISpecification<Foo> spec = ...;
var select = s.Query<Foo>(spec.AsPredicate());
 
So far, so good. :-)
Mi trovo a dover fronteggiare qualche problema di coscienza per la 
gestione degli operatori. Per ora ho fatto tutto mediante combinazione 
di Expression. Mi chiedo: in uno scenario Linq2Sql (specialmente 
utilizzando NHibernate), quanto un approccio del genere può causarmi 
problemi a livello teorico?
Per la negazione di specification complesse, avevo pensato a tracciare 
la costruzione delle specification stesse e poi applicare De Morgan a 
mano :-P ma mi chiedo... siamo sicuri che serva? (La risposta implicita 
è: "no, fidati del fw, scemo!!!").
E infine l'ultima richiesta: potete consigliarmi qualche risorsa 
introduttiva su Expression e AST così come esistono in Linq?
 
Grazie,
Giulio
 
p.s. di seguito il codice
____________________________________________
using System;
using System.Linq;
using System.Linq.Expressions;
using Domain;
using Specification;
 
namespace Specification
{
     public interface ISpecification<T>
     {
         ISpecification<T> Not();
         ISpecification<T> And(ISpecification<T> specification);
         ISpecification<T> Or(ISpecification<T> specification);
         Expression<Func<T, bool>> Get();
         bool IsSatisfiedBy(T item);
     }
 
     public abstract class Specification<T> : ISpecification<T>
     {
         protected Expression<Func<T, bool>> InnerExpression { get; set; }
 
         public virtual ISpecification<T> Not()
         {
             Expression<Func<T, bool>> @this = this.Get();
             InvocationExpression invoke = Expression.Invoke(@this, 
@this.Parameters.Cast<Expression>());
             Expression<Func<T, bool>> not = Expression.Lambda<Func<T, 
bool>>(Expression.Not(invoke), @this.Parameters);
             return new LambdaSpecification<T>(not);
         }
 
         public virtual ISpecification<T> And(ISpecification<T> 
specification)
         {
             Expression<Func<T, bool>> @this = this.Get();
             Expression<Func<T, bool>> other = specification.Get();
             InvocationExpression invoke = Expression.Invoke(other, 
@this.Parameters.Cast<Expression>());
             Expression<Func<T, bool>> and = Expression.Lambda<Func<T, 
bool>>(Expression.And(@this.Body, invoke), @this.Parameters);
             return new LambdaSpecification<T>(and);
         }
 
         public virtual ISpecification<T> Or(ISpecification<T> 
specification)
         {
             Expression<Func<T, bool>> @this = this.Get();
             Expression<Func<T, bool>> other = specification.Get();
             InvocationExpression invoke = Expression.Invoke(other, 
@this.Parameters.Cast<Expression>());
             Expression<Func<T, bool>> or = Expression.Lambda<Func<T, 
bool>>(Expression.Or(@this.Body, invoke), @this.Parameters);
             return new LambdaSpecification<T>(or);
         }
 
         public virtual Expression<Func<T, bool>> Get()
         {
             return this.InnerExpression;
         }
 
         public virtual bool IsSatisfiedBy(T item)
         {
             return this.Get().Compile().Invoke(item);
         }
     }
     public class LambdaSpecification<T> : Specification<T>
     {
         public LambdaSpecification(Expression<Func<T, bool>> expression)
         {
             this.InnerExpression = expression;
         }
     }
}
 
namespace Domain
{
     public class FooLabelNullOrEmptySpecification : Specification<Foo>
     {
         public FooLabelNullOrEmptySpecification()
         {
             this.InnerExpression = (item) => 
string.IsNullOrWhiteSpace(item.Label);
         }
     }
 
     public class FooIdGreatherEqualThanZeroSpecification : 
Specification<Foo>
     {
         public FooIdGreatherEqualThanZeroSpecification()
         {
             this.InnerExpression = (item) => item.Id >= 0;
         }
     }
 
     public class Foo
     {
         public Foo(string label, int id)
         {
             this.Id = id;
             this.Label = label;
         }
         public int Id { get; set; }
         public string Label { get; set; }
     }
}
 

 del fw, scemo!!!").
 
già, fidati :-)
 
.m
 
-- 
blog @ //
milestone.topics.it
 
 
 

 query sul db aberranti.
 
Mumble mumble...
Ti spiego il problema di coscienza. Leggendo Evans/Fowler, c'è scritto 
che la specification (intesa come oggetto che espone un metodo 
.isSatistiedBy(Object o): bool) serve per la selezione ma anche per la 
validazione dello stato di un oggetto.
Avevo pensato anche a una cosa del tipo:
 
interface ISpecification<T>
{
	IQueryable<T> Select(IQueryable<T> source);
}
 
e considerare la validazione del singolo elemento come riuscita quando 
il risultato della .Select(.) ha un solo oggetto e questo coincide con 
l'unico oggetto di 'source'. Ma perderebbe un po' di poesia... :-)
 
Ciao,
Giulio
 
-- 
 

 query sul db aberranti.
 
Ma poi perche' "bellissimo"?  Non che la meta-programmazione non sia un 
argomento "affascinante"...  Ma, proprio approfondendo quella e simili 
materie, intervengono altre considerazioni, limiti intrinseci (per lo meno 
al paradigma corrente), e questioni di efficacia (in produzione!).  E i modi 
canonici di costruire applicazioni di qualita' ed efficacemente danno 
proprio l'indicazione opposta: *semplificare* il livello di astrazione.
 
Con questo non intendo che non hanno senso (per esempio) i linguaggi di alto 
livello, ma neanche si ottiene ordine dal chaos, cioe' limitandosi ad 
accumulare astrazioni su astrazioni.  Il mio consiglio a Giulio, ovviamente, 
sarebbe quello di invertire completamente la rotta e ri-scoprire la 
dimensione funzionale-relazionale degli invarianti del problema, e il DBMS 
che ne incarna la sostanza e i servizi.  Poi tutto il resto diventa molto 
molto piu' semplice.
 
-LV
 
 

 l'indicazione opposta: *semplificare* il livello di astrazione.
 
+1, "bellissimo" esteticamente, codice che guardi, che hai sudato e di 
cui sei apparentemente soddisfatto...ma...astrai asrtai e astrai... :-)
