2016-12-07 2 views
4

Ich Herumalbern mit einer kleinen SQL-Bibliothek mit einer fließend API erstellen und will, so etwas tun:C# Fluent API Mit Dynamic Func <> Ausbau

var person = connection.GetOne<Person>("select * from [Person] where [Id] = 1") 
         .WithMany<Pet>("select * from [Pet] where [PersonId] = 1") 
         .WithMany<Address>("select * from [Address] where [PersonId] = 1]") 
         .Build((person, pets, addresses) => 
         { 
          person.Pets = pets; 
          person.Addresses = addresses; 

          return person; 
         }); 

Ich baue viel habe die fließend API vor, aber alle waren viel einfacher und nicht so stark auf Generika angewiesen. Meine Frage ist speziell, wie man die Endfunktion Build() implementiert. Ich bin mir nicht sicher, ob es überhaupt möglich ist (scheint nicht so, aber vielleicht ist Expression der Schlüssel?), Aber wie kann ich die generischen Typen verfolgen, die in den Aufrufen der höheren Kettenmethoden angegeben sind (zB GetOne <> (), WithMany <>()), so dass, wenn die .Build() aufgerufen wird die Func <> die benötigt wird, ist der richtige Typ?

In dem obigen Beispiel ich die Func < wollen würde> Func < Person, IEnumerable <Pet>, IEnumerable <Adresse> > so dass der Entwickler das Stammelement konstruieren kann sein (Person) in welcher Weise auch immer sie brauchen - In diesem Fall füllen Sie einige Sammlungen mit den Ergebnissen von ein -> vielen Abfragen.

Gibt es eine Möglichkeit, dies zu tun oder bin ich kein Glück? Es scheint wie eine Menge Orte, die ich geschaut habe ähnliche Dinge tun, die ganzen:

Func<In1, TResult> 
Func<In1, In2, TResult> 
Func<In1, In2, In3, TResult> 
...etc, etc 

... Art der Sache, die Sie zu einer maximalen Anzahl von Parametern für die Funktion offensichtlich begrenzt.

Jede Hilfe oder Hinweise würden sehr geschätzt werden.

+0

schauen Sie, wie LINQ implementiert IEnumerable.Select https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,bc79a642e00b8681 – pm100

Antwort

0

Wenn Sie eine starke automatische Vervollständigung wünschen und verhindern, dass jemand schreibt .Build(person => {}), wenn Sie (person, pet) => {} erwarten, müssen Sie in Ihrem Builder ausführlich sein.

Hier ist ein Beispiel für drei Ebenen tief:

class Person { public IEnumerable<Pet> Pets { get; set;} } class Pet {} class Address{} 

public static class Builder 
{ 
    public static Level1<T> GetOne<T>(this object obj, string blah) { 
     return new Level1<T>(); 
    } 
} 
public class Level1<T1> { 
    public Level2<T1, T2> WithMany<T2>(string blah) { return new Level2<T1, T2>(); } 
    public T1 Build(Func<T1, T1> pred) { return pred(default(T1)); } 
} 
public class Level2<T1, T2> 
{ 
    public Level3<T1, T2, T3> WithMany<T3>(string blah) { return new Level3<T1, T2, T3>(); } 
    public T1 Build(Func<T1, IEnumerable<T2>, T1> pred) { return pred(default(T1), default(IEnumerable<T2>)); } 
} 
public class Level3<T1, T2, T3> 
{ 
    public T1 Build(Func<T1, IEnumerable<T2>, IEnumerable<T3>, T1> pred) { 
     return pred(default(T1), default(IEnumerable<T2>), default(IEnumerable<T3>)); 
    } 
} 

Wo wir hier starke Typisierung erhalten:

obj.GetOne<Person>("select * from [Person] where [Id] = 1") 
.WithMany<Pet>("select * from [Pet] where [PersonId] = 1") 
.WithMany<Address>("select * from [Address] where [PersonId] = 1]") 
.Build((person, pets, addresses) => { 
    person.Pets = pets; 
    return person; 
}); 

Oder

obj.GetOne<Person>("select * from [Person] where [Id] = 1") 
.WithMany<Pet>("select * from [Pet] where [PersonId] = 1") 
.Build((person, pets) => { return person; }); 

In Bezug auf Ihre Notiz über eine begrenzte Menge an Parameter - das ist richtig.Ich glaube nicht, dass es trotzdem etwas zu umgehen gibt, während ich leider stark tippen muss.

+1

Danke, Rob. Ich habe diese Technik benutzt und, obwohl ich ausführlich bin, mache ich das, was ich erreichen möchte. Ich unterstütze 12 Ebenen von Eins-zu-Viele-Verbänden, von denen ich denke, dass sie für einen Großteil der Nutzungen ausreichen sollten. –

0

Es hängt davon ab, wie viele Methoden Sie hinzufügen können, aber jeder sollte wahrscheinlich ein anderes Objekt vom Typ T mit denselben Methoden zurückgeben. Dazu müssen Sie alle Funktionen für jede Anzahl von Typparametern wiederholen, die Sie in Ihrer Build-Funktion haben.

Vielleicht nicht eine Antwort auf Ihre Frage, aber man konnte die statische Typprüfung verzichten und dynamisch obwohl etwas verwenden:

public class Builder 
{ 
    List<Type> _types = new List<Type>(); 
    List<object> _values = new List<Object>(); 
    public Builder GetOne<T>() 
    { 
     _types.Add(typeof(T)); 
     // Do stuff 
     _values.Add(someObjectYouRetrieved); 
     return this; 
    } 

    public T Build<T1, T>(Func<T1, T2, T> func) => 
     func(_values[0] as T1); 

    public T Build<T1, T2, T>(Func<T1, T2, T> func) => 
     func(_values[0] as T1, _values[1] as T2); 

    public T Build<T1, T2, T3, T>(Func<T1, T2, T3, T> func) => 
     func(_values[0] as T1, _values[1] as T2, _values[2] as T3); 

    // Add more for the amount of type params you want to allow 
} 

Dies gilt nicht Sie unbegrenzte Flexibilität, aber zumindest verhindert, dass es zu einer Explosion von Methoden.

Es gibt Ihnen auch keine statische Typprüfung, aber da Sie SQL machen, haben Sie nicht Typ Sicherheit an erster Stelle.

Es ist jedoch ein Kompromiss.

Verwandte Themen