How to get data as IEnumerable<MODEL> from DataTable by using LINQ? - c#

I have a DataTable with some attributes data get from a database, each data in that DataTable belong to a product, and each product could have more than one attribute.
So i have another DataTable which has all the products in a foreach by looping through each row i'm adding each product to it's List<Plu> like this:
var productAttr = new List<Plu.Attributi>();
foreach (DataRow rowPlu in dt.Rows)
{
try
{
int id = (int)rowPlu["ID_PLUREP"];
plu.Add(new Plu(
id,
(string)rowPlu["CODICE_PRP"],
(string)rowPlu[ESTESA],
(string)rowPlu[DESCR], (float)rowPlu["PRE_PRP"],
rowPlu.IsNull("IMG_IMG") ? null : (string)rowPlu["IMG_IMG"],
productAttr,
null,
(int)rowPlu["ID_MENU_PRP"]
));
}
catch
{
return plu;
}
}
For now the productAttr is empty but now i need to add to each product it's attributes, so with the following function i get a DataTable filled with data from database with all product attributes:
var attributi = Attributi(connection, idNegozio);
and then i was trying to do something like this inside the foreach
foreach (DataRow rowPlu in dt.Rows)
{
try
{
int id = (int)rowPlu["ID_PLUREP"];
plu.Add(new Plu(
id,
(string)rowPlu["CODICE_PRP"],
(string)rowPlu[ESTESA],
(string)rowPlu[DESCR], (float)rowPlu["PRE_PRP"],
rowPlu.IsNull("IMG_IMG") ? null : (string)rowPlu["IMG_IMG"],
from row in attributi.AsEnumerable() where row.Field<int>("ID_PLUREP_VAT") == id select row,
null,
(int)rowPlu["ID_MENU_PRP"]
));
}
catch
{
return plu;
}
}
But the LINQ returns a EnumerableRowCollection while i need a IEnumerable<Plu.Attribute>, so i was wondering if there is a lazy way to cast the .AsEnumerable to IEnumerable<Plu.Attrbute>...

The problem is, that the DataTable only knows which values are in the cells. It does not know what these values stand for. It does not know that the number in column 0 is in fact an Id. It doesn't know that the string in column 1 is the Name of a Customer, and the DateTime in column 2 is the Birthday of the Customer.
If you will be using the contents of this Datatable (or similar DataTables) for other queries in the future, you need some translation from DataRow to the items that they stand for.
Once you've got the translation from DataRow to Plu, you can convert your DataTable to an IEnumerable<Plu>, and do other LINQ processing on it.
Usage will be like:
DataTable table = ...
var mySelectedData = table.AsEnumerable().ToPlus()
.Where(plu => ...)
.Select(plu => new {...})
.ToList();
You need two extension methods: one that converts a DataRow to a Plu and one that converts a sequence of DataRows to a sequence of Plus. See extension methods demystified
public static Plu ToPlu(this DataRow row)
{
// TODO implement
}
public static IEnumerable<Plu> ToPlus(this IEnumerable<DataRow> dataRows)
{
// TODO: exception if null dataRows
return dataRows.Select(row => row.ToPlu());
}
If desired, create an extension method from DataTable to extract the Plus:
public static IEnumerable<Plu> ExtractPlus(this DataTable table)
{
// TODO: exception if table null
return table.AsEnumerable().ToPlus();
}
Usage:
DataTable table = ...
IEnumerable<Plu> plus = table.ExtractPlus();
I haven't got the faintest idea what a Plu is, and you forgot to mention the relevant properties of the Plu, so I'll give you an example of a table that contains Customers:
class Customer
{
public int Id {get; set;} // Id will be in column 0
public string Name {get; set;} // Name will be in column 1
...
}
public static Customer ToCustomer(this DataRow row)
{
return new Customer
{
Id = (int)row[0],
Name = (string)row[1],
};
}
If desired, instead of columnIndex you can use the name of the column.
So by only creating a ToPlu, and a one-liner method to convert sequences of DataRows to a sequence of Plus, you've extended LINQ with your methods to read your tables.
To be on the safe side, consider creating an extension method that converts a sequence of Plus to a DataTable. This way, the layout of the table is in one location: ToPlu(DataRow) and ToDataRow(Plu). Future changes in the table layout will be easier to manage, users of your DataTable will only think in sequences of Plus.

You can do something like below. If you want IEnumerable<Plu> you can remove the .ToList() from the end.
dt.AsEnumerable().Select(x => new Plu {
Id = x.Field<int>("ID_PLUREP"),
CodicePrep = x.Field<string>("CODICE_PRP"),
....
Attributes = attributi.AsEnumerable()
.Where(y => y.Field<int>("ID_PLUREP_VAT") == x.Field<int>("ID_PLUREP"))
.Select(z => new Attributi
{
....
}).ToList(),
....
}).ToList();

Related

Best way to remove duplicates from DataTable depending on column values

I have a DataSet which contains just one Table, so you could say I'm working with a DataTable here.
The code you see below works, but I want to have the best and most efficient way to perform the task because I work with some data here.
Basically, the data from the Table should later be in a Database, where the primary key - of course - must be unique.
The primary key of the data I work with is in a column called Computer Name. For each entry we also have a date in another column date.
I wrote a function which searches for duplicates in the Computer Name column, and then compare the dates of these duplicates to delete all but the newest.
The Function I wrote looks like this:
private void mergeduplicate(DataSet importedData)
{
Dictionary<String, List<DataRow>> systems = new Dictionary<String, List<DataRow>>();
DataSet importedDataCopy = importedData.Copy();
importedData.Tables[0].Clear();
foreach (DataRow dr in importedDataCopy.Tables[0].Rows)
{
String systemName = dr["Computer Name"].ToString();
if (!systems.ContainsKey(systemName))
{
systems.Add(systemName, new List<DataRow>());
}
systems[systemName].Add(dr);
}
foreach (KeyValuePair<String,List<DataRow>> entry in systems) {
if (entry.Value.Count > 1) {
int firstDataRowIndex = 0;
int secondDataRowIndex = 1;
while (entry.Value.Count > 1) {
DateTime time1 = Validation.ConvertStringIntoDateTime(entry.Value[firstDataRowIndex]["date"].ToString());
DateTime time2 = Validation.ConvertStringIntoDateTime(entry.Value[secondDataRowIndex]["date"].ToString());
//delete older entry
if (DateTime.Compare(time1,time2) >= 0) {
entry.Value.RemoveAt(firstDataRowIndex);
} else {
entry.Value.RemoveAt(secondDataRowIndex);
}
}
}
importedData.Tables[0].ImportRow(entry.Value[0]);
}
}
My Question is, since this code works - what is the best and fastest/most efficient way to perform the task?
I appreciate any answers!
I think this can be done more efficiently. You copy the DataSet once with DataSet importedDataCopy = importedData.Copy(); and then you copy it again into a dictionary and then you delete the unnecessary data from the dictionary. I would rather just remove the unnecessary information in one pass. What about something like this:
private void mergeduplicate(DataSet importedData)
{
Dictionary<String, DataRow> systems = new Dictionary<String, DataRow>();
int i = 0;
while (i < importedData.Tables[0].Rows.Count)
{
DataRow dr = importedData.Tables[0].Rows[i];
String systemName = dr["Computer Name"].ToString();
if (!systems.ContainsKey(systemName))
{
systems.Add(systemName, dr);
}
else
{
// Existing date is the date in the dictionary.
DateTime existing = Validation.ConvertStringIntoDateTime(systems[systemName]["date"].ToString());
// Candidate date is the date of the current DataRow.
DateTime candidate = Validation.ConvertStringIntoDateTime(dr["date"].ToString());
// If the candidate date is greater than the existing date then replace the existing DataRow
// with the candidate DataRow and delete the existing DataRow from the table.
if (DateTime.Compare(existing, candidate) < 0)
{
importedData.Tables[0].Rows.Remove(systems[systemName]);
systems[systemName] = dr;
}
else
{
importedData.Tables[0].Rows.Remove(dr);
}
}
i++;
}
}
maybe not the most efficient way but you said you appreciate any answers
List<DataRow> toDelete = dt.Rows.Cast<DataRow>()
.GroupBy(s => s["Computer Name"])
.SelectMany(grp => grp.OrderBy(x => x["date"])
.Skip(1)).ToList();
toDelete.ForEach(x => dt.Rows.Remove(x));
You could try to use CopyToDataTable, like this:
importedData.Tables[0] = importedData.Tables[0].AsEnumerable()
.GroupBy(r => new {CN = r["Computer Name"], Date = r["date"]})
.Select(g => g.OrderBy(r => r["Date"]).(First())
.CopyToDataTable();

Linq Query for querying a list of DataSet

I have a list of DataSet.
for example:
List<DataSet> list = new List<DataSet>();
For my task, the number of DataSet in the list and the number of DataTable in each DataSet will be known at the run time.
Now I want to get those tables from the DataSets that contains a certain string in their names, for instance say 'Group1'.
I am trying with the following code:
var ds= from set in list from table in set
where li.Where(e=>e.Tables.Contains("Group")) select table;
But i am getting the error as 'An expression of type System.Data.DataSet is not allowed in a subsequent from clause in a query expression with source typeList'.
Please help me with the correct approach.
I've tried to replicate your data structure by creating another class. Hope this helps.
namespace TestCode
{
class Program
{
static void Main(string[] args)
{
var list = new List<TC> {new TC(2), new TC(2), new TC(3), new TC(4), new TC(5), new TC(2)};
var dt = list.Where( // Contains 3 elements
x => x.X == 2
);
//var ds = from set in list
// from table in set
// where li.Where(e => e.Tables.Contains("Group"))
// select table;
}
}
internal class TC
{
public int X { get; set; }
internal TC(int val)
{
X = val;
}
}
}
You original query is close. It just needs to be fleshed out a bit. First off it helps to declare the type in the from statement. Also specify you want the table collection from the set. The where clause should just need to examine the TableName property of the tables:
List<DataSet> list = new List<DataSet>();
var ds = from DataSet set in list
from DataTable table in set.Tables
where table.TableName.Contains("Group")
select table;
This gets the tables with the contained name:
var tables = list.SelectMany(x => x.Tables.Cast<DataTable>())
.Where(x => x.TableName.Contains("Group"));

dynamic datatable sorting in ascending or descending

I have created dynamic table
DataTable date = new DataTable();
date.Columns.Add("date1");
and made fill the column name "date1" with date as
date1(Column name)
05-07-2013
10-07-2013
09-07-2013
02-07-2013
and made fill my dynamic table
Now i want this dynamic table data to be sort as ascending or descending order
For eg:
date1(Column name)
02-07-2013
05-07-2013
09-07-2013
10-07-2013
This cannot be done with the original data table. However you can create a new, sorted one:
DataView view = date.DefaultView;
view.Sort = "date1 ASC";
DataTable sortedDate = view.ToTable();
You can use DataTable.Select(filterExpression, sortExpression) method.
Gets an array of all DataRow objects that match the filter criteria,
in the specified sort order.
date.Select("", "YourColumn ASC");
or
date.Select("", "YourColumn DESC");
As an alternative, you can use DataView like;
DataView view = date.DefaultView;
view.Sort = "YourColumn ASC";
DataTable dt = view.ToTable();
Thought I would give in my two cents here. Instead of using a sorting algorithm which takes time and computational performance, why not instead reverse the way in which you are adding data to your data object.
This won't work for everyone's scenario - but for my own it worked out perfectly.
I had a database which listed items in an ascending order, but or ease of use I needed to reverse the way in which people could see the data (DESC) so that the newest input shows at the top, rather then the bottom of the list.
So, I just changed my for loop so instead of working from 0 -> upwards, it started from the length of the datatable (-1 to stop an overflow) and then stops when it is >= to 0;
private Dictionary<string, string> GetComboData(string table, int column, bool id, int idField = 0)
{
SqlClass sql = new SqlClass(database);
Dictionary<string, string> comboBoxData = new Dictionary<string, string>();
if (sql.connectedToServer)
{
sql.SelectResults(SQLCommands.Commands.SelectAll(table));
for (int i = sql.table.Rows.Count-1; i >= 0; i--)
{
string tool = sql.table.Rows[i].ItemArray.Select(x => x.ToString()).ToArray()[column];
string ID = sql.table.Rows[i].ItemArray.Select(x => x.ToString()).ToArray()[idField];
comboBoxData.Add(ID, tool);
}
}
return comboBoxData;
}
using OrderByDescending()
#foreach (var rca in Model.OrderByDescending(x=>x.Id))
{
<tr class="heading">
<td>#rca.PBINo</td>
<td>#rca.Title</td>
<td>#rca.Introduction</td>
<td>#rca.CustomerImpact</td>
<td>#rca.RootCauseAnalysis</td>
</tr>
}

Get specific value from datatable with where-clause

I want to query a specific value from a DataTable.
Lets say i have a DataTable which contains 2 columns:
id
item_name
Now what I want to do is like i would do it with mysql: SELECT * FROM "DataTable" WHERE item_name = 'MyItemName'
And then get the id that belongs to that 'item_name'...
int blah;
while (MyReader.Read())
{
blah = MyReader.GetInt32("id");
}
Now: how can I do this using DataTable?
I've got a snippet but I can't seem to show the returned value in a messagebox:
string test = Item1txt.Text;
var query = producten.Rows.Cast<DataRow>().Where(x => x.Field<string>("item_name") == test);
foreach (var st in query)
{
MessageBox.Show(st.ToString());
// how can i show the id that belongs to "test" ?
}
query will be an IQueryable<DataRow>, so st will be a DataRow. Try this:
foreach (var st in query)
{
MessageBox.Show(st.Field<int>("id").ToString());
}
Or if you know there will only item with that item_name, here's an alternative version which does essentially the same thing, but is probably a bit easier to understand:
var st = producten.Rows.Cast<DataRow>().FirstOrDefault(x => x.Field<string>("item_name") == test);
if(item != null)
{
MessageBox.Show(st.Field<int>("id").ToString());
}
You can use linq directly on the datatable without the need of Rows or the Cast.
var query = producten.AsEnumerable().Where(x => x.Field<string>("item_name") == test);
foreach (var st in query)
{
MessageBox.Show(st.Field<int>("id"));
}
I usually use the Rowfilter property of the defaultview of the datatable, but I must admit I never did LINQ myself, so there's probably a better way now...

How to get what exist in the first datatable and not exist in the second data table in a third one?

Q:
I have two queries each one return a DataTable. I wanna to return another DaTaTable as a result of(What exist in the first DataTable AND Not Exist(NOT IN) the second DataTable).
My queries:
EDIT : I make it general:
1-DT1:
DataTable dt1 = cc1assiscrsevalDAL.GetAll(int.Parse(Session["course_prof"].ToString()), 0);
2-DT2:
DataTable dt2 = cc1assiscrsevalDAL.GetConfirmedEval(int.Parse(Session["course_prof"].ToString()));
Note:batch_no,crsnum,lect_code are the composite primary key
What is the best way to do that?(wise performance).
I wanna also to do that with LINQ.(if possible).
var dt = dt1.AsEnumerable().Except(dt2.AsEnumerable(), new CustomDataRowEqualityComparer()).CopyToDataTable();
public class CustomDataRowEqualityComparer: IEqualityComparer<DataRow>
{
public bool Equals(DataRow x, DataRow y)
{
return ((int)x["crsnum"]) == ((int)y["crsnum"])
&& ((int)x["crsnum_e"]) == ((int)y["crsnum_e"])
&& ((int)x["crstteng"]) == ((int)y["crstteng"]);
}
public int GetHashCode(DataRow obj)
{
return ((int)obj["crsnum"]) ^ ((int)obj["crsnum_e"]) ^ ((int)obj["crstteng"]) ;
}
}
There is an extension method in the linq called Except which solves your problem but we need to create a separate class for that which i have done in the above code.
select *
from (*target_query*) t
join
(
select batch_no,crsnum,lect_code from (*target_query*) q
except
select batch_no,crsnum,lect_code from cc1assiscrseval
) temp on temp.batch_no = t.batch_no and temp.lect_code = t.lect_code, temp.crsnum = t.crsnum
Pretty dirty solution, but I think you could simplify it by getting only desired batch_no, crsnum and lect_code, without performing first query twice. But you'll have to figure it out yourself.
Linq provides you with an Except method so you could do something like this
var _differences = dt1.AsEnumerable.Except(dt2.AsEnumerable()); // No checked or tested in VS
You could also first get the relevant columns by using something like this:
var x = From a In dt1
Select (...relevant columns)
var y = From a In dt2
Select (...relevant columns)
And then do the above except.
HTH!

Categories

Resources