web-dev-qa-db-fra.com

Renvoyer des résultats de type anonyme?

À l'aide de l'exemple simple ci-dessous, quel est le meilleur moyen de renvoyer les résultats de plusieurs tables à l'aide de Linq to SQL?

Disons que j'ai deux tables:

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

Je veux rendre tous les chiens avec leur BreedName. Je devrais obtenir tous les chiens utilisant quelque chose comme ceci sans aucun problème:

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;
}

Mais si je veux des chiens avec des races et que j'essaye ça, j'ai des problèmes:

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;
}

Maintenant, je me rends compte que le compilateur ne me laissera pas renvoyer un ensemble de types anonymes car il attend Chiens, mais existe-t-il un moyen de le renvoyer sans avoir à créer un type personnalisé? Ou dois-je créer ma propre classe pour DogsWithBreedNames et spécifier ce type dans la sélection? Ou y a-t-il un autre moyen plus facile?

182
Jonathan S.

J'ai tendance à aller pour ce modèle:

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;
}

Cela signifie que vous avez une classe supplémentaire, mais elle est rapide et facile à coder, facilement extensible, réutilisable et sécurisée.

198
teedyay

Vous pouvez renvoyer des types anonymes, mais ce n'est vraiment pas joli .

Dans ce cas, je pense qu'il serait bien préférable de créer le type approprié. S'il ne doit être utilisé que dans le type contenant la méthode, faites-en un type imbriqué.

Personnellement, je voudrais que C # obtienne "les types nommés anonymes" - c’est-à-dire le même comportement que les types anonymes, mais avec des noms et des déclarations de propriété, mais c’est tout.

EDIT: D'autres suggèrent de renvoyer des chiens, puis d'accéder au nom de la race via un chemin de propriété, etc. C'est une approche parfaitement raisonnable, mais IME conduit à des situations dans lesquelles vous avez effectué une requête d'une manière particulière en raison des données que vous souhaitez utiliser. use - et que les méta-informations sont perdues lorsque vous retournez juste IEnumerable<Dog> - la requête peut attendre que vous utilisiez (par exemple) Breed plutôt que Owner en raison de certaines options de chargement, etc., mais si vous oubliez cela et commencez à utiliser d'autres propriétés, votre application fonctionnera peut-être mais de manière moins efficace que celle que vous aviez initialement envisagée. Bien sûr, je pourrais parler de bêtises, de sur-optimiser, etc.

67
Jon Skeet

Juste pour ajouter ma valeur de deux centimes :-) J'ai récemment appris un moyen de gérer des objets anonymes. Il ne peut être utilisé que pour cibler le framework .NET 4 et cela uniquement lors de l'ajout d'une référence à System.Web.dll, mais c'est très simple:

...
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" };
    }
}

Afin de pouvoir ajouter une référence à System.Web.dll, vous devrez suivre conseil de rushonerok : Assurez-vous que la structure cible de votre [projet] est ".NET Framework 4" et non ".NET Framework 4 Client Profile ".

16
Peter Perháč

Vous pouvez faire quelque chose comme ça:


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();
}
7
tjscience

Vous devez d'abord utiliser la méthode ToList() pour prendre des lignes dans la base de données, puis sélectionner des éléments en tant que classe. Essaye ça:

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;}

Donc, le truc est premier ToList(). Il est immédiatement fait la requête et obtient les données de la base de données. La deuxième astuce consiste à sélectionner des éléments et utiliser un initialiseur d'objet pour générer de nouveaux objets avec des éléments chargés.

J'espère que cela t'aides.

7
Hakan KOSE

Non, vous ne pouvez pas renvoyer de types anonymes sans passer par une supercherie.

Si vous n'utilisiez pas C #, ce que vous rechercheriez (renvoyer plusieurs données sans un type concret) est appelé un tuple.

Il y a beaucoup d'implémentations de C # Tuple, en utilisant celui montré ici , votre code fonctionnerait comme ceci.

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;
}

Et sur le site d’appel:

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);
    }
}
7
joshperry

En C # 7, vous pouvez maintenant utiliser des n-uplets! ... ce qui vous évite de créer une classe uniquement pour renvoyer le résultat.

Voici un exemple de code:

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();
}

Cependant, vous devrez peut-être installer le package de nuget System.ValueTuple.

5
Rosdi Kasim

Maintenant, je me rends compte que le compilateur ne me laissera pas retourner un ensemble de types anonymes puisqu'il attend Chiens, mais existe-t-il un moyen de le retourner sans avoir à créer un type personnalisé?

Utilisez use object pour renvoyer une liste de types anonymes sans créer de type personnalisé. Cela fonctionnera sans l'erreur du compilateur (dans .net 4.0). J'ai renvoyé la liste au client, puis l'ai analysée en JavaScript:

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;
}
4
Sergey

Il suffit de sélectionner les chiens, puis d'utiliser dog.Breed.BreedName, cela devrait fonctionner correctement.

Si vous avez beaucoup de chiens, utilisez DataLoadOptions.LoadWith pour réduire le nombre d'appels à la base de données.

3
Andrey Shchekin

Vous ne pouvez pas renvoyer directement de types anonymes, mais vous pouvez les passer en boucle via votre méthode générique. Il en va de même pour la plupart des méthodes d'extension LINQ. Il n'y a pas de magie là-dedans, alors qu'il semble qu'ils renverraient des types anonymes. Si le paramètre est anonyme, le résultat peut également être anonyme.

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;
    }
}

Ci-dessous un exemple basé sur le code de la question initiale:

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;
}
2
George Mamaladze

Si vous avez une configuration de relation dans votre base de données avec une contrainte de clé antérieure sur BreedId, ne l'obtenez-vous pas déjà?

DBML relationship mapping

Donc je peux maintenant appeler:

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

Et dans le code qui appelle ça:

var album = GetAlbum(1);

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

Donc, dans votre cas, vous appelez quelque chose comme dog.Breed.BreedName - comme je l'ai dit, cela dépend de la base de données configurée avec ces relations.

Comme d'autres l'ont mentionné, DataLoadOptions contribuera à réduire les appels à la base de données en cas de problème.

0
Zhaph - Ben Duguid

Si l’idée principale est de faire en sorte que l’instruction de sélection SQL envoyée au serveur de base de données ne contienne que les champs obligatoires, et non tous les champs d’entité, vous pouvez le faire:

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;
        }
    }
}
0
Reader Man San

Essayez ceci pour obtenir des données dynamiques. Vous pouvez convertir le code de la liste <>

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);
0
Yargicx

BreedId dans la table Dog est évidemment une clé étrangère de la ligne correspondante dans la table Breed. Si votre base de données est correctement configurée, LINQ to SQL devrait automatiquement créer une association entre les deux tables. La classe de chiens résultante aura une propriété de race et la classe de race devrait avoir une collection de chiens. En le configurant de cette manière, vous pouvez toujours renvoyer IEnumerable<Dog>, qui est un objet qui inclut la propriété de race. Le seul inconvénient est que vous devez précharger l'objet race avec les objets chien de la requête pour pouvoir y accéder après la suppression du contexte de données, et (comme l'a suggéré une autre affiche) exécuter une méthode sur la collection qui entraînera la requête à exécuter immédiatement (ToArray dans ce cas):

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

}

Il est alors trivial d'accéder à la race pour chaque chien:

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

Eh bien, si vous retournez des chiens, vous feriez:

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;
}

Si vous voulez que Breed soit chargé avec impatience et non paresseux, utilisez simplement la construction appropriée DataLoadOptions .

0
Dave Markle