How to throuhly join two datatables using linq without column names - c#

This is propably answered somewhere else, but I haven't found working solution yet.
I have two datatables and I want to join them into one datatable containing all data from both of them, or at least from the first of them and some columns from the second datatable.
I don't want to list all columns (totally 180) from the first datatable. I have tried eg. this
var JoinedResult = from t1 in table1.Rows.Cast<DataRow>()
join t2 in table2.Rows.Cast<DataRow>()
on Convert.ToInt32(t1.Field<string>("ProductID")) equals t2.Field<int>("FuelId")
select t1;
but that gives only the columns from table1. How to get colums from table2 too to my result? Finally, I need to add my result to a dataset.
ResultSet.Tables.Add(JoinedResult.CopyToDataTable());
EDIT:
I ended up with this as the solution.
This follows an example given here Create join with Select All (select *) in linq to datasets
DataTable dtProduct = dsProduct.Tables[0];
DataTable dtMoistureLimits = ds.Tables[0];
//clone dt1, copies all the columns to newTable
DataTable dtProductWithMoistureLimits = dtProduct.Clone();
//copies all the columns from dt2 to newTable
foreach (DataColumn c in dtMoistureLimits.Columns)
dtProductWithMoistureLimits.Columns.Add(c.ColumnName, c.DataType);
var ProductsJoinedWithMoistureLimits = dtProduct.Rows.Cast<DataRow>()
.Join(dtMoistureLimits.Rows.Cast<DataRow>(),// join table1 and table2
t1 => new { ProductID = t1.Field<int>("ProductID"), DelivererID = t1.Field<int>("DelivererID") },
t2 => new { ProductID = t2.Field<int>("MoistureLimits_ProductID"), DelivererID = t2.Field<int>("MoistureLimits_DelivererID") },
(t1, t2) => // when they match
{ // make a new object
// containing the matching t1 and t2
DataRow row = dtProductWithMoistureLimits.NewRow();
row.ItemArray = t1.ItemArray.Concat(t2.ItemArray).ToArray();
dtProductWithMoistureLimits.Rows.Add(row);
return row;
});
However, in dtMoistureLimits there is not rows for all "ProductID" and "DelivererID" in dtProduct. Currently my solution returns only matching rows.
How to improve solution to return also those rows where there is not data for "ProductID" and "DelivererID" in dtMoistureLimits?

Solution using method syntax, without having to mention all columns
var result = table1.Rows.Cast<DataRow>()
.Join(table2.Rows.Cast<DataRow>(), // join table1 and table2
t1 => Convert.ToInt32(t1.Field<string>("ProductID")) // from every t1 get the productId
t2 => t2.Field<int>("FuelId") // from every t2 get the fuelId,
(t1, t2) => new // when they match
{ // make a new object
T1 = t1, // containing the matching t1 and t2
T2 = t2,
}

var JoinedResult = (from t1 in table1.Rows.Cast<DataRow>()
join t2 in table2.Rows.Cast<DataRow>()
on Convert.ToInt32(t1.Field<string>("ProductID")) equals t2.Field<int>("FuelId")
select new { T1 = t1,
T2 = t2.column_name // all columns needed can be listed here
}).ToList();
EDIT:
To convert the above result to a DataTable, use the following method:
DataTable dataTable = new DataTable();
//Get all the properties
PropertyInfo[] Props = JoinedResult.Select(y=>y.T1).First().GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in Props)
{
//Defining type of data column gives proper data table
var type = (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(prop.PropertyType) : prop.PropertyType);
//Setting column names as Property names
dataTable.Columns.Add(prop.Name, type);
}
dataTable.Columns.Add(t2_column_name, t2_column_type);
foreach (var item in JoinedResult)
{
var values = new object[Props.Length];
for (int i = 0; i < Props.Length; i++)
{
//inserting property values to datatable rows
values[i] = Props[i].GetValue(item.T1, null);
}
values[Props.Length] = item.T2;
dataTable.Rows.Add(values);
}

Related

Use .Select().Join() to Join Two DataTables

I have been searching high and low for this to no avail. I have two DataTables that I want to join without creating a new resultant table as I simply need to update some rows in one of the tables to be displayed in a grid view, similar to the below code, but with a join:
sage_invoices.Select("CCE2 IS NULL")
.ToList<DataRow>()
.ForEach(row =>
{
row["Error"] = 1;
row["ErrorMessage"] = "Missing Region Code (Dimension 2 - CCE2)";
});
Everything I've found produces a new output datatable, similar to the below code:
var collection = from t1 in dt1.AsEnumerable()
join t2 in dt2.AsEnumerable()
on t1["id"] equals t2["id"]
select new { T1 = t1, T2 = t2 };
What I can't find is how to join two DataTables using .Join:
sage_invoices.Select()
.Join(<What Goes here?>)
.ToList<DataRow>()
.ForEach(row =>
{
row["Error"] = 1;
row["ErrorMessage"] = "ITMREF is not a Sage Product Code";
});
If anyone could point me in the right direction, I would be most grateful.
Thanks
Gareth
I typically accomplish this by building an anonymous object that contains a reference to my source and destination objects through a Join or GroupJoin, then looping over the result of the Join to update my destination object. See the example below.
Take a look at the documentation on Join and GroupJoin. Join is great for a 1-1 match, while GroupJoin is a 0-* match (like a SQL left join). The arguments to Join and GroupJoin allow you to specify a selector function for each IEnumerable followed by a selector function for the output object. Note that t1 and t2 below refer to table1 and table2.
using System;
using System.Data;
using System.Linq;
public class Program
{
public static void Main()
{
var table1 = GetEmptyTable();
table1.Rows.Add(1, "Old Value", false);
table1.Rows.Add(2, "Untouched Value", false);
var table2 = GetEmptyTable();
table2.Rows.Add(1, "New Value", false);
table2.Rows.Add(3, "Unused Value", false);
Console.WriteLine("Before...");
Console.WriteLine(PrintTable(table1));
var matched = table1.Select()
.Join(table2.Select(), t1 => (int)t1["A"], t2 => (int)t2["A"], (t1, t2)
=> new
{
DestinationRow = t1,
SourceRow = t2
});
foreach (var match in matched)
{
match.DestinationRow["B"] = match.SourceRow["B"];
match.DestinationRow["C"] = true;
}
Console.WriteLine("After...");
Console.WriteLine(PrintTable(table1));
}
private static DataTable GetEmptyTable()
{
var table = new DataTable();
table.Columns.Add("A", typeof(int));
table.Columns.Add("B", typeof(string));
table.Columns.Add("C", typeof(bool));
return table;
}
private static string PrintTable(DataTable table)
{
return string.Join(Environment.NewLine, table.Select().Select(x => "[" +
string.Join(", ", x.ItemArray) + "]"));
}
}

join 2 datatable with and condition caluse LINQ

hi guy i have 2 datatable like this
dt1
id (1,2,3)
name (abc,xyz,def)
num(11,12,13)
dt2
id (1,2,3)
name (abc,xyz,def)
num_from (10,13,11)
num_to (14,14,14)
how could i select id which have num between num_from and num_to using linq
i tried this
dtres = (from t1 in dt1.AsEnumerable()
join t2 in dt1.AsEnumerable() on t1.Field<string>("ID") equals t2.Field<string>("ID")
where t1["num"]>= t2["num_from"] &&
t1["num"]<= t2["num_to"]
select t1).CopyToDataTable();
Consider the following code:
It produce the result as IEnumerable<AnonymousType>, not DataRow, so cannot apply CopyToDataTable() extension method, instead I have provided a custom extension method at the bottom of this code ToDataTable, you can change the number of columns from the final result, I have included everything.
My understanding from your question is you need a filter such that Num in DataTable1 is between Num_From and Num_To in the Datatable2
var resultDataTable =
dt1.AsEnumerable().Join(dt2.AsEnumerable(), t1 => t1["id"], t2 => t2["id"], (t1, t2) => new { t1, t2})
.Where(t => (int.Parse(t.t2["num_from"].ToString()) <= int.Parse(t.t1["num"].ToString()) && int.Parse(t.t2["num_to"].ToString()) >= int.Parse(t.t1["num"].ToString())))
.Select(t => new {
Id1 = t.t1["id"].ToString(),
Name1 = t.t1["name"].ToString(),
Num1 = t.t1["num"].ToString(),
Id2 = t.t2["id"].ToString(),
Name2 = t.t2["name"].ToString(),
Num_From = t.t2["num_from"].ToString(),
Num_To = t.t2["num_to"].ToString()
}
).ToList().ToDataTable();
Extension method to convert IEnumerable to DataTable
public static class ExtensionDT
{
public static DataTable ToDataTable<T>(this List<T> items)
{
var tb = new DataTable(typeof(T).Name);
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
tb.Columns.Add(prop.Name, prop.PropertyType);
}
foreach (var item in items)
{
var values = new object[props.Length];
for (var i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
tb.Rows.Add(values);
}
return tb;
}
}
Creating a join operation on Linq database is not possible like we do on Mysql and sql. But you can create a simple function to help you do that. You will need a function to return a string or interger for you:
private ObservableCollection<Var_Items> _var_ItemsList;
public ObservableCollection<Var_Items> Var_ItemsList
{ get { return _var_ItemsList; }
set { _var_ItemsList = value;
NotifyPropertyChanged("Var_ItemsList");
}
}
dtres = from t1 in dt1.AsEnumerable() where t1["num"]>= getMyVar1(t1["num"]) and t1["num"]<= getMyVar1(t1["num"]) select t1;
public string getMyVar1(int find_var)
{
var thisvar = from t2 in dt2.AsEnumerable() where t2["num_from"] >= find_var select t2;
varitems = new ObservableCollection<Var_Items>(Var_ItemsList);
return varitems.Last();
}
public string getMyVar2(int find_var)
{
var thisvar = from t2 in dt2.AsEnumerable() where t2["num_to"] >= find_var select t2;
varitems = new ObservableCollection<Var_Items>(Var_ItemsList);
return varitems.Last();
}
I have tried to simplify my answer to be easier to understand. I hope this helps

How to use Inner Join and then fill a DataSet with result of the join?

Well, this is my question. In short terms; I have two tables, Consequents and Atomic propositions:
AtomicP table
ID Proposition
1 | A |
1 | B |
1 | C |
2 | D |
2 | E |
Consequent Table
ID | Consequent |
1 | A |
2 | B |
And all I just want to do, is to implement a inner join which gives me all the values where the ID for both tables is the same(i.e):
AtomicP Table "A" "B" "C" -> "A" Consequent Table
and withe result given tanks to the inner joins , save that result in a Data Set or in another data structure that could be better.
Best regards.
Assuming the destination table has the values Id, Proposition and Consequent ..
insert into newtable (id,proposition,consequent) select id,atomicP,Consequent from atmicp,consequent where atomicP.id = consequent.id
public class Proposition
{
public int Id;
public string Value;
public Proposition(int id, string value){
Id = id;
Value = value;
}
}
public class Consequent
{
public int Id;
public string Value;
public Consequent(int id, string value){
Id = id;
Value = value;
}
}
var atomicP = new List<Proposition>{
new Proposition(1, "A"),
new Proposition(1, "B"),
new Proposition(1, "C"),
new Proposition(2, "D"),
new Proposition(2, "E"),
}
var consequents = new List<Consequent>{
new Consequent(1, "A"),
new Consequent(2, "B"),
}
var query = from proposition in atomicP
join consequent in consequents on proposition.Id == consequent.Id
select proposition.Value;
return query.ToList();
use this function
private DataTable JoinDataTables(DataTable t1, DataTable t2, params Func<DataRow, DataRow, bool>[] joinOn)
{
DataTable result = new DataTable();
foreach (DataColumn col in t1.Columns)
{
if (result.Columns[col.ColumnName] == null)
result.Columns.Add(col.ColumnName, col.DataType);
}
foreach (DataColumn col in t2.Columns)
{
if (result.Columns[col.ColumnName] == null)
result.Columns.Add(col.ColumnName, col.DataType);
}
foreach (DataRow row1 in t1.Rows)
{
var joinRows = t2.AsEnumerable().Where(row2 =>
{
foreach (var parameter in joinOn)
{
if (!parameter(row1, row2)) return false;
}
return true;
});
foreach (DataRow fromRow in joinRows)
{
DataRow insertRow = result.NewRow();
foreach (DataColumn col1 in t1.Columns)
{
insertRow[col1.ColumnName] = row1[col1.ColumnName];
}
foreach (DataColumn col2 in t2.Columns)
{
insertRow[col2.ColumnName] = fromRow[col2.ColumnName];
}
result.Rows.Add(insertRow);
}
}
return result;
}
An example of how you might use this:
var test = JoinDataTables(Consequents, Atomic,
(row1, row2) =>
row1.Field<int>("ID") == row2.Field<int>("ID"));
I assume you want to join In C# and get DataTable(bit unclear in question).
Code snippets joins two DataTable using Linq and inserts to another Table.
DataTable results = new DataTable();
results.Columns.Add("ID", typeof(int));
results.Columns.Add("Proposition", typeof(string));
results.Columns.Add("Consequent", typeof(string));
var result1 = from arow in AtomicP.AsEnumerable()
join con in Consequent.AsEnumerable()
on arow.Field<int>("ID") equals con.Field<int>("ID")
select results.LoadDataRow(new object[]
{
arow.Field<int>("ID"),
arow.Field<string>("Proposition"),
con.Field<string>("Consequent")
}, false);
Now we can access results by iterating through results.
foreach(DataRow row in results.Rows)
{
foreach(DataColumn column in results.Columns)
{
//Console.WriteLine(row[column]);
}
}
Working Code

having trouble with left join LINQ

I have two DataTables t1 and t2. I'm trying to perform a LINQ left join, multiple equijoin, to get the DataRows in t1 that are not in t2.
In SQL, what I'm trying to accomplish is:
select t1.*
from t1
left join t2
on t1.a=t2.a and
t1.b=t2.b and
t1.c=t2.c
where
t2.a is null
So far I have the following:
public DataTable t1_without_t2(DataTable t1, DataTable t2)
{
var query = from t1_row in t1.AsEnumerable()
join t2_row in t2.AsEnumerable()
on
new { t_a = t1_row["a"], t_b = t1_row["b"], t_c = t1_row["c"]}
equals
new { t_a = t2_row["a"], t_b = t2_row["b"], t_c = t2_row["c"]}
into leftJoinT1withoutT2
from join_row in leftJoinT1withoutT2.DefaultIfEmpty()
where t2_row["a"] == null
select new
{
j_a = join_row["a"],
j_b = join_row["b"],
j_c = join_row["c"],
};
DataTable dt = t1.Clone();
foreach (var result in query)
{
dt.LoadDataRow(
new object[]
{
result.j_a,
result.j_b,
result.j_c
},
false);
}
return dt;
}
This is failing on the line j_a = join_row["a"] with this message:
Column 'a' does not belong to table.
I thought that the into leftJoinT1withoutT2 line was supposed to put the results of the join into a var with the column structure of table t1, from which the non-matching entries would be removed using where t2_row["a"] == null . Is that not what's happening here? I'm a little confused.
It should look like this:
var query = from t1_row in t1.AsEnumerable()
join t2_row in t2.AsEnumerable()
on
new { t_a = t1_row["a"], t_b = t1_row["b"], t_c = t1_row["c"] }
equals
new { t_a = t2_row["a"], t_b = t2_row["b"], t_c = t2_row["c"] }
into leftJoinT1withoutT2
from join_row in leftJoinT1withoutT2.DefaultIfEmpty()
.Where(r => r == null)
select new
{
j_a = t1_row["a"],
j_b = t1_row["b"],
j_c = t1_row["c"],
};
Have a look at How to: Perform Left Outer Joins (C# Programming Guide).
The join_row gets null (i.e. default TSource value, see Enumerable.DefaultIfEmpty) when there is no matching element in t2, while t1_row always contains the joined value. So as far as you need only those rows for which join_row is null, I used .Where(r => r == null).

How to concat only unique columns with data

I am using this below:
public static DataTable DataTableJoiner(DataTable dt1, DataTable dt2)
{
using (DataTable targetTable = dt1.Clone())
{
var dt2Query = dt2.Columns.OfType<DataColumn>().Select(dc =>
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression,
dc.ColumnMapping));
var dt2FilterQuery = from dc in dt2Query.AsEnumerable()
where targetTable.Columns
.Contains(dc.ColumnName) == false
select dc;
targetTable.Columns.AddRange(dt2FilterQuery.ToArray());
var rowData = from row1 in dt1.AsEnumerable()
join row2 in dt2.AsEnumerable()
on row1.Field<int>("Code") equals
row2.Field<int>("Code")
select row1.ItemArray
.Concat(row2.ItemArray
.Where(r2 =>
row1.ItemArray.Contains(r2) == false)).ToArray();
foreach (object[] values in rowData) targetTable.Rows.Add(values);
return targetTable;
}
}
There is a problem with this line:
select row1.ItemArray.Concat(row2.ItemArray.Where(r2 =>
row1.ItemArray.Contains(r2) == false)).ToArray();
It seems to be saying don't include me if this value (rather than column) already exists.
I am using this method to join two tables together based on a column that both tables share, but I only want the unique columns with data of both tables as a final result.
Any ideas?
I am not sure if I understand your requirement 100%, but this:
row2.ItemArray.Where(r2 => row1.ItemArray.Contains(r2) == false)
will filter out those items that happen to appear in any column of table 1, not just the column you are joining on.
So what I would try to do is filter the item based on the index, using an overload of the Where extension method:
// Get the index of the column we are joining on:
int joinColumnIndex = dt2.Columns.IndexOf("Code");
// Now we can filter out the proper item in the rowData query:
row2.ItemArray.Where((r2,idx) => idx != joinColumnIndex)
...
No, wait. Here:
var dt2FilterQuery = from dc in dt2Query.AsEnumerable()
where targetTable.Columns
.Contains(dc.ColumnName) == false
select dc;
You are filtering out all columns of table 2 whose name also appear in table 1. So what you probably want is this:
public static DataTable DataTableJoiner(DataTable dt1, DataTable dt2)
{
DataTable targetTable = dt1.Clone();
var dt2Query = dt2.Columns.OfType<DataColumn>().Select(dc =>
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression,
dc.ColumnMapping));
var dt2FilterQuery = from dc in dt2Query.AsEnumerable()
where !targetTable.Columns.Contains(dc.ColumnName)
select dc;
var columnsToAdd = dt2FilterQuery.ToArray();
var columnsIndices = columnsToAdd.Select(dc => dt2.Columns.IndexOf(dc.ColumnName));
targetTable.Columns.AddRange(columnsToAdd);
var rowData = from row1 in dt1.AsEnumerable()
join row2 in dt2.AsEnumerable()
on row1.Field<int>("Code") equals
row2.Field<int>("Code")
select row1.ItemArray
.Concat(row2.ItemArray
.Where((r2,idx) =>
columnsIndices.Contains(idx))).ToArray();
foreach (object[] values in rowData) targetTable.Rows.Add(values);
return targetTable;
}
Btw. I don't quite understand why you are wrapping the DataTable you return in a using statement. Imho it is kind of pointless to dispose the very object you return to your caller right away...

Categories