5 Stimmen

LINQ : Generika mit IQueryable

Ich muss einige "Lookup"-Abfragen zu meiner C#.NET-Anwendung hinzufügen. Im Grunde wird es viele Tabellen geben, die alle das gleiche Schema haben, aber unterschiedliche Werte enthalten.

Ich bin damit konfrontiert, immer wieder denselben Code zu schreiben (es sollte eine OO-Methode dafür geben )

[Bearbeiten - unten ist geändert, um mehr vollständige Informationen zu zeigen] Wie auch immer, was ich gerne tun würde, ist:

public List<GenericLookupE>         GetLookupItems( string what )
{
    // create db thing
    if ( "regions" == what )  return FetchLookup( db.lkRegions.AsQueryable() );
    if ( "partners" == what ) return FetchLookup( db.lkPartners.AsQueryable() );
    if ( "funders" == what )  return FetchLookup( db.lkFunders.AsQueryable() );

    return null; // or something more intelligent than that =)
}

private List<GenericLookupE>        FetchLookup<T>( IQueryable<T> lookup )
{
    return lookup.OrderBy( p => p.Sequence ).Select
    (   p => new GenericLookupE()
        {
            ID      = p.ID
            ,Label      = p.Label
            ,StateCode  = p.StateCode
            ,Sequence   = p.Sequence
        }
    ).ToList();
}

Das Problem ist natürlich, dass der Compiler nicht weiß, was 'p => p.Sequence' ist. Irgendwelche Ideen?

Ich danke Ihnen allen.

3voto

Sheridan Bulger Punkte 1214

Typbeschränkungen für Generika sind Ihr Freund. Mit einem Basistyp oder einer Schnittstelle, die Sequence:

where T : IMyBaseInterface

http://msdn.microsoft.com/en-us/library/bb384067.aspx

2voto

Sergey Kalinichenko Punkte 694383

Fügen Sie eine gemeinsame Schnittstelle zu Ihrem Klassen, definieren Sequence und fügen Sie eine Einschränkung zu Ihrer generischen Anforderung hinzu Parameter, um die gemeinsame Schnittstelle zu implementieren.

interface ICommonInterface { // This is an awful name, please pick something better
    int Sequence { get }
}

private List<GenericLookupE> FetchLookup<T>( IQueryable<T> lookup ) where T : ICommonInterface {
    // Your stuff here
}

1voto

Ani Punkte 107342

Die saubere Lösung ist die Verwendung eines gemeinsamen Basistyps oder einer gemeinsamen Schnittstelle.

Wenn dies aus irgendeinem Grund nicht möglich ist, gibt es eine Möglichkeit, den gewünschten Ausdruck dynamisch zu konstruieren:

// Returns an expression of the form: p => new GenericLookupE
// { ID = p.ID, Label = p.Label, StateCode = p.StateCode, Sequence = p.Sequence }
// Can be written as a more generic 'auto-mapper', but
// this sample only solves your specific problem.
static Expression<Func<T, GenericLookupE>> GetLookupMapper<T>()
{ 
    var parameter = Expression.Parameter(typeof(T), "p");

    string[] properties = { "ID", "Label", "StateCode", "Sequence" };

    var bindings = from propName in properties
                   let source = Expression.Property(parameter, propName)
                   let target = typeof(GenericLookupE).GetProperty(propName)
                   select Expression.Bind(target, source);    

    var newExp = Expression.New(typeof(GenericLookupE));    
    var body = Expression.MemberInit(newExp, bindings);

    return Expression.Lambda<Func<T, GenericLookupE>>(body, parameter);
}

Und dann wird Ihre Methode:

private List<GenericLookupE> FetchLookup<T>(IQueryable<T> lookup)
{
    return lookup.Select(GetLookupMapper<T>())
                 .OrderBy(p => p.Sequence)
                 .ToList();
}

Ich bezweifle allerdings, dass Sie so etwas brauchen.

0voto

Scott Rippey Punkte 15184

Ihre Objekte Regions , Partners y Funders scheinen die gleichen Eigenschaften zu haben ( ID , Label , StateCode y Sequence ). Dies wird gemeinhin als "Duck-Typing" bezeichnet.

Um jedoch eine "generische", stark typisierte Lösung zu schreiben, die mit allen von ihnen funktioniert, müssen sie sich alle von einer gemeinsamen Schnittstelle oder einem Basistyp ableiten, der diese Eigenschaften auflistet!

Ich sehe die folgenden 3 Lösungen:

  1. Machen Sie Regions , Partners y Funders einen Basistyp erben (wie z. B. GenericLookupE ), oder sie müssen eine gemeinsame Schnittstelle implementieren (z. B. IGenericLookupE ).
    • Wenn diese Klassen für Sie erstellt werden, könnte es schwierig sein, dies zu tun
    • Wenn Sie dies tun KÖNNEN, dann können Sie einfach .Cast auf den richtigen Typ ohne Select neue Objekte ( Siehe Update )
  2. を使用します。 dynamic Typ oder Reflektion, um die Eigenschaften zu erhalten (was zwar nur ein lockerer Typ ist, aber funktionieren kann)
  3. Erstellen Sie einmalige Lösungen für jeden Typ (was Sie eigentlich vermeiden wollen)

Update

Hier finden Sie einige Möglichkeiten, wie Sie Ihre Datenbankobjekte je nach Framework eine gemeinsame Schnittstelle implementieren lassen können:

Hinzufügen einer Basisklasse (oder Schnittstelle) zu allen LINQ to SQL-Entitäten

Implementierung einer Schnittstelle auf einem LINQ to SQL-Objekt unter Verwendung einer partiellen Klasse

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X