¿Devolver resultados de tipo anónimo?
Utilizando el sencillo ejemplo siguiente, ¿cuál es la mejor manera de devolver resultados de varias tablas utilizando Linq to SQL?
Digamos que tengo dos tablas:
Dogs: Name, Age, BreedId
Breeds: BreedId, BreedName
Quiero devolver todos los perros con sus BreedName
. Debería hacer que todos los perros usen algo como esto sin 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;
}
Pero si quiero perros con razas y pruebo esto tengo 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;
}
Ahora me doy cuenta de que el compilador no me permite devolver un conjunto de tipos anónimos porque espera perros, pero ¿hay alguna manera de devolver esto sin tener que crear un tipo personalizado? ¿O tengo que crear mi propia clase DogsWithBreedNames
y especificar ese tipo en la selección? ¿O hay otra manera más fácil?
Tiendo a optar por este patrón:
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;
}
Significa que tienes una clase extra, pero es rápida y fácil de codificar, fácilmente extensible, reutilizable y con escritura segura.
Puedes devolver tipos anónimos, pero realmente no es bonito .
En este caso creo que sería mucho mejor crear el tipo apropiado. Si solo se va a utilizar dentro del tipo que contiene el método, conviértalo en un tipo anidado.
Personalmente, me gustaría que C# obtuviera "tipos anónimos con nombre", es decir, el mismo comportamiento que los tipos anónimos, pero con nombres y declaraciones de propiedades, pero eso es todo.
EDITAR: Otros sugieren devolver perros y luego acceder al nombre de la raza a través de una ruta de propiedad, etc. Ese es un enfoque perfectamente razonable, pero IME conduce a situaciones en las que ha realizado una consulta de una manera particular debido a los datos que desea. uso, y esa metainformación se pierde cuando regresa IEnumerable<Dog>
, es posible que la consulta espere que use (digamos) Breed
en lugar Owner
de algunas opciones de carga, etc., pero si lo olvida y comienza a usar otras propiedades, su aplicación puede funcionar, pero no tan eficientemente como había previsto originalmente. Por supuesto, podría estar diciendo tonterías, o optimizando demasiado, etc.
Sólo para añadir mi granito de arena :-) Recientemente aprendí una forma de manejar objetos anónimos. Solo se puede usar cuando se apunta al marco .NET 4 y solo cuando se agrega una referencia a System.Web.dll, pero es bastante 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" };
}
}
Para poder agregar una referencia a System.Web.dll, deberá seguir los consejos de rushonerok : asegúrese de que el marco de destino de su [proyecto] sea ".NET Framework 4", no ".NET Framework 4 Client Profile".
¡En C# 7 ahora puedes usar tuplas!... lo que elimina la necesidad de crear una clase solo para devolver el resultado.
Aquí hay un código de muestra:
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();
}
Sin embargo, es posible que necesites instalar el paquete nuget System.ValueTuple.