name - summary documentation c#
Devolver resultados de tipo anĂ³nimos? (14)
Usando el ejemplo simple a continuación, ¿cuál es la mejor manera de devolver resultados de múltiples tablas usando Linq a SQL?
Digamos que tengo dos tablas:
Dogs: Name, Age, BreedId
Breeds: BreedId, BreedName
Quiero devolver todos los perros con su BreedName
. Debería obtener todos los perros usando algo así 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 permitirá devolver un conjunto de tipos anónimos ya que está esperando Perros, pero ¿hay alguna forma de devolver esto sin tener que crear un tipo personalizado? ¿O tengo que crear mi propia clase para DogsWithBreedNames
y especificar ese tipo en la selección? ¿O hay otra manera más fácil?
¡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 System.ValueTuple nuget.
Bueno, si regresas Perros, harías:
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 desea que la Raza DataLoadOptions cargada y no cargada de pereza, solo use la construcción apropiada DataLoadOptions .
No puede devolver tipos anónimos directamente, pero puede pasarlos por su método genérico. Lo mismo ocurre con la mayoría de los métodos de extensión LINQ. No hay magia allí, mientras parece que devolverían tipos anónimos. Si el parámetro es anónimo, el resultado también puede 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;
}
}
Debajo de un ejemplo basado en el código de la pregunta 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;
}
No, no puedes devolver tipos anónimos sin pasar por algún truco.
Si no usaba C #, lo que estaría buscando (devolver datos múltiples sin un tipo concreto) se llama Tuple.
Hay muchas implementaciones de tupla de C #, usando la que se muestra aquí , su código funcionaría así.
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;
}
Y en el sitio de llamadas:
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);
}
}
Podrías hacer algo como esto:
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();
}
Puede 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 desde el tipo que contiene el método, conviértalo en un tipo anidado.
Personalmente, me gustaría que C # obtenga "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 perros que regresan, y luego acceden al nombre de la raza a través de una ruta de propiedad, etc. Ese es un enfoque perfectamente razonable, pero IME lleva a situaciones en las que ha hecho una consulta de una manera particular debido a los datos que desea use - y esa metainformación se pierde cuando acaba de regresar IEnumerable<Dog>
- la consulta puede esperar que use (digamos) Breed
lugar de Owner
debido a algunas opciones de carga, etc., pero si olvida eso 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 hablando de basura, o sobre-optimizar, etc.
Si la idea principal es hacer que la declaración de selección de SQL enviada al servidor de la base de datos solo tenga los campos requeridos, y no todos los campos de la entidad, entonces puede hacer esto:
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;
}
}
}
Si tiene una configuración de relación en su base de datos con una restricción de clave foriegn en BreedId, ¿no lo consigue ya?
Mapeo de relaciones DBML http://www.doodle.co.uk/userfiles/image/relationship.png
Entonces ahora puedo llamar:
internal Album GetAlbum(int albumId)
{
return Albums.SingleOrDefault(a => a.AlbumID == albumId);
}
Y en el código que llama eso:
var album = GetAlbum(1);
foreach (Photo photo in album.Photos)
{
[...]
}
Entonces, en su caso, estaría llamando algo así como dog.Breed.BreedName: como dije, esto depende de que su base de datos esté configurada con estas relaciones.
Como otros han mencionado, DataLoadOptions ayudará a reducir las llamadas a la base de datos si eso es un problema.
Simplemente seleccione perros, luego use dog.Breed.BreedName
, esto debería funcionar bien.
Si tiene muchos perros, use DataLoadOptions.LoadWith para reducir el número de llamadas a bases de datos.
Solo para agregar el valor de mis dos centavos :-) Recientemente aprendí una forma de manejar objetos anónimos. Solo se puede usar al orientar el framework .NET 4 y solo cuando se agrega una referencia a System.Web.dll, pero luego 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 [del proyecto] sea ".NET Framework 4" y no ".NET Framework 4 Client Profile".
Tiendo a ir 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 tiene una clase extra, pero es rápido y fácil de codificar, fácilmente extensible, reutilizable y seguro.
BreedId
in the Dog
table es obviamente una clave foránea para la fila correspondiente en la tabla de Breed
. Si tiene su base de datos configurada correctamente, LINQ to SQL debería crear automáticamente una asociación entre las dos tablas. La clase de perro resultante tendrá una propiedad de raza, y la clase de raza debería tener una colección de perros. Al configurarlo de esta manera, todavía puede devolver IEnumerable<Dog>
, que es un objeto que incluye la propiedad de raza. La única advertencia es que debe precargar el objeto de raza junto con los objetos de perro en la consulta para que se pueda acceder después de que se haya eliminado el contexto de datos y (como otro cartel ha sugerido) ejecutar un método en la colección que causará el consulta que se realizará de inmediato (ToArray en este caso):
public IEnumerable<Dog> GetDogs()
{
using (var db = new DogDataContext(ConnectString))
{
db.LoadOptions.LoadWith<Dog>(i => i.Breed);
return db.Dogs.ToArray();
}
}
Entonces es trivial acceder a la raza de cada perro:
foreach (var dog in GetDogs())
{
Console.WriteLine("Dog''s Name: {0}", dog.Name);
Console.WriteLine("Dog''s Breed: {0}", dog.Breed.Name);
}
ToList()
debe usar el método ToList()
para tomar las filas de la base de datos y luego seleccionar elementos como una clase. Prueba esto:
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;}
Entonces, el truco es primero ToList()
. Inmediatamente realiza la consulta y obtiene los datos de la base de datos. El segundo truco es Seleccionar elementos y usar el inicializador de objetos para generar nuevos objetos con los elementos cargados.
Espero que esto ayude.
Ahora me doy cuenta de que el compilador no me permitirá devolver un conjunto de tipos anónimos ya que está esperando Perros, pero ¿hay alguna forma de devolver esto sin tener que crear un tipo personalizado?
Use use object para devolver una lista de tipos anónimos sin crear un tipo personalizado. Esto funcionará sin el error del compilador (en .net 4.0). Devolví la lista al cliente y luego la analicé 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;
}