I have a DataTable that is filled by a Stored Procedure, and from that datatable which contains a collection of Requests(RequestNumber and Tasks(TaskId). When I have reach the first Request number with a Task, I add it to my list, then with additional datarows, I check the list to see if they exist(if(dr["RequestNumber"].ToString() != acList[i].RequestNumber)) if they do, I delete the dataRow, if not I add them to the list.
This works good in sequential order, but if the datarow and list are off by one it allows the row to be added. Is there any other way to accomplish finding if the value exists in the list.
Thanks in advance.
foreach (DataRow dRow in dt.Rows)
{
DataRow dr = dt.NewRow();
dr["Project"] = dRow["Project"];
dr["RequestNumber"] = dRow["RequestNumber"];
dr["RequestId"] = dRow["RequestId"];
dr["TaskType"] = dRow["TaskType"];
dr["TaskId"] = dRow["TaskId"];
dr["TaskStatus"] = dRow["TaskStatus"];
dr["AssignedTo"] = dRow["AssignedTo"];
dr["DateDue"] = dRow["DateDue"];
if (acList.Count == 0)
{
acList.Add(new AssignedClass
{
Project = dr["Project"].ToString(),
RequestNumber = dr["RequestNumber"].ToString(),
RequestId = dr["RequestId"].ToString(),
TaskType = dr["TaskType"].ToString(),
TaskId = dr["TaskId"].ToString(),
TaskStatus = dr["TaskStatus"].ToString(),
AssignedTo = dr["AssignedTo"].ToString(),
DateDue = dr["DateDue"].ToString()
});
}
else
{
for (int i = 0; i < acList.Count; i++)
{
if(dr["RequestNumber"].ToString() != acList[i].RequestNumber)
{
acList.Add(new AssignedClass
{
Project = dr["Project"].ToString(),
RequestNumber = dr["RequestNumber"].ToString(),
RequestId = dr["RequestId"].ToString(),
TaskType = dr["TaskType"].ToString(),
TaskId = dr["TaskId"].ToString(),
TaskStatus = dr["TaskStatus"].ToString(),
AssignedTo = dr["AssignedTo"].ToString(),
DateDue = dr["DateDue"].ToString()
});
}
else
{
dr.Delete();
}
}
}
Using LINQ, it's as simple as checking if there are any matches:
if ( !acList.Any(a => a.RequestNumber == dr["RequestNumber"].ToString() )
acList.Add( ... );
Also, it seems that the code at the beginning assigning dRow to dr has no purpose. Just use dRow directly throughout the rest of your code. And I don't think you want to treat (acList.Count == 0) as a special case, because that just causes you to have to duplicate your logic and thus maintain two separate copies of the same code. So if I understood everything correctly, this simplified code should accomplish the same thing:
foreach (DataRow dRow in dt.Rows)
{
if ( !acList.Any(a => a.RequestNumber == dRow["RequestNumber"].ToString() )
{
acList.Add(new AssignedClass
{
Project = dRow["Project"].ToString(),
RequestNumber = dRow["RequestNumber"].ToString(),
RequestId = dRow["RequestId"].ToString(),
TaskType = dRow["TaskType"].ToString(),
TaskId = dRow["TaskId"].ToString(),
TaskStatus = dRow["TaskStatus"].ToString(),
AssignedTo = dRow["AssignedTo"].ToString(),
DateDue = dRow["DateDue"].ToString()
});
}
}
This would be a great job for LINQ's Union method, but it requires an IEqualityComparer<AssignedClass> implementation. Unless you do this often, it's probably not worth coding (even though it's 10-ish lines if done properly). This would help, however:
acList = acList
.Concat(from row in dt.Rows
from ac in acList
where ac.RequestNumber != row["RequestNumber"].ToString()
select AssignedClassFromDataRow(row))
.ToList();
where
private static AssignedClass AssignedClassFromDataRow(DataRow row)
{
// maybe some checks...
return new AssignedClass
{
Project = dRow["Project"].ToString(),
RequestNumber = dRow["RequestNumber"].ToString(),
RequestId = dRow["RequestId"].ToString(),
TaskType = dRow["TaskType"].ToString(),
TaskId = dRow["TaskId"].ToString(),
TaskStatus = dRow["TaskStatus"].ToString(),
AssignedTo = dRow["AssignedTo"].ToString(),
DateDue = dRow["DateDue"].ToString()
}
}
Slightly more time complex than a hash-based solution, but simple enough to implement.
EDIT:
If you actually need the extra performance provided by hashing, you can write the EqualityComparer (but keep in mind these guidelines). Such solution would look like this in the end:
acList = acList
.Union(
dt.Rows.Select(AssignedClassFromDataRow),
new MyAssignedClassRequestNumberComparer())
.ToList();
You can use HashSet<AssignedClass>, all you need is to create custom IEqualityComarer<AssignedClass> in which you check RequestNumber property of passed objects, and pass instance of this comparer in constructor of HashSet
Edit
Here is possible implementation of IEqualityComarer<AssignedClass> :
public class AssignedClassComparer : IEqualityComparer<AssignedClass>
{
public bool Equals(AssignedClass x, AssignedClass y)
{
return x.RequestNumber == y.RequestNumber;
}
public int GetHashCode(AssignedClass obj)
{
return obj.RequestNumber.GetHashCode();
}
}
EDIT2:
Or you can simply use HashSet to store only keys, while enumerating through rows:
var keys = new HashSet<string>();
foreach (DataRow dRow in dt.Rows)
{
if (keys.Add(dRow["RequestNumber"].ToString()))
{
acList.Add(new AssignedClass
{
Project = dRow["Project"].ToString(),
RequestNumber = dRow["RequestNumber"].ToString(),
RequestId = dRow["RequestId"].ToString(),
TaskType = dRow["TaskType"].ToString(),
TaskId = dRow["TaskId"].ToString(),
TaskStatus = dRow["TaskStatus"].ToString(),
AssignedTo = dRow["AssignedTo"].ToString(),
DateDue = dRow["DateDue"].ToString()
});
}
}
With the option of linq and taking into account that the beginning code block and the check for 0 entries seem a bit redundant. I think the process could boil down to
var distinctRows = dt.Rows.GroupBy(x => x["RequestNumber"]).Select(x => x.First());
acList.AddRange(distinctRows.Select(x => x.MapToAssignedClass());
// Added Mapping method for readability
public static AssignedClass MapToAssignedClass(this DataRow dr)
{
return new AssignedClass
{
Project = dr["Project"].ToString(),
RequestNumber = dr["RequestNumber"].ToString(),
RequestId = dr["RequestId"].ToString(),
TaskType = dr["TaskType"].ToString(),
TaskId = dr["TaskId"].ToString(),
TaskStatus = dr["TaskStatus"].ToString(),
AssignedTo = dr["AssignedTo"].ToString(),
DateDue = dr["DateDue"].ToString()
});
}
Related
I'm new to CS. I have a ListBox control that I populate from an SQL table called Category. I have a class called Category to match the fields from the DB. I want all my fields available to edit and save. The ListBox has a single field, CategoryDesc. When I select an item in the ListBox I want two textboxes and a check box to update with the CategoryID (string), CategoryDesc (string), and IsActive (bool). I have it working but it seems cumbersome and like I'm taking a lot of steps. I want to learn efficient coding so I'm submitting the following for suggestions on how to clean it up and make it more efficient. Any positive comments will be greatly appreciated.
id ListControl()
{
this.LstCategory.SelectedIndexChanged -= new System.EventHandler(this.LstCategory_SelectedIndexChanged);
DataTable categoryDt = new DataTable();
categoryDt = GetDataTable("GetListCategory");
for (int i = 0; i < categoryDt.Rows.Count; i++)
{
category.Add(new Category()
{
CategoryID = (int)(categoryDt.Rows[i]["CategoryId"]),
CategoryDesc = (string)(categoryDt.Rows[i]["CategoryDesc"]),
ShortCode = (string)(categoryDt.Rows[i]["ShortCode"]),
IsActive = (bool)(categoryDt.Rows[i]["IsActive"]),
CanDelete = (bool)(categoryDt.Rows[i]["CanDelete"])
});
LstCategory.Items.Add((string)(categoryDt.Rows[i]["CategoryDesc"]));
}
this.LstCategory.SelectedIndexChanged += new System.EventHandler(this.LstCategory_SelectedIndexChanged);
}
private void LstCategory_SelectedIndexChanged(object sender, EventArgs e)
{
if (LstCategory.SelectedIndex >= 0)
{
string desc = LstCategory.SelectedItem.ToString();
foreach (var c in category)
{
if (c.CategoryDesc == desc)
{
TxtDescription.Text = c.CategoryDesc;
TxtShortCode.Text = c.ShortCode;
ChkIsActive.Checked = c.IsActive;
}
}
}
else
{
TxtDescription.Text = string.Empty;
TxtShortCode.Text = string.Empty;
ChkIsActive.Checked = false;
}
}
Thanks.
Learn to use Linq
This
categoryDt = GetDataTable("GetListCategory");
for (int i = 0; i < categoryDt.Rows.Count; i++)
{
category.Add(new Category()
{
CategoryID = (int)(categoryDt.Rows[i]["CategoryId"]),
CategoryDesc = (string)(categoryDt.Rows[i]["CategoryDesc"]),
ShortCode = (string)(categoryDt.Rows[i]["ShortCode"]),
IsActive = (bool)(categoryDt.Rows[i]["IsActive"]),
CanDelete = (bool)(categoryDt.Rows[i]["CanDelete"])
});
LstCategory.Items.Add((string)(categoryDt.Rows[i]["CategoryDesc"]));
}
can be replaced by
category = categoryDt.Select(cd => new Category{
CategoryID = (int)(cd["CategoryId"]),
CategoryDesc = (string)(cd[i]["CategoryDesc"]),
ShortCode = (string)(cd["ShortCode"]),
IsActive = (bool)(cd[i]["IsActive"]),
CanDelete = (bool)(cd[i]["CanDelete"])}).ToList();
LstCategory.Items.AddRange(category.Select(c=>c.Desc));
and
string desc = LstCategory.SelectedItem.ToString();
foreach (var c in category)
{
if (c.CategoryDesc == desc)
{
TxtDescription.Text = c.CategoryDesc;
TxtShortCode.Text = c.ShortCode;
ChkIsActive.Checked = c.IsActive;
}
}
can be replaced by
var c = category.FirstOrDefault(c=>c ==desc);
TxtDescription.Text = c.CategoryDesc;
TxtShortCode.Text = c.ShortCode;
ChkIsActive.Checked = c.IsActive;
There might be a few typos here and there becuase I dont have yur data structures to try it out on.
But LINQ is incredibly useful for performing operations on collections.
'select' is used to transform 'project' (its not a filter)
'where' is used to filter
'FindFirstOrDefault' will retunr the first match (or null)
'Count' counts
'ToList' converts to list
The nice thing is you can chain then together
mylist.Where(...).Select(..) etc
This code is working but taking too much time. Every data table contains 1000nds of rows and each time I need to filter data from another data tables with respect to a column.
for (int i = 0; i < dsResult.Tables[0].Rows.Count; i++)
{
DataTable dtFiltered = dtWorkExp.Clone();
foreach (DataRow drr in dtWorkExp.Rows)
{
if (drr["UserId"].ToString() == dsResult.Tables[0].Rows[i]["Registration NO."].ToString())
{
dtFiltered.ImportRow(drr);
}
}
DataTable dtFilteredAward= dtAwards.Clone();
foreach (DataRow drr in dtAwards.Rows)
{
if (drr["UserId"].ToString() == dsResult.Tables[0].Rows[i]["Registration NO."].ToString())
{
dtFilteredAward.ImportRow(drr);
}
}
DataTable dtFilteredOtherQual = dtOtherQual.Clone();
foreach (DataRow drr in dtOtherQual.Rows)
{
if (drr["UserId"].ToString() == dsResult.Tables[0].Rows[i]["Registration NO."].ToString())
{
dtFilteredOtherQual.ImportRow(drr);
}
}
//Do some operation with filtered Data Tables
}
You can declare these lines outside the for loop.
DataTable dtFiltered = dtWorkExp.Clone();
And instead of doing accessing dsResult.Table[0] each time, you can assign this to one variable and use it.
You can also replace the foreach loop with LINQ.
What I would do:
All rows of the main datatable as enumerable:
var rows = dsResult.Tables[0].AsEnumerable();
Get the column you're going to filter with:
var filter = rows.Select(r => r.Field<string>("Registration NO."));
Create a method that accepts that filter, a table to filter and a field to compare.
public static DataTable Filter<T>(EnumerableRowCollection<T> filter, DataTable table, string fieldName)
{
return table.AsEnumerable().Where(r => filter.Contains(r.Field<T>(fieldName))).CopyToDataTable();
}
Finally use the method to filter all tables:
var dtFiltered = Filter<string>(filter, dtWorkExp, "UserId");
var dtFilteredAward = Filter<string>(filter, dtAwards, "UserId");
var dtFilteredOtherQual = Filter<string>(filter, dtOtherQual, "UserId");
All together woul be something like this
public void YourMethod()
{
var rows = dsResult.Tables[0].AsEnumerable();
var filter = rows.Select(r => r.Field<string>("Registration NO."));
var dtFiltered = Filter<string>(filter, dtWorkExp, "UserId");
var dtFilteredAward = Filter<string>(filter, dtAwards, "UserId");
var dtFilteredOtherQual = Filter<string>(filter, dtOtherQual, "UserId");
}
public static DataTable Filter<T>(EnumerableRowCollection<T> filter, DataTable table, string fieldName)
{
return table.AsEnumerable().Where(r => filter.Contains(r.Field<T>(fieldName))).CopyToDataTable();
}
Put the value of the expression in a variable.
var regNo = dsResult.Tables[0].Rows[i]["Registration NO."].ToString();
Put the index of column to the variable. Access by index more faster then by column name.
int index = dtWorkExp.Columns["UserId"].Ordinal;
Result code:
int dtWorkIndex = dtWorkExp.Columns["UserId"].Ordinal;
int dtAwardsIndex = dtAwards.Columns["UserId"].Ordinal;
int dtOtherQualIdex = dtOtherQual.Columns["UserId"].Ordinal;
for (int i = 0; i < dsResult.Tables[0].Rows.Count; i++)
{
var regNo = dsResult.Tables[0].Rows[i]["Registration NO."].ToString();
DataTable dtFiltered = dtWorkExp.Clone();
foreach (DataRow drr in dtWorkExp.Rows)
{
if (drr[dtWorkIndex].ToString() == regNo)
{
dtFiltered.ImportRow(drr);
}
}
...
Of course, the column index can be set as a constant if you know it exactly in advance. Also, if the UserId indexes match in all tables, a single variable is sufficient.
You can also try using the BeginLoadData and EndLoadData methods.
DataTable dtFiltered = dtWorkExp.Clone();
dtFiltered.BeginLoadData();
foreach (DataRow drr in dtWorkExp.Rows)
{
if (drr[dtWorkIndex].ToString() == regNo)
{
dtFiltered.ImportRow(drr);
}
}
dtFiltered.EndLoadData();
But I'm not sure if they make sense together with ImportRow.
Finally, parallelization comes to help.
for (int i = 0; i < dsResult.Tables[0].Rows.Count; i++)
{
var regNo = ...;
var workTask = Task.Run(() =>
{
DataTable dtFiltered = dtWorkExp.Clone();
foreach (DataRow drr in dtWorkExp.Rows)
{
if (drr[dtWorkIndex].ToString() == regNo)
{
dtFiltered.ImportRow(drr);
}
}
return dtFiltered;
});
var awardTask = Task.Run(() =>
...
var otherQualTask = Task.Run(() =>
...
//Task.WaitAll(workTask, awardTask, otherQualTask);
await Task.WhenAll(workTask, awardTask, otherQualTask);
//Do some operation with filtered Data Tables
}
I need some help to calculate a property inside my Linq query.
I know I need to use "let" somewhere, but I can't figure it out!
So, first I have this method to get my list from Database:
public BindingList<Builders> GetListBuilders()
{
BindingList<Builders> builderList = new BindingList<Builders>();
var ctx = new IWMJEntities();
var query = (from l in ctx.tblBuilders
select new Builders
{
ID = l.BuilderID,
Projeto = l.NomeProjeto,
Status = l.Status,
DataPedido = l.DataPedido,
DataPendente = l.DataPendente,
DataEntregue = l.DataEntregue,
DataAnulado = l.DataAnulado
});
foreach (var list in query)
builderList.Add(list);
return builderList;
}
Then, I have a function to calculate the Days between Dates accordingly to Status:
public int GetDays()
{
int Dias = 0;
foreach (var record in GetListBuilders)
{
if (record.Status == "Recebido")
{
Dias = GetBusinessDays(record.DataPedido, DateTime.Now);
}
else if (record.Status == "Pendente")
{
Dias = GetBusinessDays(record.DataPedido, (DateTime)record.DataPendente);
}
else if (record.Status == "Entregue")
{
Dias = GetBusinessDays(record.DataPedido, (DateTime)record.DataEntregue);
}
else if (record.Status == "Anulado")
{
Dias = GetBusinessDays(record.DataPedido, (DateTime)record.DataAnulado);
}
}
return Dias;
}
I need to call the GetDays in a DataGridView to give the days for each record.
My big problem is, How do I get this? include it in Linq Query? Calling GetDays() (need to pass the ID from each record to GetDays() function)!?
Any help?
Thanks
I think it would be easier to create an extension method:
public static int GetBusinessDays(this Builders builder) // or type of ctx.tblBuilders if not the same
{
if (builder == null) return 0;
switch(builder.status)
{
case "Recebido": return GetBusinessDays(builder.DataPedido, DateTime.Now);
case "Pendente": return GetBusinessDays(builder.DataPedido, (DateTime)builder.DataPendente);
case "Entregue": return GetBusinessDays(builder.DataPedido, (DateTime)builder.DataEntregue);
case "Anulado": GetBusinessDays(builder.DataPedido, (DateTime)builder.DataAnulado);
default: return 0;
}
}
Then, call it like that:
public BindingList<Builders> GetListBuilders()
{
BindingList<Builders> builderList = new BindingList<Builders>();
var ctx = new IWMJEntities();
var query = (from l in ctx.tblBuilders
select new Builders
{
ID = l.BuilderID,
Projeto = l.NomeProjeto,
Status = l.Status,
DataPedido = l.DataPedido,
DataPendente = l.DataPendente,
DataEntregue = l.DataEntregue,
DataAnulado = l.DataAnulado,
Dias = l.GetBusinessDays()
});
foreach (var list in query)
builderList.Add(list);
return builderList;
}
To do better, to convert a object to a new one, you should create a mapper.
Why does it need to be a part of the query? You can't execute C# code on the database. If you want the calculation to be done at the DB you could create a view.
You're query is executed as soon as the IQueryable is enumerated at the foreach loop. Why not just perform the calculation on each item as they are enumerated and set the property when you are adding each item to the list?
I can't work out how to get the Type of a LINQ result. My set contains both strings and bools, so I run into trouble when I try to act on the rows. I attached an incredibly rough workout using try/catch (for a laugh), but it hurts my soul and would much rather know the proper method in obtaining the Type.
private AppointmentInfoClass UpdateDataContext(DataSet phaseDataSet) {
var phaseCollection = new AppointmentInfoClass();
var Type = phaseCollection.GetType();
var properties = Type.GetProperties();
var result = from DataRow myRow in DataBindings.CompanyAppsDataSet.Tables[0].Rows
where (int)myRow["AppointmentID"] == ApptID
select myRow;
var k = 0;
foreach (DataRow row in phaseDataSet.Tables[0].Rows) {
string header;
header = row.Field<string>("Header");
foreach (var field in result) {
try {
properties[k].SetValue(phaseCollection, field.Field<string>(header));
}
catch (Exception) {
properties[k].SetValue(phaseCollection, field.Field<bool>(header).ToString());
}
}
k++;
}
return phaseCollection;
}
It will return the type you have written instead of Type
string s = field.Field<string>("ColumnName");
bool b = field.Field<bool>("ColumnName");
How to convert datatable into list of generic type. Below is the scenario.
I have datatable with name table1 and contains columns col1,col2. how could we convert this table into a list of type name table1bj(which can be different per the datatable name) with properties col1 and col2 with compatible datatype as of datatable column data types.
There are many post on SO but these are with the converting datatable into predefined object list. Here in my case I have to generate object and list dynamically from the datatable. Thanks.
Assuming that you've already created the class table1bj (consider to make it uppercase due to .NET naming conventions) with two properties col1,col2 (the same). You just have to use Enumerable.Select to create instances of this class and ToList to create a generic List<table1bj>:
List<table1bj> result = table1.AsEnumerable()
.Select(row => new table1bj
{
col1 = row.Field<string>("col1"),
col1 = row.Field<string>("col1")
}
).ToList();
I have also presumed that these properties are strings, otherwise use the correct type with the Field extension method. If you don't know the type you should stay with your DataTable since it's already an in-memory collection with dynamic types.
You can do like this...
Create Class with properties :
public class table1bj
{
public string col1{ get; set; }
public string col2{ get; set; }
}
Convert DataTable to Generic Type :
List<table1bj> Objtable1bj = table1.ToCollection<table1bj>();
I know this question asked many times ago, but also I need a solutions for convert data table to dynamic or generic types in one method and I can't find answer for this, so post my answer.
You can use a extension method to convert data table to any type like below:
public static class Extension
{
public static IList<T> ToList<T>(this DataTable dt, bool isFirstRowColumnsHeader = false) where T : new()
{
var results = new List<T>();
if (dt != null && dt.Rows.Count > 0)
{
var columns = dt.Columns.Cast<DataColumn>().ToList();
var rows = dt.Rows.Cast<DataRow>().ToList();
var headerNames = columns.Select(col => col.ColumnName).ToList();
//
// Find properties name or columns name
if (isFirstRowColumnsHeader)
{
for (var i = 0; i < headerNames.Count; i++)
{
if (rows[0][i] != DBNull.Value && !string.IsNullOrEmpty(rows[0][i].ToString()))
headerNames[i] = rows[0][i].ToString();
}
//
// remove first row because that is header
rows.RemoveAt(0);
}
// Create dynamic or anonymous object for `T type
if (typeof(T) == typeof(System.Dynamic.ExpandoObject) ||
typeof(T) == typeof(System.Dynamic.DynamicObject) ||
typeof(T) == typeof(System.Object))
{
var dynamicDt = new List<dynamic>();
foreach (var row in rows)
{
dynamic dyn = new ExpandoObject();
dynamicDt.Add(dyn);
for (var i = 0; i < columns.Count; i++)
{
var dic = (IDictionary<string, object>)dyn;
dic[headerNames[i]] = row[columns[i]];
}
}
return (dynamic)dynamicDt;
}
else // other types of `T
{
var properties = typeof(T).GetProperties();
if (columns.Any() && properties.Any())
{
foreach (var row in rows)
{
var entity = new T();
for (var i = 0; i < columns.Count; i++)
{
if (!row.IsNull(columns[i]))
{
typeof(T).GetProperty(headerNames[i])? // ? -> maybe the property by name `headerNames[i]` is not exist in entity then get null!
.SetValue(entity, row[columns[i]] == DBNull.Value ? null : row[columns[i]]);
}
}
results.Add(entity);
}
}
}
}
return results;
}
}
We can do it by Reflection also, this method helps to set ClassObject properties by DataTable:
using System.Reflection;
public void SetObjectProperties(object objClass, DataTable dataTable)
{
DataRow _dataRow = dataTable.Rows[0];
Type objType = objClass.GetType();
List<PropertyInfo> propertyList = new List<PropertyInfo>(objType.GetProperties());
foreach (DataColumn dc in _dataRow.Table.Columns)
{
var _prop = propertyList.Where(a => a.Name == dc.ColumnName).Select(a => a).FirstOrDefault();
if (_prop == null) continue;
_prop.SetValue(objClass, Convert.ChangeType(_dataRow[dc], Nullable.GetUnderlyingType(_prop.PropertyType) ?? _prop.PropertyType), null);
}
}