Retornar resultados do tipo anônimo?


194

Usando o exemplo simples abaixo, qual é a melhor maneira de retornar resultados de várias tabelas usando o Linq para SQL?

Digamos que eu tenha duas tabelas:

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

Eu quero devolver todos os cães com os deles BreedName. Eu deveria ter todos os cães usando algo assim sem problemas:

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

Mas se eu quero cães com raças e tente isso, tenho problemas:

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

Agora percebo que o compilador não me permite retornar um conjunto de tipos anônimos, pois espera Dogs, mas existe uma maneira de retornar isso sem ter que criar um tipo personalizado? Ou tenho que criar minha própria classe DogsWithBreedNamese especificar esse tipo no select? Ou existe outra maneira mais fácil?


Por curiosidade, por que todos os exemplos do Linq são exibidos usando tipos anônimos, se não funcionam. Por exemplo, este exemplo fazforeach (var cust in query) Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
Hot Licks

@ Hot Licks - a tabela Customer nesses exemplos é uma entidade representada por uma classe. O exemplo simplesmente não parece mostrar as definições dessas classes.
Jonathan S.

Também não diz que um swizzle do compilador está substituindo "var" pelo nome da classe.
Hot Licks

Respostas:


213

Eu costumo seguir esse padrão:

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

Isso significa que você tem uma classe extra, mas é rápido e fácil de codificar, facilmente extensível, reutilizável e seguro para o tipo.


Eu gosto dessa abordagem, mas agora não sei como exibir o nome do cachorro. Se eu estiver vinculando o resultado a um DataGrid, posso obter as propriedades do Dog sem defini-las explicitamente na classe DogWithBreed ou preciso criar o getter / setter para cada campo que eu quero exibir?
1111 Jonathan S.

4
O DataGrids não permite que você especifique a propriedade como "Dog.Name"? Eu esqueço agora porque eu odeio-los o suficiente para nunca usá-los ...
teedyay

@JonathanS. ? como u fez isso na coluna template por favor me diga que estou em situação semelhante
rahularyansharma

Ei, eu gosto desse método de encapsular as duas classes em uma. Isso facilita o trabalho. Sem mencionar que a criação de classes simples para serem usadas apenas no contexto atual o torna mais limpo.
atrasa-se

6
Isso realmente não responde à pergunta que o OP tinha: "existe uma maneira de retornar isso sem ter que criar um tipo personalizado"?
tjscience

69

Você pode retornar tipos anônimos, mas isso realmente não é bonito .

Nesse caso, acho que seria muito melhor criar o tipo apropriado. Se ele for usado apenas dentro do tipo que contém o método, torne-o um tipo aninhado.

Pessoalmente, eu gostaria que o C # recebesse "tipos anônimos nomeados" - ou seja, o mesmo comportamento dos tipos anônimos, mas com nomes e declarações de propriedades, mas é isso.

EDIT: Outros estão sugerindo o retorno de cães e, em seguida, acessando o nome da raça através de um caminho de propriedade etc. Essa é uma abordagem perfeitamente razoável, mas o IME leva a situações nas quais você fez uma consulta de uma maneira específica devido aos dados que deseja use - e essa meta-informação é perdida quando você apenas retorna IEnumerable<Dog>- a consulta pode estar esperando que você use (digamos), em Breedvez de Ownerdevido a algumas opções de carregamento etc., mas se você esquecer isso e começar a usar outras propriedades, seu aplicativo poderá funcionar, mas não tão eficientemente quanto você imaginou originalmente. Claro, eu poderia estar falando besteira, ou otimizando demais, etc ...


3
Ei, eu não sou de não querer recursos por medo de serem abusados, mas você pode imaginar os tipos de códigos maliciosos que veríamos se permitissem que tipos anônimos nomeados fossem desmaiados? (shiver)
Dave Markle

19
Podemos ver alguns abusos. Também podemos ver um código muito mais simples, onde queremos apenas uma tupla, basicamente. Não tudo precisa ser um objeto com comportamento complexo. Às vezes "apenas os dados" são a coisa certa. IMO, é claro.
9139 Jon Skeet

1
Obrigado, então sua preferência é criar tipos, mesmo que seja para uma exibição única como essa? Tenho muitos relatórios que dividem os mesmos dados de maneiras diferentes e esperava não ter que criar todos esses tipos diferentes (DogsWithBreeds, DogsWithOwnerNames etc.)
Jonathan S.

1
Eu tentaria não precisar dividi-lo de várias maneiras ou colocar a parte de fatiar no local que precisa dos dados para que você possa usar tipos anônimos - mas além disso, sim. É uma droga, de certa forma, mas essa é a vida que eu tenho medo :(
Jon Skeet

17

Só para adicionar o valor de meus dois centavos :-) Eu aprendi recentemente uma maneira de lidar com objetos anônimos. Ele pode ser usado apenas ao direcionar a estrutura do .NET 4 e somente ao adicionar uma referência ao System.Web.dll, mas é bastante simples:

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

Para poder adicionar uma referência ao System.Web.dll, você deve seguir os conselhos do rushonerok : Verifique se a estrutura de destino do [projeto] é ".NET Framework 4" e não ".NET Framework 4 Client Profile".



8

Não, você não pode retornar tipos anônimos sem passar por alguns truques.

Se você não estava usando C #, o que você procuraria (retornando vários dados sem um tipo concreto) é chamado de Tupla.

Existem muitas implementações de tupla em C #, usando a mostrada aqui , seu código funcionaria assim.

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

E no site de chamada:

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
Isso não funciona. Lança um NotSupportedException : Somente construtores sem parâmetros e initializers são suportados no LINQ to Entities
mshsayem

1
Verdadeiro, a classe Tuple não possui um construtor padrão e não funcionará corretamente com o LINQ to Entities dessa maneira. No entanto, funciona bem com o LINQ to SQL, como na pergunta. Eu não tentei isso, mas pode funcionar ... select Tuple.Create(d, b).
Joshperry

1
Como as Tuplas não são suportadas por alguns provedores de LINQ, você não pode selecionar um tipo anônimo, convertê-lo em IEnumerable e selecionar uma tupla?
TehPers

8

Você poderia fazer algo assim:


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

8

Você deve usar o ToList()método primeiro para obter linhas do banco de dados e, em seguida, selecionar itens como uma classe. Tente o seguinte:

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

Então, o truque é o primeiroToList() . É imediatamente faz a consulta e obtém os dados do banco de dados. O segundo truque é selecionar itens e usar o inicializador de objetos para gerar novos objetos com itens carregados.

Espero que isto ajude.


8

No C # 7, agora você pode usar tuplas! ... o que elimina a necessidade de criar uma classe apenas para retornar o resultado.

Aqui está um código de exemplo:

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

Pode ser necessário instalar o pacote de nuget System.ValueTuple.


4

Agora percebo que o compilador não me permite retornar um conjunto de tipos anônimos, pois espera Dogs, mas existe uma maneira de retornar isso sem ter que criar um tipo personalizado?

Use use object para retornar uma lista de tipos anônimos sem criar um tipo personalizado. Isso funcionará sem o erro do compilador (no .net 4.0). Voltei a lista ao cliente e a analisei no 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;
}

1
Na minha opinião, seria mais correto e legível se o seu método de assinatura ficou assim: públicas IEnumerable <object> GetDogsWithBreedNames ()
pistola-pete

3

Basta selecionar os cães e, em seguida dog.Breed.BreedName, usar , isso deve funcionar bem.

Se você tiver muitos cães, use DataLoadOptions.LoadWith para reduzir o número de chamadas de banco de dados.


2

Você não pode retornar tipos anônimos diretamente, mas pode fazer um loop através do método genérico. O mesmo acontece com a maioria dos métodos de extensão LINQ. Não há mágica lá, enquanto parece que eles retornariam tipos anônimos. Se o parâmetro for anônimo, o resultado também poderá ser anônimo.

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

Abaixo um exemplo baseado no código da pergunta original:

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

0

Bem, se você estiver retornando Dogs, você faria:

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

Se você deseja que o Breed seja carregado com antecedência e não com carregamento lento, basta usar a construção DataLoadOptions apropriada .


0

BreedIdna Dogtabela é obviamente uma chave estrangeira para a linha correspondente na Breedtabela. Se você configurou seu banco de dados corretamente, o LINQ to SQL deve criar automaticamente uma associação entre as duas tabelas. A classe Dog resultante terá uma propriedade Breed e a classe Breed deve ter uma coleção Dogs. Ao configurá-lo dessa maneira, você ainda pode retornar IEnumerable<Dog>, que é um objeto que inclui a propriedade de criação. A única ressalva é que você precisa pré-carregar o objeto de criação junto com os objetos de cachorro na consulta para que eles possam ser acessados ​​após o descarte do contexto de dados e (como outro autor sugeriu) executar um método na coleção que fará com que o objeto consulta a ser executada imediatamente (neste caso, ToArray):

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

}

É então trivial acessar a raça para cada cão:

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

0

Se a idéia principal é fazer com que a instrução SQL select enviada ao servidor de Banco de Dados tenha apenas os campos obrigatórios e nem todos os campos de Entidade, você poderá fazer isso:

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

Tente isso para obter dados dinâmicos. Você pode converter o código da Lista <>

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

Se você tem uma configuração de relacionamento no seu banco de dados com uma restrição de chave estrangeira no BreedId, você não entende isso já?

Mapeamento de Relacionamento DBML

Agora eu posso ligar para:

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

E no código que chama isso:

var album = GetAlbum(1);

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

Portanto, no seu exemplo, você chamaria algo como dog.Breed.BreedName - como eu disse, isso depende do seu banco de dados ser configurado com esses relacionamentos.

Como outros já mencionaram, o DataLoadOptions ajudará a reduzir as chamadas ao banco de dados, se isso for um problema.


0

Isso não responde exatamente à sua pergunta, mas o Google me levou aqui com base nas palavras-chave. É assim que você pode consultar um tipo anônimo em uma lista:

var anon = model.MyType.Select(x => new { x.Item1, x.Item2});
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.