2010-03-04 54 views
22

Tengo el siguiente código que rellena dataTable1 y dataTable2 con dos consultas SQL simples, dataTableSqlJoined se completa desde las mismas tablas pero se unen.Crear DataTable combinada de dos DataTables unidas con LINQ. C#

Estoy tratando de escribir una consulta LINQ que pueda crear el dataTableLinqJoined como si se hubiera creado mediante SQL. En mi ejemplo a continuación, solo devuelve los valores de dataTable1.

El problema que tengo es qué poner en el SELECT de la consulta linq. ¿Cómo puedo crear un DataRow nuevo que contenga todas las Columnas de ambas DataRows? No sabré el nombre/esquema exacto de las columnas de las consultas hasta el tiempo de ejecución.

sqlCommand = new SqlCommand("SELECT ID, A, B FROM Table1", sqlConnection, sqlTransaction); 
sqlAdapter = new SqlDataAdapter(sqlCommand); 
DataTable dataTable1 = new DataTable(); 
sqlAdapter.Fill(dataTable1); 

sqlCommand = new SqlCommand("SELECT ID, C, D FROM Table2", sqlConnection, sqlTransaction); 
sqlAdapter = new SqlDataAdapter(sqlCommand); 
DataTable dataTable2 = new DataTable(); 
sqlAdapter.Fill(dataTable2); 

sqlCommand = new SqlCommand("SELECT Table1.ID, A, B, Table2.ID, C, D FROM Table1 INNER JOIN Table2 ON Table1.ID = Table2.ID", sqlConnection, sqlTransaction); 
sqlAdapter = new SqlDataAdapter(sqlCommand); 
DataTable dataTableSqlJoined = new DataTable(); 
sqlAdapter.Fill(dataTableSqlJoined); 

var dataRows = 
    from 
     dataRows1 in dataTable1.AsEnumerable() 
    join 
     dataRows2 in dataTable2.AsEnumerable() 
    on 
     dataRows1.Field<int>("ID") equals dataRows2.Field<int>("ID") 
    select 
     dataRows1; // + dataRows2; 

DataTable dataTableLinqJoined = dataRows.CopyToDataTable(); 

Para un fondo poco más, la consulta combinada es muy DB intensivo y está causando problemas de rendimiento. Los datos devueltos por la primera consulta son bastante estáticos y pueden almacenarse en caché. Los datos devueltos por la segunda consulta cambian constantemente, pero se ejecutan rápidamente y, por lo tanto, no necesitan almacenarse en caché. También hay un montón de código que depende del paso de la DataTable combinada y, por lo tanto, no hay muchas opciones factibles disponibles para pasar los datos en un formato diferente.

+0

Tengo una pequeña curiosidad acerca de cómo, por un lado, conoces el rendimiento de estas consultas en forma absoluta, pero por otro lado, no conoces la estructura de la tabla hasta el tiempo de ejecución. –

+0

Las consultas se crean dinámicamente. –

Respuesta

20
.

¿Ya has visto esta página?

HOW TO: Implement a DataSet JOIN helper class in Visual C# .NET

Si ese enfoque no es LINQy suficiente para usted, usted podría romper la fila de datos en matrices de objetos:

DataTable targetTable = dataTable1.Clone(); 
var dt2Columns = dataTable2.Columns.OfType<DataColumn>().Select(dc => 
    new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping)); 
targetTable.Columns.AddRange(dt2Columns.ToArray()); 
var rowData = 
    from row1 in dataTable1.AsEnumerable() 
    join row2 in dataTable2.AsEnumerable() 
     on row1.Field<int>("ID") equals row2.Field<int>("ID") 
    select row1.ItemArray.Concat(row2.ItemArray).ToArray(); 
foreach (object[] values in rowData) 
    targetTable.Rows.Add(values); 

Creo que eso es lo más concisa como vas a ser capaz de hacerlo y explicaré por qué: es el esquema.

A DataRow no es un objeto independiente; depende de su propiedad DataTable y no puede vivir sin él. No hay forma compatible para crear un DataRow "desconectado"; el método de extensión CopyToDataTable() funciona en filas que ya existen en un DataTable y simplemente copia el esquema desde el origen (recuerde, cada DataRow tiene una referencia a su padre Table) antes de copiar las filas (lo más probable es que use ImportRow, aunque no he en realidad abrió Reflector para comprobar).

En este caso, tiene un nuevo esquema que debe crear.Antes de que pueda crear una (nueva) fila, necesita crear la tabla para mantenerlos primero, y eso significa escribir al menos las 3 líneas de código en la parte superior del método anterior.

Luego, finalmente puede crear las filas, pero solo una a la vez, ya que DataTable y su asociado DataRowCollection no exponen ningún método para agregar varias filas a la vez. Se podría, por supuesto, añadir su propio método de extensión para el DataRowCollection para hacer de este "look" más agradable:

public static void AddRange(this DataRowCollection rc, 
    IEnumerable<object[]> tuples) 
{ 
    foreach (object[] data in tuples) 
     rc.Add(tuples); 
} 

Posteriormente, se podría deshacerse de la foreach en el primer método y reemplazarlo con:

targetTable.Rows.AddRange(rowData); 

Aunque eso es solo mover la verbosidad, no eliminarla.

En pocas palabras, mientras trabaje con la jerarquía de clases heredada DataSet, siempre habrá un pequeño error. Las extensiones de Linq to DataSet son agradables, pero solo son extensiones y no pueden alterar las limitaciones anteriores.

+0

Había mirado esa página pero no me gustó el hecho de que no fuera LINQy. Tu ejemplo sobre la construcción de la TargetTable es genial. Gracias. –

+0

Hecho una edición menor de su código, el itemarray debe ser un Concat en lugar de una Unión. De lo contrario, elimina cualquier columna con valores idénticos (nulos). Aparte de eso sin embargo. ¡Está funcionando perfectamente! ¡Gracias! –

+0

targetTable.Columns.AddRange (dt2Columns.ToArray()); da error como su columna de adición con el mismo nombre nuevamente. – Cannon

0
select new { 
    ID = dataRows1.ID, // no need to select dataRows2.ID, because of JOIN. 
    A = dataRows1.A, 
    B = dataRows1.B, 
    C = dataRows2.C, 
    D = dataRows2.D 
}; 
+0

No conozco los nombres de las columnas/esquema hasta el tiempo de ejecución. Tampoco hay una forma "simple" de convertir este tipo en una DataTable. –

+0

No detecté eso en la pregunta. Lo siento. Para convertir tipos desconocidos en una DataTable, puede usar la reflexión (o hacer cosas alocadas con Expression : http://www.infoq.com/articles/expression-compiler) –

1

Perdónenme si sueno como un idiota.

Creo que debe tener lista la tabla final (con todos los campos de la tabla A & tabla B).
Y, en lugar de usar LINQ, haga una unión & y luego haga un ForEach en el resultado & inserte el valor en la tabla de datos final.

Pseudocódigo:

dt1.Join (DT2) .Where (...) ParaCada (fila => código para leer el contenido del objeto anónimo & añadirlo a finalTable.Rows)

+0

Considero algo como esto. p.ej. ejecutando la consulta combinada, pero en lugar de realizar las uniones simplemente colocando valores nulos en las columnas de la segunda consulta. Luego solo ejecuta la segunda consulta para llenarlos. +1 por la respuesta, pero todavía estoy esperando algo más "elegante". –

5

Aaronaught que fue genial. Pero me gustaría agregar algunas mejoras a su código LINQy. Al agregar columnas de dataTable2 a la tabla Target, habría una posibilidad de que ya exista una columna en la tabla Target (a la que nos estamos uniendo). Así que, aquí vamos.

DataTable targetTable = dataTable1.Clone(); 
var dt2Columns = dataTable2.Columns.OfType<DataColumn>().Select(dc => 
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping)); 
var dt2FinalColumns=from dc in dt2Columns.AsEnumerable() 
        where targetTable.Columns.Contains(dc.ColumnName) == false 
        select dc; 
targetTable.Columns.AddRange(dt2FinalColumns.ToArray()); 
var rowData =from row1 in dataTable1.AsEnumerable() 
      join row2 in dataTable2.AsEnumerable() 
      on row1.Field<int>("ID") equals row2.Field<int>("ID") 
      select row1.ItemArray.Concat(row2.ItemArray.Where(r2=> row1.ItemArray.Contains(r2)==false)).ToArray(); 
foreach (object[] values in rowData) 
targetTable.Rows.Add(values); 

Espero que esto sea útil para los chicos como yo.

+0

¿Qué es dtcomments? – Cannon

+0

Cambiar dtcomments to dt2Columns. Una pregunta: ¿Cómo unir tablas si tengo más de dos tablas para unirme? – Cannon

+0

'var rowData = de row1 en dataTable1.AsEnumerable() unen fila2 en dataTable2.AsEnumerable() en row1.Field ("ID") es igual a row2.Field ("ID") unen Row3 en .AsEnumerable() en ' – suryakiran

Cuestiones relacionadas