Por lo que entiendo de la documentación de SelectMany, se podría usar para producir una secuencia (aplanada) de una relación de 1 a muchos.
Tengo las siguientes clases
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public string Description { get; set; }
}
Luego trato de usarlos usando la sintaxis de expresión de consulta así
var customers = new Customer[]
{
new Customer() { Id=1, Name ="A"},
new Customer() { Id=2, Name ="B"},
new Customer() { Id=3, Name ="C"}
};
var orders = new Order[]
{
new Order { Id=1, CustomerId=1, Description="Order 1"},
new Order { Id=2, CustomerId=1, Description="Order 2"},
new Order { Id=3, CustomerId=1, Description="Order 3"},
new Order { Id=4, CustomerId=1, Description="Order 4"},
new Order { Id=5, CustomerId=2, Description="Order 5"},
new Order { Id=6, CustomerId=2, Description="Order 6"},
new Order { Id=7, CustomerId=3, Description="Order 7"},
new Order { Id=8, CustomerId=3, Description="Order 8"},
new Order { Id=9, CustomerId=3, Description="Order 9"}
};
var customerOrders = from c in customers
from o in orders
where o.CustomerId == c.Id
select new
{
CustomerId = c.Id
, OrderDescription = o.Description
};
foreach (var item in customerOrders)
Console.WriteLine(item.CustomerId + ": " + item.OrderDescription);
Esto da lo que necesito.
1: Order 1
1: Order 2
1: Order 3
1: Order 4
2: Order 5
2: Order 6
3: Order 7
3: Order 8
3: Order 9
Supongo que esto se traduce en usar el método SelectMany cuando no se usa la sintaxis de la expresión de consulta.
De cualquier manera, estoy tratando de entender el uso de SelectMany. Entonces, incluso si mi consulta anterior no se traduce a SelectMany, dadas las dos clases y los datos simulados, ¿podría alguien proporcionarme una consulta de linq que use SelectMany?
c#
linq
data-structures
linq-to-objects
Jackie Kirby
fuente
fuente
Respuestas:
Aquí está su consulta usando
SelectMany
, modelada exactamente según su ejemplo. ¡Misma salida!var customerOrders2 = customers.SelectMany( c => orders.Where(o => o.CustomerId == c.Id), (c, o) => new { CustomerId = c.Id, OrderDescription = o.Description });
El primer argumento asigna a cada cliente a una colección de pedidos (completamente análogo a la cláusula "dónde" que ya tiene).
El segundo argumento transforma cada par emparejado {(c1, o1), (c1, o2) .. (c3, o9)} en un nuevo tipo, que hice igual que en su ejemplo.
Entonces:
La colección resultante es plana, como cabría esperar en su ejemplo original.
Si omitiera el segundo argumento, terminaría con una colección de todos los pedidos que coinciden con un cliente. Sería solo eso, una colección plana de
Order
objetos.Me cuesta mucho acostumbrarme a usarlo, a veces todavía tengo problemas para entenderlo. :(
fuente
GroupBy
podría ser una mejor opción para este escenario en particular.SelectMany () funciona como Select, pero con esa característica adicional de acoplar una colección seleccionada. Debe usarse siempre que desee una proyección de elementos de subcolecciones, y no le importe el elemento contenedor de la subcolección.
Por ejemplo, digamos que su dominio se ve así:
public class Customer { public int Id { get; set; } public string Name { get; set; } public List<Order> Orders { get; set; } } class Order { public int Id { get; set; } public Customer Customer { get; set; } public string Description { get; set; } }
Para obtener la misma lista que deseaba, su Linq se vería así:
var customerOrders = Customers .SelectMany(c=>c.Orders) .Select(o=> new { CustomerId = o.Customer.Id, OrderDescription = o.Description });
... que producirá el mismo resultado sin necesidad de la recogida plana de Pedidos. La toma SelectMany recogida y itera pedidos de cada cliente a través de que para producir una
IEnumerable<Order>
de unaIEnumerable<Customer>
.fuente
Aunque esta es una pregunta antigua, pensé que mejoraría un poco las excelentes respuestas:
SelectMany devuelve una lista (que puede estar vacía) para cada elemento de la lista de control. Cada elemento de estas listas de resultados se enumeran en la secuencia de salida de las expresiones y, por lo tanto, se concatenan en el resultado. Por lo tanto, una lista 'lista -> b' [] -> concatenar -> b 'lista.
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; using System.Diagnostics; namespace Nop.Plugin.Misc.WebServices.Test { [TestClass] public class TestBase { [TestMethod] public void TestMethod1() { //See result in TestExplorer - test output var a = new int[]{7,8}; var b = new int[] {12,23,343,6464,232,75676,213,1232,544,86,97867,43}; Func<int, int, bool> numberHasDigit = (number , digit) => ( number.ToString().Contains(digit.ToString()) ); Debug.WriteLine("Unfiltered: All elements of 'b' for each element of 'a'"); foreach(var l in a.SelectMany(aa => b)) Debug.WriteLine(l); Debug.WriteLine(string.Empty); Debug.WriteLine("Filtered:" + "All elements of 'b' for each element of 'a' filtered by the 'a' element"); foreach(var l in a.SelectMany(aa => b.Where(bb => numberHasDigit(bb, aa)))) Debug.WriteLine(l); } } }
fuente
Aquí hay otra opción usando SelectMany
var customerOrders = customers.SelectMany( c => orders.Where(o => o.CustomerId == c.Id) .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
Si usa Entity Framework o LINQ to Sql y tiene una asociación (relación) entre las entidades, entonces puede hacerlo:
var customerOrders = customers.SelectMany( c => c.orders .Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
fuente