Best Practice: Convert LINQ Query result to a DataTable without looping - c#

What is the best practice to convert LINQ-Query result to a new DataTable?
can I find a solution better than foreach every result item?
EDIT
AnonymousType
var rslt = from eisd in empsQuery
join eng in getAllEmployees()
on eisd.EMPLOYID.Trim() equals eng.EMPLOYID.Trim()
select new
{
eisd.CompanyID,
eisd.DIRECTID,
eisd.EMPLOYID,
eisd.INACTIVE,
eisd.LEVEL,
eng.EnglishName
};
EDIT 2:
I got exception:
Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator.
as I try to execute the query
and found the solution here IEnumerable.Except wont work, so what do I do? and Need linq help

Use Linq to Dataset. From the MSDN : Creating a DataTable From a Query (LINQ to DataSet)
// Query the SalesOrderHeader table for orders placed
// after August 8, 2001.
IEnumerable<DataRow> query =
from order in orders.AsEnumerable()
where order.Field<DateTime>("OrderDate") > new DateTime(2001, 8, 1)
select order;
// Create a table from the query.
DataTable boundTable = query.CopyToDataTable<DataRow>();
If you have anonymous types :
From the Coder Blog : Using Linq anonymous types and CopyDataTable
It explains how to use MSDN's How to: Implement CopyToDataTable Where the Generic Type T Is Not a DataRow

Converting Query result in DataTables Generic Function
DataTable ddt = new DataTable();
ddt = LINQResultToDataTable(query);
public DataTable LINQResultToDataTable<T>(IEnumerable<T> Linqlist)
{
DataTable dt = new DataTable();
PropertyInfo[] columns = null;
if (Linqlist == null) return dt;
foreach (T Record in Linqlist)
{
if (columns == null)
{
columns = ((Type)Record.GetType()).GetProperties();
foreach (PropertyInfo GetProperty in columns)
{
Type colType = GetProperty.PropertyType;
if ((colType.IsGenericType) && (colType.GetGenericTypeDefinition()
== typeof(Nullable<>)))
{
colType = colType.GetGenericArguments()[0];
}
dt.Columns.Add(new DataColumn(GetProperty.Name, colType));
}
}
DataRow dr = dt.NewRow();
foreach (PropertyInfo pinfo in columns)
{
dr[pinfo.Name] = pinfo.GetValue(Record, null) == null ? DBNull.Value : pinfo.GetValue
(Record, null);
}
dt.Rows.Add(dr);
}
return dt;
}

I am using morelinq.2.2.0 package in asp.net web application, Nuget package manager console
PM> Install-Package morelinq
Namespace
using MoreLinq;
My sample stored procedure sp_Profile() which returns profile details
DataTable dt = context.sp_Profile().ToDataTable();

Use System.Reflection and iterate for each record in the query object.
Dim dtResult As New DataTable
Dim t As Type = objRow.GetType
Dim pi As PropertyInfo() = t.GetProperties()
For Each p As PropertyInfo In pi
dtResult.Columns.Add(p.Name)
Next
Dim newRow = dtResult.NewRow()
For Each p As PropertyInfo In pi
newRow(p.Name) = p.GetValue(objRow,Nothing)
Next
dtResult.Rows.Add(newRow.ItemArray)
Return dtResult

Try
Datatable dt= (from rec in dr.AsEnumerable() select rec).CopyToDataTable()

Related

Convert LINQ to DataTable

The linq query is returning result in form of {DataRow Job1, DataRow Run}. How can i convert JoinResult into a DataTable .
var JoinResult = (from Job1 in Jobdata.AsEnumerable()
join Product in Productdata.AsEnumerable()
on Job1.Field<int>("Job_Id") equals Product.Field<int>("Job_Id")
join Run in data.AsEnumerable()
on Job1.Field<int>("Run_Id") equals Run.Field<int>("Run_Id")
select new { Job1, Run });
You can create an extension method & use it like below:
/// <Summary>
/// Convert a IEnumerable to a DataTable.
/// <TypeParam name="T">Type representing the type to convert.</TypeParam>
/// <param name="source">List of requested type representing the values to convert.</param>
/// <returns> returns a DataTable</returns>
/// </Summary>
public static DataTable ToDataTable<T>(this IEnumerable<T> source)
{
// Use reflection to get the properties for the type we’re converting to a DataTable.
var props = typeof(T).GetProperties();
// Build the structure of the DataTable by converting the PropertyInfo[] into DataColumn[] using property list
// Add each DataColumn to the DataTable at one time with the AddRange method.
var dt = new DataTable();
dt.Columns.AddRange(
props.Select(p => new DataColumn(p.Name, BaseType(p.PropertyType))).ToArray());
// Populate the property values to the DataTable
source.ToList().ForEach(
i => dt.Rows.Add(props.Select(p => p.GetValue(i, null)).ToArray())
);
return dt;
}
//To call the above method:
var dt = JoinResult.ToDataTable();
Note: You just need to update your linq query to get IEnumerable data.
Hope it helps you.
You can create a new datatable by looping trough the query result:
private DataTable createDt()
{
var JoinResult = (from Job1 in Jobdata.AsEnumerable()
join Product in Productdata.AsEnumerable()
on Job1.Field<int>("Job_Id") equals Product.Field<int>("Job_Id")
join Run in data.AsEnumerable()
on Job1.Field<int>("Run_Id") equals Run.Field<int>("Run_Id")
select new { Job1, Run });
DataTable newdt = new DataTable();
// declare strongly typed data columns
DataColumn run = new DataColumn("run");
run.DataType = System.Type.GetType("System.Int32");
newdt.Columns.Add(run);
DataColumn job1 = new DataColumn("job1");
job1.DataType = System.Type.GetType("System.Int32");
newdt.Columns.Add(job1);
foreach (var x in JoinResult)
{
DataRow dr = newdt.NewRow();
dr["run"] = x.Run;
dr["job1"] = x.Job1;
newdt.Rows.Add(dr);
}
return newdt;
}
BTW - you can not use CopyToDataTable() in your case: Why am I not getting .CopyToDataTable() in Linq Query()
but if you insist to use it: How to: Implement CopyToDataTable Where the Generic Type T Is Not a DataRow

How to use LINQ to get unique columns from a DataTable

I have a DataTable in C# with columns defined as follows:
DataTable dt = new DataTable();
dt.Columns.Add("OrgName", typeof(string));
dt.Columns.Add("OrgExId", typeof(string));
dt.Columns.Add("UserName", typeof(string));
dt.Columns.Add("UserExId", typeof(string));
dt.Columns.Add("UserEmail", typeof(string));
"UserName", "UserExId", and "UserEmail" are all unique and they are grouped by "OrgName" and "OrgExId"
I want to write a LINQ query to make a new DataTable that contains unique "OrgExId's" and "OrgName's"
This is as far as I got:
var results = from row in dt.AsEnumerable()
group row by row["OrgExId"] into orgs
select orgs;
Specifically in this query, I don't understand how I am supposed to select the rows from the original DataTable. Visual Studio says orgs is of the type `IGrouping, but I have never really seen this type before and am not sure how to manipulate it.
Is this a key value pair?
Sorry about that all. I did not specify my end result.
I want to end up with a DataTable with two columns, distinct "OrgExId" and "OrgName". (There is a one to one relationship between "OrgExId" and "OrgName")
All you really need is a Distinct clause
var output = dt.AsEnumerable()
.Select(x => new {OrgExId = x["OrgExId"], OrgName = x["OrgName"]})
.Distinct();
You can then iterate over this and build a DataTable or whatever you need.
UPDATE: You asked for the output to be a DataTable and the above solution didn't quite sit well with me since it requires extra work. To make this more efficient you could do a custom equality comparer.
Your linq looks like this...
// This returns a DataTable
var output = dt.AsEnumerable()
.Distinct(new OrgExIdEqualityComparer())
.CopyToDataTable();
And your comparer looks like this...
public class OrgExIdEqualityComparer : IEqualityComparer<DataRow>
{
public bool Equals(DataRow x, DataRow y)
{
return x["OrgExId"].Equals(y["OrgExId"]);
}
public int GetHashCode(DataRow obj)
{
return obj["OrgExId"].GetHashCode();
}
}
Use Key property of IGrouping:
var results = from row in dt.AsEnumerable()
group row by new {
row.GetField<string>("OrgExId"),
row.GetField<string>("UserName")
} into orgs
select orgs.Key;
It will give you collection of anonymous types. To get DataTable you can simply iterate over results and add it into DataTable.
DataTable dt = new DataTable();
dt.Columns.Add("OrgName", typeof(string));
dt.Columns.Add("OrgExId", typeof(string));
dt.Columns.Add("UserName", typeof(string));
dt.Columns.Add("UserExId", typeof(string));
dt.Columns.Add("UserEmail", typeof(string));
// put some data for testing purpose
var id = Guid.NewGuid().ToString();
for (var i = 0; i < 10; i++)
dt.Rows.Add(id, i.ToString(), "user_name", Guid.NewGuid().ToString());
var x = dt.Rows.Cast<DataRow>().Select(x => x.Field<string>("UserName")).Distinct();
Console.WriteLine(x);

Select distinct values from a large DataTable column

I have a DataTable with 22 columns and one of the columns I have is called "id". I would like to query this column and keep all the distinct values in a list. The table can have between 10 and a million rows.
What is the best method to do this? Currently I am using a for loop to go though the column and compare the values and if the values are the same then the it goes to the next and when not the same it adds the id to the array. But as the table can have 10 to a million rows is there a more efficient way to do this! How would I go about doing this more efficiently?
Method 1:
DataView view = new DataView(table);
DataTable distinctValues = view.ToTable(true, "id");
Method 2:
You will have to create a class matching your datatable column names and then you can use the following extension method to convert Datatable to List
public static List<T> ToList<T>(this DataTable table) where T : new()
{
List<PropertyInfo> properties = typeof(T).GetProperties().ToList();
List<T> result = new List<T>();
foreach (var row in table.Rows)
{
var item = CreateItemFromRow<T>((DataRow)row, properties);
result.Add(item);
}
return result;
}
private static T CreateItemFromRow<T>(DataRow row, List<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
if (row.Table.Columns.Contains(property.Name))
{
if (row[property.Name] != DBNull.Value)
property.SetValue(item, row[property.Name], null);
}
}
return item;
}
and then you can get distinct from list using
YourList.Select(x => x.Id).Distinct();
Please note that this will return you complete Records and not just ids.
This will retrun you distinct Ids
var distinctIds = datatable.AsEnumerable()
.Select(s=> new {
id = s.Field<string>("id"),
})
.Distinct().ToList();
dt- your data table name
ColumnName- your columnname i.e id
DataView view = new DataView(dt);
DataTable distinctValues = new DataTable();
distinctValues = view.ToTable(true, ColumnName);
All credit to Rajeev Kumar's answer, but I received a list of anonymous type that evaluated to string, which was not as easy to iterate over. Updating the code as below helped to return a List that was more easy to manipulate (or, for example, drop straight into a foreach block).
var distinctIds = datatable.AsEnumerable().Select(row => row.Field<string>("id")).Distinct().ToList();
Try this:
var idColumn="id";
var list = dt.DefaultView
.ToTable(true, idColumn)
.Rows
.Cast<DataRow>()
.Select(row => row[idColumn])
.ToList();
Sorry to post answer for very old thread. my answer may help other in future.
string[] TobeDistinct = {"Name","City","State"};
DataTable dtDistinct = GetDistinctRecords(DTwithDuplicate, TobeDistinct);
//Following function will return Distinct records for Name, City and State column.
public static DataTable GetDistinctRecords(DataTable dt, string[] Columns)
{
DataTable dtUniqRecords = new DataTable();
dtUniqRecords = dt.DefaultView.ToTable(true, Columns);
return dtUniqRecords;
}
Note: Columns[0] is the column on which you want to perform the DISTINCT query and sorting
DataView view = new DataView(DT_InputDataTable);
DataTable distinctValues = new DataTable();
view = new DataView(DT_InputDataTable) { Sort = DT_InputDataTable.Columns[0].ToString() };
distinctValues = view.ToTable(true, DT_InputDataTable.Columns[0].ToString());

How do I get this Linq query with custom join into a DataSet?

I cannot seem to get past the anonymous type used by Linq's select clause when using join. I want to put my query result into a datatable. Here's what I have in memory at runtime...
DataTable1 (source)
----------
col1A <int> (acting PK, not explicitly defined as such)
col1B <string>
col1C <string>
col1D <string> (FK -> dt2.col2A, also not explicitly defined)
col1E <string>
DataTable2 (source)
----------
col2A <string> (acting PK, also not explicitly defined)
col2B <string>
DataTable3 (destination)
----------
colA <int>
colB <string>
colC <string>
colD <string>
Here's what I want (SQL equivelant to what I'm looking for)...
INSERT INTO DataTable3 (colA, colB, colC, colD)
SELECT dt1.col1A, dt1.col1B, dt1.col1C, dt2.col2B
FROM DataTable1 dt1
LEFT JOIN DataTable2 dt2 ON (dt1.col1D = dt2.col2A)
Working backwards, I see this simple line to place an enumerated query result set into a datatable:
DataTable DataTable3 = query.CopyToDataTable();
Here's what I tried...
I don't know how to build 'query' in a way that will allow me to use this.
Attempt 1 (using var query; this worked, read further):
var query =
from dt1 in DataTable1.AsEnumerable()
join dt2 in DataTable2.AsEnumerable()
on dt1.Field<string>("col1D") equals dt2.Field<string>("col2A")
select new { col1A = dt1.Field<int>("col1A"),
col1B = dt1.Field<string>("col1B"),
col1C = dt1.Field<string>("col1C"),
col2B = dt2.Field<string>("col2B") };
But that gives me this error on 'query.CopyToDataTable()':
"Error 1 The type 'AnonymousType#1' cannot be used as type parameter
'T' in the generic type or method
'System.Data.DataTableExtensions.CopyToDataTable(System.Collections.Generic.IEnumerable)'.
There is no implicit reference conversion from 'AnonymousType#1' to
'System.Data.DataRow'."
From what I can surmise, this error is because 'select new' doesn't build DataRows into 'query', which .CopyToDataTable() demands; it just builds anonymous records. Furthermore, I don't know how to just cast query elements into DataRow because "it is inaccessible due to protection level".
Attempt 2 (using IEnumerable query; don't bother trying this):
IEnumerable<DataRow> query =
from dt1 in DataTable1.AsEnumerable()
join dt2 in DataTable2.AsEnumerable()
on dt1.Field<string>("col1D") equals dt2.Field<string>("col2A")
select new { col1A = dt1.Field<int>("col1A"),
col1B = dt1.Field<string>("col1B"),
col1C = dt1.Field<string>("col1C"),
col2B = dt2.Field<string>("col2B") };
This gives an error on 'join':
"Error 5 Cannot implicitly convert type
'System.Collections.Generic.IEnumerable' to
'System.Collections.Generic.IEnumerable'. An
explicit conversion exists (are you missing a cast?)"
On a side note, if I comment out the 'join' clause (and the corresponding dt2 column in the 'select'), I get this error on the 'select':
"Error 2 Cannot implicitly convert type
'System.Data.EnumerableRowCollection' to
'System.Collections.Generic.IEnumerable'. An
explicit conversion exists (are you missing a cast?)"
I also tried pre-defining DataTable3 with columns to give the 'select' clause a real DataRow to go off of, but I still couldn't get the 'select' to take advantage of it.
Edits, Details, Solution:
Thanks Tim for the help. I went with Attempt 1 (var query). I also pre-defined DataTable3 as such:
DataTable DataTable3 = new DataTable();
DataTable3.Columns.Add("colA", System.Type.GetType("System.Int32"));
DataTable3.Columns.Add("colB", System.Type.GetType("System.String"));
DataTable3.Columns.Add("colC", System.Type.GetType("System.String"));
DataTable3.Columns.Add("colD", System.Type.GetType("System.String"));
I stored the results from 'query' using a foreach loop as Tim suggested:
foreach (var x in query)
{
DataRow newRow = DataTable3.NewRow();
newRow.SetField("colA", x.col1A);
newRow.SetField("colB", x.col1B);
newRow.SetField("colC", x.col1C);
newRow.SetField("colD", x.col2B);
DataTable3.Rows.Add(newRow);
}
This could be genericized in a few ways with some additional effort, but this was good enough for my purposes.
Linq should be used for queries only. Use it to query the source and build your anonymous types. Then use a foreach to add the DataRows. In general: you cannot use CopyToDataTable for anything else than IEnumerable<DataRow>. That's the reason for the error on the anonymous type.
( There is a workaround using reflection that i wouldn't suggest in general:
Implement CopyToDataTable<T> Where the Generic Type T Is Not a DataRow )
Imho a foreach is the best you can do since it's very readable:
var query =
from dt1 in DataTable1.AsEnumerable()
join dt2 in DataTable2.AsEnumerable()
on dt1.Field<string>("col1D") equals dt2.Field<string>("col2A")
select new
{
col1A = dt1.Field<int>("col1A"),
col1B = dt1.Field<string>("col1B"),
col1C = dt1.Field<string>("col1C"),
col2B = dt2.Field<string>("col2B")
};
foreach (var x in query)
{
DataRow newRow = DataTable3.Rows.Add();
newRow.SetField("col1A", x.col1A);
newRow.SetField("col1B", x.col1B);
newRow.SetField("col1C", x.col1C);
newRow.SetField("col2B", x.col2B);
}
I believe you can utilize the CopyToDataTable as follows:
dt1.AsEnumerable()
.Join(dt2.AsEnumerable(),
d => d.Field<string>("col1D"),
d => d.Field<string>("col2A"),
(d1, d2) => new { Dt1 = d1, Dt2 = d2 })
.Select(a => {
DataRow newRow = dt3.NewRow();
newRow.SetField("colA", a.Dt1.Field<int>("col1A"));
newRow.SetField("colB", a.Dt1.Field<string>("col1B"));
newRow.SetField("colC", a.Dt1.Field<string>("col1C"));
newRow.SetField("colD", a.Dt2.Field<string>("col2B"));
return newRow;
})
.CopyToDataTable(dt3, LoadOption.OverwriteChanges);
Please note that this is performing an Inner Join rather than a Left Join as you requested.
If you really want a Left Join I think the following meets your requirements:
dt1.AsEnumerable()
.GroupJoin(dt2.AsEnumerable(),
d1 => d1.Field<int>("col1D"),
d2 => d2.Field<int>("col2A"),
(l, r) => new { Left = l, Right = r })
.Select(anon => {
if(anon.Right.Any())
{
return anon.Right.Select(r =>
{
var newRow = dt3.NewRow();
newRow.SetField("colA", anon.Left.Field<int>("col1A"));
newRow.SetField("colB", anon.Left.Field<string>("col1B"));
newRow.SetField("colC", anon.Left.Field<string>("col1C"));
newRow.SetField("colD", r.Field<string>("col2B"));
return newRow;
});
}
else
{
var newRow = dt3.NewRow();
newRow.SetField("colA", anon.Left.Field<int>("col1A"));
newRow.SetField("colB", anon.Left.Field<string>("col1B"));
newRow.SetField("colC", anon.Left.Field<string>("col1C"));
return new DataRow[] { newRow };
}
})
.Aggregate((accum, next) => accum.Union(next))
.CopyToDataTable(dt3, LoadOption.OverwriteChanges);

LINQ query returning no rows

I'm new to LINQ, so I'm sure there's an error in my logic below.
I have a list of objects:
class Characteristic
{
public string Name { get; set; }
public string Value { get; set; }
public bool IsIncluded { get; set; }
}
Using each object in the list, I want to build a query in LINQ that starts with a DataTable, and filters it based on the object values, and yields a DataTable as the result.
My Code so far:
DataTable table = MyTable;
// Also tried: DataTable table = MyTable.Clone();
foreach (Characteristic c in characteristics)
{
if (c.IsIncluded)
{
var q = (from r in table.AsEnumerable()
where r.Field<string>(c.Name) == c.Value
select r);
table = rows.CopyToDataTable();
}
else
{
var q = (from r in table.AsEnumerable()
where r.Field<string>(c.Name) != c.Value
select r);
table = q.CopyToDataTable();
}
}
UPDATE
I was in a panicked hurry and I made a mistake; my DataTable was not empty, I just forgot to bind it to the DataGrid. But also, Henk Holterman pointed out that I was overwriting my result set each iteration, which was a logic error.
Henk's code seems to work the best so far, but I need to do more testing.
Spinon's answer also helped bring clarity to my mind, but his code gave me an error.
I need to try to understand Timwi's code better, but in it's current form, it did not work for me.
NEW CODE
DataTable table = new DataTable();
foreach (Characteristic c in characteristics)
{
EnumerableRowCollection<DataRow> rows = null;
if (c.IsIncluded)
{
rows = (from r in MyTable.AsEnumerable()
where r.Field<string>(c.Name) == c.Value
select r);
}
else
{
rows = (from r in MyTable.AsEnumerable()
where r.Field<string>(c.Name) != c.Value
select r);
}
table.Merge(rows.CopyToDataTable());
}
dataGrid.DataContext = table;
The logic in your posting is wonky; here is my attempt of what I think you are trying to achieve.
DataTable table = MyTable.AsEnumerable()
.Where(r => characteristics.All(c => !c.IsIncluded ||
r.Field<string>(c.Name) == c.Value))
.CopyToDataTable();
If you actually want to use the logic in your posting, change || to ^, but that seems to make little sense.
You overwrite the table variable for each characteristic, so in the end it only holds the results from the last round, and that that apparently is empty.
What you could do is something like:
// untested
var t = q.CopyToDataTable();
table.Merge(t);
And I suspect your query should use MyTable as the source:
var q = (from r in MyTable.AsEnumerable() ...
But that's not entirely clear.
If you are trying to just insert the rows into your table then try calling the CopyToDataTable method this way:
q.CopyToDataTable(table, LoadOption.PreserveChanges);
This way rather than reassigning the table variable you can just update it with the new rows that are to be inserted.
EDIT: Here is an example of what I was talking about:
DataTable table = new DataTable();
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Value", typeof(string));

Categories

Resources