2009-02-16 14 views
8

Diciamo che ho qualcosa chiamato Stuff nel mio database, con una proprietà chiamata Id. Dall'utente ottengo una sequenza di oggetti Range selezionati (o meglio li creo dal loro input) con gli Id che vogliono. Una versione ridotta che struct assomiglia a questo:C#, Linq2SQL: creazione di un predicato per trovare elementi all'interno di un numero di intervalli

public struct Range<T> : IEquatable<Range<T>>, IEqualityComparer<Range<T>> 
{ 
    public T A; 
    public T B; 
    public Range(T a, T b) 
    { 
     A = a; 
     B = b; 
    } 
    ... 
} 

Così si potrebbe per esempio hanno ottenuto:

var selectedRange = new List<Range<int>> 
    { 
     new Range(1, 4), 
     new Range(7,11), 
    }; 

Allora voglio usarlo per creare un predicato per selezionare solo le cose che hanno un valore tra quelli. Ad esempio, utilizzando il PredicateBuilder, posso per esempio farlo in questo modo:

var predicate = PredicateBuilder.False<Stuff>(); 
foreach (Range<int> r in selectedRange) 
{ 
    int a = r.A; 
    int b = r.B; 
    predicate = predicate.Or(ø => ø.Id >= a && ø.Id <= b); 
} 

e poi:

var stuff = datacontext.Stuffs.Where(predicate).ToList(); 

Quale funziona! Quello che vorrei fare ora è creare un metodo di estensione generico per creare quei predicati per me. Un po 'come questo:

public static Expression<Func<T,bool>> ToPredicate<T>(this IEnumerable<Range<int>> range, Func<T, int> selector) 
{ 
    Expression<Func<T, bool>> p = PredicateBuilder.False<T>(); 
    foreach (Range<int> r in range) 
    { 
     int a = r.A; 
     int b = r.B; 
     p = p.Or(ø => selector(ø) >= a && selector(ø) <= b); 
    } 
    return p; 
} 

problema qui, è che si blocca con un NotSupportedException a causa della chiamata di selezione (ø): Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.

credo che sia comprensibile. Ma c'è un modo per aggirare questo? Quello che mi piacerebbe finire con è così che ho potuto solo fare:

var stuff = datacontext.Stuffs.Where(selectedRange.ToPredicate<Stuff>(ø => ø.Id)); 

O, meglio ancora, creare qualcosa che restituisce un IQueryable in modo che ho potuto solo fare:

var stuff = datacontext.Stuffs.WhereWithin<Stuff>(selectedRange, ø => ø.Id); // Possibly without having to specify Stuff as type there... 

Così, ogni idee? Mi piace molto per ottenere questo lavoro, perché se non mi metterò un sacco di quei blocchi di codice foreach, creando predicati ...


Nota 1: Certo, sarebbe bello se potessi espandi a più di int, come DateTime e così via, ma non sei sicuro di come finire con l'utilizzo degli operatori> = e < = ... CompareTo funziona con linq-to-sql? Se no, non c'è nessun problema a crearne due. Uno per int e uno per DateTime, dal momento che sono principalmente i tipi per cui sarà usato.

Nota 2: Sarà utilizzato per la segnalazione, in cui l'utente sarà in grado di restringere ciò che viene fuori, in base a cose diverse. Voglio, voglio questo rapporto per quelle persone e quelle date.

risposta

7

L'utilizzo con generici è problematico, poiché C# non supporta gli operatori sui generici, ovvero è necessario scrivere manualmente l'espressione. E come abbiamo già visto, la stringa funziona in modo diverso. Ma per il resto, come su qualcosa di simile (non testata):

(modificati per più intervalli)

public static IQueryable<TSource> WhereBetween<TSource, TValue>(
     this IQueryable<TSource> source, 
     Expression<Func<TSource, TValue>> selector, 
     params Range<TValue>[] ranges) 
    { 
     return WhereBetween<TSource,TValue>(source, selector, 
      (IEnumerable<Range<TValue>>) ranges); 
    } 

    public static IQueryable<TSource> WhereBetween<TSource, TValue>(
     this IQueryable<TSource> source, 
     Expression<Func<TSource, TValue>> selector, 
     IEnumerable<Range<TValue>> ranges) 
    { 
     var param = Expression.Parameter(typeof(TSource), "x"); 
     var member = Expression.Invoke(selector, param); 
     Expression body = null; 
     foreach(var range in ranges) 
     { 
      var filter = Expression.AndAlso(
       Expression.GreaterThanOrEqual(member, 
        Expression.Constant(range.A, typeof(TValue))), 
       Expression.LessThanOrEqual(member, 
        Expression.Constant(range.B, typeof(TValue)))); 
      body = body == null ? filter : Expression.OrElse(body, filter); 
     }    
     return body == null ? source : source.Where(
      Expression.Lambda<Func<TSource, bool>>(body, param)); 
    } 

Nota; l'uso di Expression.Invoke significa che probabilmente funzionerà su LINQ-to-SQL ma non su EF (al momento, si spera risolti in 4.0).

con l'uso (testato su Northwind):

Range<decimal?> range1 = new Range<decimal?>(0,10), 
       range2 = new Range<decimal?>(15,20); 
var qry = ctx.Orders.WhereBetween(order => order.Freight, range1, range2); 

Generazione TSQL (ri-formattato):

SELECT -- (SNIP) 
FROM [dbo].[Orders] AS [t0] 
WHERE (([t0].[Freight] >= @p0) AND ([t0].[Freight] <= @p1)) 
OR (([t0].[Freight] >= @p2) AND ([t0].[Freight] <= @p3)) 

Proprio quello che volevamo ;-p

+0

Come sarebbe che il lavoro con tutta una serie di Gamma oggetti? – Svish

+0

Si potrebbe fare lo stesso con OrElse ... io aggiornare ... –

+0

E che cosa è questo "x" nella vostra param? – Svish

0

Hai trovato quell'errore , perché tutto per LINQ to SQL deve essere sotto forma di Expression. Prova questo

public static Expression<Func<T,bool>> ToPredicate<T>(
    this IEnumerable<Range<int>> range, 
    Expression<Func<T, int>> selector 
) { 
    Expression<Func<T, bool>> p = PredicateBuilder.False<T>(); 
    Func<T, int> selectorFunc = selector.Compile(); 
    foreach (Range<int> r in range) 
    { 
     int a = r.A; 
     int b = r.B; 
     p = p.Or(ø => selectorFunc(ø) >= a && selectorFunc(ø) <= b); 
    } 
    return p; 
} 

noti che compilo il selettore prima di utilizzarlo. Questo dovrebbe funzionare senza intoppi, ho usato qualcosa del genere in passato.

+0

! Come allora io uso il selettore in altre parole, quello che faccio a sostituire 'selettore (ø)' 'con in p = p.Or (ø => selettore (ø)> = un selettore && (ø) <= b); '? – Svish

+0

No, ottengo lo stesso' Metodo 'System.Object DynamicInvoke (System.Object [])' non ha alcuna traduzione supportato per SQL.' quando farlo = / – Svish

Problemi correlati