Ergebnisse von anonymem Typ zurückgeben?

194

Wie können Sie anhand des folgenden einfachen Beispiels die Ergebnisse mehrerer Tabellen mit Linq to SQL am besten zurückgeben?

Angenommen, ich habe zwei Tabellen:

Dogs:   Name, Age, BreedId
Breeds: BreedId, BreedName

Ich möchte alle Hunde mit ihren zurückgeben BreedName. Ich sollte alle Hunde dazu bringen, so etwas ohne Probleme zu benutzen:

public IQueryable<Dog> GetDogs()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select d;
    return result;
}

Aber wenn ich Hunde mit Rassen will und dies versuche, habe ich Probleme:

public IQueryable<Dog> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result;
}

Jetzt ist mir klar, dass der Compiler es mir nicht erlaubt, eine Reihe anonymer Typen zurückzugeben, da er Hunde erwartet. Gibt es jedoch eine Möglichkeit, dies zurückzugeben, ohne einen benutzerdefinierten Typ erstellen zu müssen? Oder muss ich eine eigene Klasse für DogsWithBreedNamesdiesen Typ erstellen und diesen in der Auswahl angeben? Oder gibt es einen anderen einfacheren Weg?

Jonathan S.
quelle
Warum zeigen alle Linq-Beispiele aus Neugier anonyme Typen, wenn sie nicht funktionieren? ZB tut dieses Beispielforeach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Hot Licks
@Hot Licks - Die Kundentabelle in diesen Beispielen ist eine Entität, die durch eine Klasse dargestellt wird. Das Beispiel scheint nur nicht die Definitionen dieser Klassen zu zeigen.
Jonathan S.
Es sagt Ihnen auch nicht, dass ein Compiler-Swizzle "var" durch den Klassennamen ersetzt.
Hot Licks

Antworten:

213

Ich tendiere dazu, dieses Muster zu wählen:

public class DogWithBreed
{
    public Dog Dog { get; set; }
    public string BreedName  { get; set; }
}

public IQueryable<DogWithBreed> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new DogWithBreed()
                        {
                            Dog = d,
                            BreedName = b.BreedName
                        };
    return result;
}

Es bedeutet, dass Sie eine zusätzliche Klasse haben, aber es ist schnell und einfach zu codieren, leicht erweiterbar, wiederverwendbar und typsicher.

teedyay
quelle
Ich mag diesen Ansatz, aber jetzt bin ich mir nicht sicher, wie ich den Namen des Hundes anzeigen soll. Wenn ich das Ergebnis an ein DataGrid binde, kann ich die Eigenschaften von Dog abrufen, ohne sie explizit in der DogWithBreed-Klasse zu definieren, oder muss ich den Getter / Setter für jedes Feld erstellen, das ich anzeigen möchte?
Jonathan S.
4
Können Sie in DataGrids die Eigenschaft nicht als "Dog.Name" angeben? Ich vergesse jetzt, warum ich sie genug hasse, um sie nie zu benutzen ...
teedyay
@ JonathanS. Wie hast du das in der Vorlagenspalte gemacht? Bitte sag mir, dass ich in einer ähnlichen Situation bin
rahularyansharma
Hey, ich mag diese Methode, die beiden Klassen in einer zusammenzufassen. Es macht es einfach, damit zu arbeiten. Ganz zu schweigen davon, dass das Erstellen einfacher Klassen, die nur im aktuellen Kontext verwendet werden sollen, die Übersichtlichkeit erhöht.
Verweilen Sie am
6
Dies beantwortet wirklich nicht die Frage, die das OP hatte: "Gibt es eine Möglichkeit, dies zurückzugeben, ohne einen benutzerdefinierten Typ erstellen zu müssen?"
tjscience
69

Sie können anonyme Typen zurückgeben, aber es ist wirklich nicht schön .

In diesem Fall wäre es meiner Meinung nach weitaus besser, den entsprechenden Typ zu erstellen. Wenn es nur innerhalb des Typs verwendet werden soll, der die Methode enthält, machen Sie es zu einem verschachtelten Typ.

Persönlich möchte ich, dass C # "benannte anonyme Typen" erhält - dh dasselbe Verhalten wie anonyme Typen, jedoch mit Namen und Eigenschaftsdeklarationen, aber das war's.

BEARBEITEN: Andere schlagen vor, Hunde zurückzugeben und dann über einen Eigenschaftspfad usw. auf den Rassennamen zuzugreifen. Dies ist ein durchaus vernünftiger Ansatz, aber IME führt zu Situationen, in denen Sie aufgrund der gewünschten Daten eine bestimmte Abfrage durchgeführt haben Verwenden Sie - und diese Metainformationen gehen verloren, wenn Sie gerade zurückkehren IEnumerable<Dog>- die Abfrage erwartet möglicherweise, dass Sie (sagen wir) Breedanstelle Ownereiniger Ladeoptionen usw. verwenden. Wenn Sie dies jedoch vergessen und andere Eigenschaften verwenden, funktioniert Ihre App möglicherweise jedoch nicht so effizient, wie Sie es sich ursprünglich vorgestellt hatten. Natürlich könnte ich Müll reden oder überoptimieren, etc ...

Jon Skeet
quelle
3
Hey, ich bin nicht derjenige, der aus Angst, dass sie nicht missbraucht werden, keine Funktionen haben möchte, aber können Sie sich vorstellen, welche Art von Crufty-Code wir sehen würden, wenn sie zulassen würden, dass benannte anonyme Typen ausgegeben werden? (Schauer)
Dave Markle
19
Wir könnten Missbrauch sehen. Möglicherweise sehen wir auch einen viel einfacheren Code, bei dem wir im Grunde nur ein Tupel wollen. Nicht alles muss ein Objekt mit komplexem Verhalten sein. Manchmal ist "nur die Daten" das Richtige. IMO natürlich.
Jon Skeet
1
Vielen Dank. Sie möchten also lieber Typen erstellen, auch wenn es sich um eine einmalige Ansicht wie diese handelt. Ich habe viele Berichte, die dieselben Daten auf unterschiedliche Weise aufteilen, und hatte gehofft, nicht alle diese unterschiedlichen Typen (DogsWithBreeds, DogsWithOwnerNames usw.) erstellen zu müssen
Jonathan S.
1
Ich würde versuchen , nicht zu Notwendigkeit , es zu schneiden in ganz so viele Möglichkeiten, oder das Aufschneiden Teil an der Stelle setzen , die die Daten benötigt , so dass Sie können anonyme Typen verwenden - aber darüber hinaus, ja. Es ist in gewisser Weise scheiße, aber so ist das Leben, fürchte ich :(
Jon Skeet
17

Nur um meinen Wert von zwei Cent zu addieren :-) Ich habe kürzlich gelernt, wie man mit anonymen Objekten umgeht. Es kann nur verwendet werden, wenn auf das .NET 4-Framework abgezielt wird, und zwar nur, wenn ein Verweis auf System.Web.dll hinzugefügt wird, aber dann ist es ganz einfach:

...
using System.Web.Routing;
...

class Program
{
    static void Main(string[] args)
    {

        object anonymous = CallMethodThatReturnsObjectOfAnonymousType();
        //WHAT DO I DO WITH THIS?
        //I know! I'll use a RouteValueDictionary from System.Web.dll
        RouteValueDictionary rvd = new RouteValueDictionary(anonymous);
        Console.WriteLine("Hello, my name is {0} and I am a {1}", rvd["Name"], rvd["Occupation"]);
    }

    private static object CallMethodThatReturnsObjectOfAnonymousType()
    {
        return new { Id = 1, Name = "Peter Perhac", Occupation = "Software Developer" };
    }
}

Um einen Verweis auf System.Web.dll hinzufügen zu können, müssen Sie den Ratschlägen von Rushonerok folgen: Stellen Sie sicher , dass das Zielframework Ihres [Projekts] ".NET Framework 4" und nicht ".NET Framework 4-Clientprofil" ist.

Peter Perháč
quelle
2
ASP.NET Mvc School;)
T-Moty
8

Nein, Sie können keine anonymen Typen zurückgeben, ohne einige Tricks durchzugehen.

Wenn Sie C # nicht verwenden, wird das, wonach Sie suchen (Rückgabe mehrerer Daten ohne konkreten Typ), als Tupel bezeichnet.

Es gibt viele C # -Tupel-Implementierungen. Wenn Sie die hier gezeigte verwenden , würde Ihr Code so funktionieren.

public IEnumerable<Tuple<Dog,Breed>> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new Tuple<Dog,Breed>(d, b);

    return result;
}

Und auf der anrufenden Seite:

void main() {
    IEnumerable<Tuple<Dog,Breed>> dogs = GetDogsWithBreedNames();
    foreach(Tuple<Dog,Breed> tdog in dogs)
    {
        Console.WriteLine("Dog {0} {1}", tdog.param1.Name, tdog.param2.BreedName);
    }
}
Joshperry
quelle
7
Das funktioniert nicht. Wirft eine NotSupportedException aus : In LINQ to Entities werden nur parameterlose Konstruktoren und Initialisierer unterstützt
mshsayem
1
Die Tuple-Klasse verfügt zwar nicht über einen Standardkonstruktor und funktioniert auf diese Weise nicht ordnungsgemäß mit LINQ to Entities. Es funktioniert jedoch gut mit LINQ to SQL wie in der Frage. Ich habe das nicht ausprobiert, aber es könnte funktionieren ... select Tuple.Create(d, b).
Joshperry
1
Da Tupel von einigen LINQ-Anbietern nicht unterstützt werden, können Sie nicht einen anonymen Typ auswählen, ihn in IEnumerable konvertieren und dann ein Tupel daraus auswählen?
TehPers
8

Sie könnten so etwas tun:


public System.Collections.IEnumerable GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result.ToList();
}
tjscience
quelle
8

Sie müssen zuerst die ToList()Methode verwenden, um Zeilen aus der Datenbank zu entnehmen und dann Elemente als Klasse auszuwählen. Versuche dies:

public partial class Dog {
    public string BreedName  { get; set; }}

List<Dog> GetDogsWithBreedNames(){
    var db = new DogDataContext(ConnectString);
    var result = (from d in db.Dogs
                  join b in db.Breeds on d.BreedId equals b.BreedId
                  select new
                  {
                      Name = d.Name,
                      BreedName = b.BreedName
                  }).ToList()
                    .Select(x=> 
                          new Dog{
                              Name = x.Name,
                              BreedName = x.BreedName,
                          }).ToList();
return result;}

Der Trick ist also der ersteToList() . Es wird sofort die Abfrage durchgeführt und die Daten aus der Datenbank abgerufen. Der zweite Trick besteht darin, Elemente auszuwählen und mithilfe des Objektinitialisierers neue Objekte mit geladenen Elementen zu generieren.

Hoffe das hilft.

Hakan KOSE
quelle
8

In C # 7 können Sie jetzt Tupel verwenden! ... wodurch die Notwendigkeit entfällt, eine Klasse zu erstellen, um das Ergebnis zurückzugeben.

Hier ist ein Beispielcode:

public List<(string Name, string BreedName)> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
             join b in db.Breeds on d.BreedId equals b.BreedId
             select new
             {
                Name = d.Name,
                BreedName = b.BreedName
             }.ToList();

    return result.Select(r => (r.Name, r.BreedName)).ToList();
}

Möglicherweise müssen Sie jedoch das System.ValueTuple-Nuget-Paket installieren.

Rosdi Kasim
quelle
4

Jetzt ist mir klar, dass der Compiler es mir nicht erlaubt, eine Reihe anonymer Typen zurückzugeben, da er Hunde erwartet. Gibt es jedoch eine Möglichkeit, dies zurückzugeben, ohne einen benutzerdefinierten Typ erstellen zu müssen?

Verwenden Sie use object , um eine Liste anonymer Typen zurückzugeben, ohne einen benutzerdefinierten Typ zu erstellen. Dies funktioniert ohne den Compilerfehler (in .net 4.0). Ich habe die Liste an den Client zurückgegeben und sie dann in JavaScript analysiert:

public object GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result;
}
Sergey
quelle
1
Meiner Meinung nach wäre es richtiger und lesbar sein , wenn Ihre Methode Unterschrift sah wie folgt aus: public IEnumerable <object> GetDogsWithBreedNames ()
Pistolen pete
3

Wählen Sie einfach Hunde aus und verwenden Sie sie dog.Breed.BreedName. Dies sollte gut funktionieren.

Wenn Sie viele Hunde haben, verwenden Sie DataLoadOptions.LoadWith, um die Anzahl der Datenbankaufrufe zu verringern.

Andrey Shchekin
quelle
2

Sie können anonyme Typen nicht direkt zurückgeben, sondern sie durch Ihre generische Methode schleifen. Dies gilt auch für die meisten LINQ-Erweiterungsmethoden. Es gibt dort keine Magie, obwohl es so aussieht, als würden sie anonyme Typen zurückgeben. Wenn der Parameter anonym ist, kann das Ergebnis auch anonym sein.

var result = Repeat(new { Name = "Foo Bar", Age = 100 }, 10);

private static IEnumerable<TResult> Repeat<TResult>(TResult element, int count)
{
    for(int i=0; i<count; i++)
    {
        yield return element;
    }
}

Unten ein Beispiel basierend auf Code aus der ursprünglichen Frage:

var result = GetDogsWithBreedNames((Name, BreedName) => new {Name, BreedName });


public static IQueryable<TResult> GetDogsWithBreedNames<TResult>(Func<object, object, TResult> creator)
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                    join b in db.Breeds on d.BreedId equals b.BreedId
                    select creator(d.Name, b.BreedName);
    return result;
}
George Mamaladze
quelle
0

Wenn Sie Hunde zurückgeben, tun Sie Folgendes:

public IQueryable<Dog> GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    return from d in db.Dogs
           join b in db.Breeds on d.BreedId equals b.BreedId
           select d;
}

Wenn Sie möchten, dass die Rasse eifrig und nicht faul geladen wird, verwenden Sie einfach das entsprechende DataLoadOptions- Konstrukt.

Dave Markle
quelle
0

BreedIdin der DogTabelle ist offensichtlich ein Fremdschlüssel für die entsprechende Zeile in der BreedTabelle. Wenn Sie Ihre Datenbank ordnungsgemäß eingerichtet haben, sollte LINQ to SQL automatisch eine Zuordnung zwischen den beiden Tabellen erstellen. Die resultierende Hundeklasse hat eine Breed-Eigenschaft, und die Breed-Klasse sollte eine Dogs-Sammlung haben. Wenn Sie es auf diese Weise einrichten, können Sie trotzdem zurückkehren. Dies IEnumerable<Dog>ist ein Objekt, das die Rasseeigenschaft enthält. Die einzige Einschränkung besteht darin, dass Sie das Rassenobjekt zusammen mit den Hundeobjekten in der Abfrage vorab laden müssen, damit auf sie zugegriffen werden kann, nachdem der Datenkontext entsorgt wurde, und (wie in einem anderen Poster vorgeschlagen) eine Methode für die Sammlung ausführen müssen, die das verursacht Abfrage muss sofort ausgeführt werden (in diesem Fall ToArray):

public IEnumerable<Dog> GetDogs()
{
    using (var db = new DogDataContext(ConnectString))
    {
        db.LoadOptions.LoadWith<Dog>(i => i.Breed);
        return db.Dogs.ToArray();
    }

}

Es ist dann trivial, für jeden Hund auf die Rasse zuzugreifen:

foreach (var dog in GetDogs())
{
    Console.WriteLine("Dog's Name: {0}", dog.Name);
    Console.WriteLine("Dog's Breed: {0}", dog.Breed.Name);        
}
kad81
quelle
0

Wenn die Hauptidee darin besteht, dass die an den Datenbankserver gesendete SQL-Select-Anweisung nur die erforderlichen Felder und nicht alle Entitätsfelder enthält, können Sie Folgendes tun:

public class Class1
{
    public IList<Car> getCarsByProjectionOnSmallNumberOfProperties()
    {

        try
        {
            //Get the SQL Context:
            CompanyPossessionsDAL.POCOContext.CompanyPossessionsContext dbContext 
                = new CompanyPossessionsDAL.POCOContext.CompanyPossessionsContext();

            //Specify the Context of your main entity e.g. Car:
            var oDBQuery = dbContext.Set<Car>();

            //Project on some of its fields, so the created select statment that is
            // sent to the database server, will have only the required fields By making a new anonymouse type
            var queryProjectedOnSmallSetOfProperties 
                = from x in oDBQuery
                    select new
                    {
                        x.carNo,
                        x.eName,
                        x.aName
                    };

            //Convert the anonymouse type back to the main entity e.g. Car
            var queryConvertAnonymousToOriginal 
                = from x in queryProjectedOnSmallSetOfProperties
                    select new Car
                    {
                        carNo = x.carNo,
                        eName = x.eName,
                        aName = x.aName
                    };

            //return the IList<Car> that is wanted
            var lst = queryConvertAnonymousToOriginal.ToList();
            return lst;

        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.ToString());
            throw;
        }
    }
}
Leser Man San
quelle
0

Versuchen Sie dies, um dynamische Daten zu erhalten. Sie können Code für List <> konvertieren

public object GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
                 join b in db.Breeds on d.BreedId equals b.BreedId
                 select new
                        {
                            Name = d.Name,
                            BreedName = b.BreedName
                        };
    return result.FirstOrDefault();
}

dynamic dogInfo=GetDogsWithBreedNames();
var name = dogInfo.GetType().GetProperty("Name").GetValue(dogInfo, null);
var breedName = dogInfo.GetType().GetProperty("BreedName").GetValue(dogInfo, null);
Yargicx
quelle
0

Wenn Sie in Ihrer Datenbank ein Beziehungssetup mit einer falschen Schlüsselbeschränkung für BreedId haben, bekommen Sie das nicht schon?

DBML-Beziehungszuordnung

So kann ich jetzt anrufen:

internal Album GetAlbum(int albumId)
{
    return Albums.SingleOrDefault(a => a.AlbumID == albumId);
}

Und in dem Code, der das nennt:

var album = GetAlbum(1);

foreach (Photo photo in album.Photos)
{
    [...]
}

In Ihrem Fall würden Sie also so etwas wie dog.Breed.BreedName aufrufen - wie gesagt, dies hängt davon ab, dass Ihre Datenbank mit diesen Beziehungen eingerichtet ist.

Wie bereits erwähnt, helfen die DataLoadOptions dabei, die Datenbankaufrufe zu reduzieren, wenn dies ein Problem darstellt.

Zhaph - Ben Duguid
quelle
0

Dies beantwortet Ihre Frage nicht genau, aber Google hat mich anhand der Keywords hierher geführt. So können Sie einen anonymen Typ aus einer Liste abfragen:

var anon = model.MyType.Select(x => new { x.Item1, x.Item2});
Daniel
quelle