c# - ¿LINQ usa DataRelations para optimizar las uniones?
join dataset (3)
No puedo encontrar la respuesta a esto en ninguna parte, y antes de comenzar a manipular el código generado con Reflector, pensé que valdría la pena preguntar:
Supongamos que tengo la siguiente consulta LINQ ejecutada contra DataTables en un DataSet:
var list =
from pr in parentTable.AsEnumerable()
join cr in childTable.AsEnumerable() on cr.Field<int>("ParentID") equals pr.Field<int>("ID")
where pr.Field<string>("Value") == "foo"
select cr;
Si hay una DataRelation entre la tabla primaria y la tabla secundaria que usa los campos clave que se muestran, ¿LINQ lo usará? Es decir, ¿encontrará las filas en la tabla principal para las cuales Value es "foo" y luego llamará a GetChildRows
para proyectar las filas secundarias?
¿O es algo que debo especificar explícitamente? (Y si es así, ¿cómo hago esto?)
Esta es una explicación: http://msdn.microsoft.com/en-us/library/bb386969.aspx
Pero en su ejemplo anterior, usted está contando a linq sobre la relación padre-hijo con la sintaxis "JOIN". Yo recomendaría que, si es posible, creara un conjunto de datos fuertemente tipado, y todas las relaciones y uniones se manejarían por usted.
Excavar en Reflector no pareció dar ninguna indicación de que LINQ use DataRelations, pero ese código es tremendamente difícil de leer. Así que escribí una pequeña prueba de rendimiento, y a menos que haya algo tonto acerca de la prueba que me perdí, los resultados son bastante definitivos: no, LINQ no usa DataRelations y GetChildRows () para proyectar filas unidas. Si desea iterar sobre filas secundarias, debe formular la consulta LINQ para llamar a GetChildRows () explícitamente. Y ninguno de los enfoques está tan cerca como el código de escritura que itera sobre la matriz devuelta por GetChildRows ().
Es desafortunado, porque la diferencia de rendimiento en los grandes conjuntos de datos es lo suficientemente significativa como para reemplazar el LINQ con código implementado explícitamente, lo que generalmente no es cierto para LINQ.
Mi código de prueba está abajo. El tiempo de la iteración LINQ utilizando la unión sigue siendo el mismo (alrededor de 580-590 ms en mi máquina) independientemente de si la DataRelation se crea antes o después de ella. La iteración LINQ que utiliza GetChildRows () tarda alrededor de 280 ms,
La iteración directamente sobre GetChildRows () tarda menos de un milisegundo. Eso es bastante sorprendente para mí, lo suficiente como para asumir que tenía un error en el código cuando realicé la prueba por primera vez. (Es por eso que estoy escribiendo el recuento cada vez, para asegurarme de que el compilador no haya desactivado los bucles).
class Program
{
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
DataSet ds = new DataSet();
DataTable t1 = new DataTable();
t1.Columns.Add(new DataColumn
{
ColumnName = "ID",
DataType = typeof (int),
AutoIncrement = true
});
t1.PrimaryKey = new [] { t1.Columns["ID"]};
ds.Tables.Add(t1);
DataTable t2 = new DataTable();
t2.Columns.Add(new DataColumn
{
ColumnName = "ID",
DataType = typeof(int),
AutoIncrement = true
});
t2.Columns.Add("ParentID", typeof(int));
t2.PrimaryKey = new[] { t2.Columns["ID"] };
ds.Tables.Add(t2);
sw.Reset();
sw.Start();
PopulateTables(t1, t2);
sw.Stop();
Console.WriteLine("Populating tables took {0} ms.", sw.ElapsedMilliseconds);
Console.WriteLine();
var list1 = from r1 in t1.AsEnumerable()
join r2 in t2.AsEnumerable()
on r1.Field<int>("ID") equals r2.Field<int>("ParentID")
where r1.Field<int>("ID") == 1
select r2;
sw.Reset();
sw.Start();
int count = 0;
foreach (DataRow r in list1)
{
count += r.Field<int>("ID");
}
sw.Stop();
Console.WriteLine("count = {0}.", count);
Console.WriteLine("Completed LINQ iteration in {0} ms.", sw.ElapsedMilliseconds);
Console.WriteLine();
sw.Reset();
sw.Start();
ds.Relations.Add(new DataRelation("FK_t2_t1", t1.Columns["ID"], t2.Columns["ParentID"]));
sw.Stop();
Console.WriteLine("Creating DataRelation took {0} ms.", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
var list2 =
from r1 in t1.AsEnumerable()
from r2 in r1.GetChildRows("FK_t2_t1")
where r1.Field<int>("ID") == 1
select r2;
count = 0;
foreach (DataRow r in list2)
{
count += r.Field<int>("ID");
}
sw.Stop();
Console.WriteLine("count = {0}.", count);
Console.WriteLine("Completed LINQ iteration using nested query in {0} ms.", sw.ElapsedMilliseconds);
Console.WriteLine();
sw.Reset();
sw.Start();
DataRow parentRow = t1.Select("ID = 1")[0];
count = 0;
foreach (DataRow r in parentRow.GetChildRows("FK_t2_t1"))
{
count += r.Field<int>("ID");
}
sw.Stop();
Console.WriteLine("count = {0}.", count);
Console.WriteLine("Completed explicit iteration of child rows in {0} ms.", sw.ElapsedMilliseconds);
Console.WriteLine();
Console.ReadLine();
}
private static void PopulateTables(DataTable t1, DataTable t2)
{
for (int count1 = 0; count1 < 1000; count1++)
{
DataRow r1 = t1.NewRow();
t1.Rows.Add(r1);
for (int count2 = 0; count2 < 1000; count2++)
{
DataRow r2 = t2.NewRow();
r2["ParentID"] = r1["ID"];
t2.Rows.Add(r2);
}
}
}
}
No lo creo. En este caso, LINQ to Objects probablemente solo trate los dos lados como objetos enumerables regulares, y realice la unión manualmente (sin mirar DataRelation
).