List Sorting and pattern-matching - c#

I'm trying to sort a list of telegramms to a List of Slaves.
If the PrimeAddress and the SecondaryAddress match, the telegrams belongs to the Slave.
The devices are stored in a Datatable.
I want to check if the deivce already contains the telegramm.
My first attempt looks something like this:
public static DataTable mdlform_NewMBUStele(int LoggerID, List<MbusTelegram> mList, DataTable _deviceDataTable)
{
//TODO Das ist total dirty und gar nicht clean hier...
foreach (DataRow dRow in _deviceDataTable.Rows)
{
if (dRow.ItemArray[3] is Slave)
{
foreach (MbusTelegram mb in mList)
{
int primeID = (int)dRow.ItemArray[1];
if (primeID == LoggerID)
{
Slave slv = (Slave)dRow.ItemArray[3];
foreach (MbusTelegram mbus in mList)
{
if (slv.PrimeAddress == mbus.Header.PrimeAddress && slv.SecondaryAdd == mbus.FixedDataHeader.SecondaryAddress)
{
if (slv.ListOfTelegramms == null)
{
slv.ListOfTelegramms = new List<MbusTelegram>();
}
if (!slv.ListOfTelegramms.Contains(mbus))
{
slv.ListOfTelegramms.Add(mbus);
//TODO Check if the slave already contains the telegramm, if so don't add it..
}
}
}
}
}
}
}
return _deviceDataTable;
}
Structure of the datatable:
private void IniDataTable()
{
_deviceDataTable = new DataTable("Table");
_deviceDataTable.Columns.Add("ID", typeof(int));
_deviceDataTable.Columns.Add("IDParent", typeof(int));
_deviceDataTable.Columns.Add("Name", typeof(string));
_deviceDataTable.Columns.Add("Object", typeof(object));
_deviceDataTable.Rows.Add(new object[] { 0, DBNull.Value, "Addressen", null });
//GenerateDummyDataTable();
IniDeviceTreeView();
}
This code doesn't work very well and it doesn't check if the device already contains the telegramm. Any better ideas?

There's a few things you can do to optimize your code. Firstly take all the things that doesn't change out of the inner loop. E.g. every type you use ItemArray. They are not changing for each iteration of the inner loop but with each iteration of the outer loop secondly you seem to be iterating twice over the same collection never using the variable of the outer loop (mb) so you can eleminate that loop entirely. I've done both in the code below. I've also converted the inner most loop to LINQ but that's mostly because I find it easier to read. I'm not sure the Distinct solves the TODO. I'm not sure I get the TODO at all. It might and it might not solve it you'll need to verify that
public static DataTable mdlform_NewMBUStele(int LoggerID, List<MbusTelegram> mList, DataTable _deviceDataTable){
//TODO Das ist total dirty und gar nicht clean hier...
foreach (DataRow dRow in _deviceDataTable.Rows.Cast<DataRow>().Where(d=>d.ItemArray[3] is Slave)){
var primeID = (int) dRow.ItemArray[1];
var slv = (Slave) dRow.ItemArray[3];
if (slv.ListOfTelegramms == null){
slv.ListOfTelegramms = new List<MbusTelegram>();
}
var list = slv.ListOfTelegramms;
if (primeID == LoggerID){
var items = from m in mList
where
slv.PrimeAddress == m.Header.PrimeAddress &&
slv.SecondaryAdd == m.FixedDataHeader.SecondaryAddress && !list.Contains(m, MbusTelegramEqualityComparer.Default)
select m;
list.AddRange(items.Distinct(MbusTelegramEqualityComparer.Default));
}
}
return _deviceDataTable;
}
if you also want to LINQify it for readability you could do as below (this might perform slightly worse):
public static DataTable mdlform_NewMBUStele2(int LoggerID, List<MbusTelegram> mList, DataTable _deviceDataTable){
var pairs = from dRow in _deviceDataTable.Rows.Cast<DataRow>()
where dRow.ItemArray[3] is Slave
let primeID = (int) dRow.ItemArray[1]
let slv = (Slave) dRow.ItemArray[3]
let list = slv.ListOfTelegramms
where primeID == LoggerID
select
new{
list,
items = (from m in mList
where
slv.PrimeAddress == m.Header.PrimeAddress &&
slv.SecondaryAdd == m.FixedDataHeader.SecondaryAddress &&
!list.Contains(m, MbusTelegramEqualityComparer.Default)
select m).Distinct(MbusTelegramEqualityComparer.Default)
};
foreach (var pair in pairs){
pair.list.AddRange(pair.items);
}
return _deviceDataTable;
}
the latter exampl requires that ListOfTelegrams is initialized to an empty list instead of null
EDIT
to have value comparison use a IEqualityComparer. The below uses the timestamps only for equality. The code is updated with the usage. If the ListOfTelegrams can contain duplicates you'll need to handle this in the call to distinct as well otherwise you can leave the call to Distinct out.
public class MbusTelegramEqualityComparer : IEqualityComparer<MbusTelegram>{
public static readonly MbusTelegramEqualityComparer Default = new MbusTelegramEqualityComparer();
public bool Equals(MbusTelegram x, MbusTelegram y){
return x.TimeStamp == y.TimeStamp;
}
public int GetHashCode(MbusTelegram obj){
return obj.TimeStamp.GetHashCode();
}
}

Related

IEnumerable gives error exception handeling can't read list

I want the code to show how many wagons there are and which animals are in each wagon. This is my error:
System.InvalidOperationException: "The collection has been changed. The inventory processing may not be performed. "
This is the code:
public IEnumerable<Animal> GetAnimals()
{
return Animals.AsEnumerable();
}
public void Checker(List<Animal> listAnimals)
{
foreach (Animal animal in listAnimals)
{
foreach (Wagon wagon in Wagons)
{
foreach (Animal wagonAnimal in wagon.GetAnimals())
{
if (wagon.StartCapacity <= wagon.MaxCapacity &&
animal.Formaat + wagon.StartCapacity <= wagon.MaxCapacity &&
wagonAnimal.Eater == Eater.carnivoor &&
animal.Eater == Eater.herbivoor &&
animal.Formaat >= wagonAnimal.Formaat)
{
wagon.AddAnimal(animal);
Wagons.Add(wagon);
}
else
{
Wagon waggi = new Wagon();
waggi.AddAnimal(animal);
Wagons.Add(waggi);
}
}
}
Wagon wag = new Wagon();
wag.AddAnimal(animal);
Wagons.Add(wag);
}
}
Can anyone give me some hints on this issue?
If you are looking to modify the collection while looping I would use a List object instead of an IEnumerable.
Some sample code would be like this:
List<Wagons> Wagons = new List<Wagons>
Wagons.AddAnimal(animal1);
foreach(Animal animal in Wagons.GetAnimals(){
animal.Eater = Eater.herbivore;
}
Also looking at your code:
if (wagon.StartCapacity <= wagon.MaxCapacity &&
animal.Formaat + wagon.StartCapacity <=
wagon.MaxCapacity && wagonAnimal.Eater == Eater.carnivoor &&
animal.Eater == Eater.herbivoor && animal.Formaat >= wagonAnimal.Formaat)
{
wagon.AddAnimal(animal);
Wagons.Add(wagon);
} else {
wagon.AddAnimal(animal);
Wagons.Add(wagon);
}
This if/else statement does the exact same code, so you really don't need an if/else, you can just add the animal and add the wagon.
Lastly, shouldn't the parameter to your method accept a List or IEnumerable collection of wagons and not animals so you can loop through the wagons, and look through the animals in the wagons?
You cannot modify a list while iterating over it using foreach and in.
Example:
foreach (Wagon wagon in Wagons)
{
Wagon waggi = new Wagon();
Wagons.Add(waggi);
}
will not work.
If you use e.g.
// This is needed to not get an endless loop (Because the length of the list
// increases after each time the Add() method is called to Wagons.)
int wagonCount = Wagons.Count;
for (int i = 0; i < wagonCount ; i++)
{
Wagon waggi = new Wagon();
waggi.AddAnimal(animal);
Wagons.Add(waggi);
}
this will work.
My working example for your code (as far as I could get what you want to do is here:
https://dotnetfiddle.net/6HXYmI and here: https://gist.github.com/SeppPenner/a082062d3ce2d5b8196bbf4618319044.
I would also recommend to check your code style according to the definitions from Microsoft: https://learn.microsoft.com/en-US/dotnet/csharp/programming-guide/inside-a-program/coding-conventions.
Actually you can't modify the list while looping.
You need to create another object and add the wagon and animals respectively.
Try this and comment if you didn't understand yet

How to return from Recursive Foreach Function

I am implementing recursion in one of my requirements. My actual requirement is as below:-
There is one master Table called Inventory which has many records like say "Inventory A","Inventory B","Inventory C".
There is one more table called Inventory Bundle which link one Inventory with other. So Inventory Bundle table has two columns :- SI & TI which represent Source Inventory Id and Target Inventory ID.
Record Ex.
SI TI
A B
B C
In my requirement if I click on any inventory then the associated inventory should also be fetched out.
Like here if I click on B then A & C should be fetched out. I use following recursion method to get the requirement:-
List<Guid> vmAllBundle = new List<Guid>();
List<Guid> vmRecursiveBundle = new List<Guid>();
List<Guid> processedList = new List<Guid>();
public List<Guid> GetAllRecursiveBundle(Guid invId, Guid originalInvId)
{
List<Guid> vmInvSrcBundleList = GetSourceInventory(invId); //Fetch to get All Related Source Inventories
List<Guid> vmInvTarBundleList = GetTargetInventory(invId); //Fetch to get All Related Target Inventories
vmAllBundle.AddRange(vmInvSrcBundleList);
vmAllBundle.AddRange(vmInvTarBundleList);
if (vmAllBundle.Contains(originalInvId))
vmAllBundle.Remove(originalInvId);
vmAllBundle = vmAllBundle.Distinct().ToList();
vmRecursiveBundle = vmAllBundle.ToList().Except(processedList).ToList();
foreach (Guid vmInvBundle in vmRecursiveBundle)
{
vmRecursiveBundle.Remove(vmInvBundle);
processedList.Add(vmInvBundle);
GetAllRecursiveBundle(vmInvBundle, originalInvId);
if (vmRecursiveBundle.Count == 0)
return vmAllBundle;
}
return null;
}
I am able to fetch the data using this method but I am facing problem while returning.
When I am returning it is calling GetAllRecursiveBundle() withing the foreach loop and continue to call until all the items in vmAllBundle gets finished. After this it exits the recursion.
This is something new to me so posting the question to ask if this is normal behavior or some code logic has to be changed.
Modified Code
public List<Guid> GetAllRecursiveBundle(Guid invId, Guid originalInvId)
{
if (vmRecursiveBundle.Count > 0)
vmRecursiveBundle.Remove(invId);
List<Guid> vmInvSrcBundleList = GetSourceInventory(invId); //Fetch to get All Related Source Inventories
List<Guid> vmInvTarBundleList = GetTargetInventory(invId); //Fetch to get All Related Target Inventories
vmAllBundle.AddRange(vmInvSrcBundleList);
vmAllBundle.AddRange(vmInvTarBundleList);
if (vmAllBundle.Contains(originalInvId))
vmAllBundle.Remove(originalInvId);
vmAllBundle = vmAllBundle.Distinct().ToList();
vmRecursiveBundle = vmAllBundle.ToList().Except(processedList).ToList();
foreach (Guid vmInvBundle in vmRecursiveBundle)
{
processedList.Add(vmInvBundle);
GetAllRecursiveBundle(vmInvBundle, originalInvId);
if (vmRecursiveBundle.Count == 0)
break;
}
return vmAllBundle;
}
I am very surprised that your code runs at all.
You are modifying the list being iterated by a foreach - normally that would throw an exception.
foreach (Guid vmInvBundle in vmRecursiveBundle)
{
vmRecursiveBundle.Remove(vmInvBundle); // **CRASHES HERE**
}
Modifying the collection being iterated by the foreach is not allowed, and would be considered bad practice even if it were allowed (because it frequently causes bugs).
You could change to a for loop, which has no such scruples:
for (int i = 0; i < vmRecursiveBundle.Count; i++)
{
Guid vmInvBundle = vmRecursiveBundle[i];
vmRecursiveBundle.Remove(vmInvBundle); // **NO CRASH**
i--; // counteracts the i++ so the next Guid is not skipped
}
For further details, see What is the best way to modify a list in a 'foreach' loop?
Normally, recursive method calls need something like a break value, that has to be checked on return, to signal the end of the recursive calls and to stop calling the reursive method. I do not fully understand your code, therefore here is an example:
private string SearchFileRecursive(string directory, string fileToFind)
{
string filePath = null;
string[] files = Directory.GetFiles(directory);
string foundFile = files.FirstOrDefault( file => (0 == string.Compare(Path.GetFileName(file), fileToFind, true)));
if(string.IsNullOrEmpty(foundFile))
{ // not found
string[] subDirectories = Directory.GetDirectories(directory);
foreach(string subDirectory in subDirectories)
{
filePath = SearchFileRecursive(subDirectory, fileToFind);
if(!string.IsNullOrEmpty(filePath)) // found
break;
}
}
else
{ // found
filePath = Path.Combine(directory, foundFile);
}
return filePath;
}

Best practice to check if DataRow contains a certain column

At the moment, when I iterate over the DataRow instances, I do this.
foreach(DataRow row in table)
return yield new Thingy { Name = row["hazaa"] };
Sooner of later (i.e. sooner), I'll get the table to be missing the column donkey and the poo will hit the fan. After some extensive googling (about 30 seconds) I discovered the following protection syntax.
foreach(DataRow row in table)
if(row.Table.Columns.Contains("donkey"))
return yield new Thingy { Name = row["hazaa"] };
else
return null;
Now - is this the simplest syntax?! Really? I was expecting a method that gets me the field if it exists or null otherwise. Or at least a Contains method directly on the row.
Am I missing something? I'll be mapping in many fields that way so the code will look dreadfully unreadable...
You can create an extension method to make it cleaner:
static class DataRowExtensions
{
public static object GetValue(this DataRow row, string column)
{
return row.Table.Columns.Contains(column) ? row[column] : null;
}
}
Now call it like below:
foreach(DataRow row in table)
return yield new Thingy { Name = row.GetValue("hazaa") };
As your DataTable table always has the same columns ( they won`t change for any row ) you only need to check for the columnname once.
if (table.Columns.Contains("donkey"))
{
foreach ...
}
I really liked the approach taken by #Varun K. So, having that as a departing point I just wanted to put my two cents, in case it helps someone else. I simply improved it making it generic instead of just using object as a return type.
static class Extensions
{
public static T Get<T>(this DataRow self, string column)
{
return self.Table.Columns.Contains(column)
? (T)self[column]
: default(T);
}
}
}
To build on the answer by Varun K, use a generic type parameter:
public static T GetValue<T>(this DataRow row, string column)
{
if (!row.Table.Columns.Contains(column))
return default(T);
object value = row[ColumnName];
if (value == DBNull.Value)
return default(T);
return (T)value;
}
foreach (DataColumn item in row.Table.Columns)
{
switch (item.ColumnName)
{
case "ID":
{
p.ID = Convert.ToInt32(row[item.ColumnName].ToString());
}
break;
case "firstName":
{
p.firstName = row[item.ColumnName].ToString();
}
break;
case "lastName":
{
p.lastName = row[item.ColumnName].ToString();
}
break;
default:
break;
};
}
Sometimes a column name might exist, but a row does not contain the data for that column; for example, after filling DataTable using ReadXML.
A simple, fast and secure solution would be to use type checking:
if(row["columnname"].GetType() != typeof(System.DBNull)){
//DataRow contains "columname"
}else{
//a safe scope to set default cell data
}

problem in preparing where clause of an update method in c#

hey guys, i'm finding little difficult to prepare a where clause in following update method
i'm creating a update method, here am i doin' this in a rightway ? but still m confused that how do i know that which property is to use in where clause
or any better approach to create a generic update method ? i wud be very thankfull
EDIT
public bool UpdateData(object Entity, ref String error)
{
Type objectType = Entity.GetType();
PropertyInfo[] properties = objectType.GetProperties();
error = "";
string column = null;
int i = 0;
SqlConnection conn = OpenConnection();
SqlCommand sqlcommand=null;
foreach (PropertyInfo info in properties)
{
if (i == 0)
{
i++;
continue;
}
column += (i >= 0 && i < properties.Length - 1) ? string.Format(#"{0}=#{0},", info.Name) : string.Format(#"{0}=#{0}",info.Name);
i++;
}
try
{
string sqlQuery = string.Format(#"update {0} set {1}
where {2}='{3}'", objectType.Name, column,1,1);//see here m not getting how to prepare this where clause
sqlcommand = new SqlCommand(sqlQuery, conn);
i = 0;
foreach (PropertyInfo info in properties)
{
if (i == 0)
{
i++;
continue;
}
sqlcommand.Parameters.AddWithValue(string.Format("#{0}", info.Name), info.GetValue(Entity, null));
}
sqlcommand.ExecuteNonQuery();
sqlcommand = null;
return true;
}
catch (Exception ex)
{
return false;
}
finally
{
CloseConnection(conn);
}
}
EDIT
see in above code there is an if condition if (i == 0) in foreach loop i dont want to do this way coz here i'm assuming my objects property i.e employee_id is at 1st index in array of PropertyInfo[].. what if some one makes a class properties like public string employee_name{get,set} public string employee_add{get,set} public string employee_id{get,set} in this case the foreach loop will skip employee_name instead of 'employee_id' coz i used if(i=0), i want a way to skip only identity value i.e employee_id in foreach loop in my update function irresptive of its index in propertyInfo array..... did i explained well ?
You can use a custom attribute or an interface to determine the property that represent the id of your entity and use it in your where clause. Although this approach has a negative side that you have to change your entity classes (which is not always possible).
Here's an example :
[PrimaryKey]
public string EmployeeId
{
get;set;
}
or
public class Employee:IEntity
{
public object EntityId
{
get
{
return this.EmployeeId
}
}
}
another way might be to store the entities metadata somewhere else (a xml file or a dictionary for example)
for example:
<entities>
<entity type="Employee" primarykey="EmployeeId" />
</entities>
or
Hashtable EntityPrimaryKeys=new Hashtable{(typeof(Employee),"EmployeeId")};
You really should use SQLCOmmand.Parameters instead of concatenating strings int he sql.
That way it will be more readable, secure and more feature proof.
You have a classical query exploit in your code where somebody could send in a parameter containing '; drop database yourdatabase; select * from dual where ''=' or similar.
So please update your code and if you have the same issue we will see what's going on.

C# DataRow Empty-check

I got this:
DataTable dtEntity = CreateDataTable();
drEntity = dtEntity.NewRow();
Then I add data to the row (or not).
Lots of code, really don't know if there's anything inside the row.
Depends on the input (i am importing from some files).
I'd like to do something like:
if (drEntity`s EVERY CELL IS NOT EMPTY)
{
dtEntity.Rows.Add(drEntity);
}
else
{
//don't add, will create a new one (drEntity = dtEntity.NewRow();)
}
Is there some nice way to check if the DataRow's every cell is empty?
Or I should foreach, and check them one by one?
A simple method along the lines of:
bool AreAllColumnsEmpty(DataRow dr)
{
if (dr == null)
{
return true;
}
else
{
foreach(var value in dr.ItemArray)
{
if (value != null)
{
return false;
}
}
return true;
}
}
Should give you what you're after, and to make it "nice" (as there's nothing as far as I'm aware, in the Framework), you could wrap it up as an extension method, and then your resultant code would be:
if (datarow.AreAllColumnsEmpty())
{
}
else
{
}
I created an extension method (gosh I wish Java had these) called IsEmpty as follows:
public static bool IsEmpty(this DataRow row)
{
return row == null || row.ItemArray.All(i => i is DBNull);
}
The other answers here are correct. I just felt mine lent brevity in its succinct use of Linq to Objects. BTW, this is really useful in conjunction with Excel parsing since users may tack on a row down the page (thousands of lines) with no regard to how that affects parsing the data.
In the same class, I put any other helpers I found useful, like parsers so that if the field contains text that you know should be a number, you can parse it fluently. Minor pro tip for anyone new to the idea. (Anyone at SO, really? Nah!)
With that in mind, here is an enhanced version:
public static bool IsEmpty(this DataRow row)
{
return row == null || row.ItemArray.All(i => i.IsNullEquivalent());
}
public static bool IsNullEquivalent(this object value)
{
return value == null
|| value is DBNull
|| string.IsNullOrWhiteSpace(value.ToString());
}
Now you have another useful helper, IsNullEquivalent which can be used in this context and any other, too. You could extend this to include things like "n/a" or "TBD" if you know that your data has placeholders like that.
I prefer approach of Tommy Carlier, but with a little change.
foreach (DataColumn column in row.Table.Columns)
if (!row.IsNull(column))
return false;
return true;
I suppose this approach looks more simple and cleaner.
public static bool AreAllCellsEmpty(DataRow row)
{
if (row == null) throw new ArgumentNullException("row");
for (int i = row.Table.Columns.Count - 1; i >= 0; i--)
if (!row.IsNull(i))
return false;
return true;
}
I know this has been answered already and it's an old question, but here's an extension method to do the same:
public static class DataExtensions
{
public static bool AreAllCellsEmpty(this DataRow row)
{
var itemArray = row.ItemArray;
if(itemArray==null)
return true;
return itemArray.All(x => string.IsNullOrWhiteSpace(x.ToString()));
}
}
And you use it like so:
if (dr.AreAllCellsEmpty())
// etc
You could use this:
if(drEntity.ItemArray.Where(c => IsNotEmpty(c)).ToArray().Length == 0)
{
// Row is empty
}
IsNotEmpty(cell) would be your own implementation, checking whether the data is null or empty, based on what type of data is in the cell. If it's a simple string, it could end up looking something like this:
if(drEntity.ItemArray.Where(c => c != null && !c.Equals("")).ToArray().Length == 0)
{
// Row is empty
}
else
{
// Row is not empty
}
Still, it essentially checks each cell for emptiness, and lets you know whether all cells in the row are empty.
DataTable.NewRow will initialize each field to:
the default value for each DataColumn (DataColumn.DefaultValue)
except for auto-increment columns (DataColumn.AutoIncrement == true), which will be initialized to the next auto-increment value.
and expression columns (DataColumn.Expression.Length > 0) are also a special case; the default value will depend on the default values of columns on which the expression is calculated.
So you should probably be checking something like:
bool isDirty = false;
for (int i=0; i<table.Columns.Count; i++)
{
if (table.Columns[i].Expression.Length > 0) continue;
if (table.Columns[i].AutoIncrement) continue;
if (row[i] != table.Columns[i].DefaultValue) isDirty = true;
}
I'll leave the LINQ version as an exercise :)
AFAIK, there is no method that does this in the framework. Even if there was support for something like this in the framework, it would essentially be doing the same thing. And that would be looking at each cell in the DataRow to see if it is empty.
I did it like this:
var listOfRows = new List<DataRow>();
foreach (var row in resultTable.Rows.Cast<DataRow>())
{
var isEmpty = row.ItemArray.All(x => x == null || (x!= null && string.IsNullOrWhiteSpace(x.ToString())));
if (!isEmpty)
{
listOfRows.Add(row);
}
}
Maybe a better solution would be to add an extra column that is automatically set to 1 on each row. As soon as there is an element that is not null change it to a 0.
then
If(drEntitity.rows[i].coulmn[8] = 1)
{
dtEntity.Rows.Add(drEntity);
}
else
{
//don't add, will create a new one (drEntity = dtEntity.NewRow();)
}
To delete null and also empty entries Try this
foreach (var column in drEntitity.Columns.Cast<DataColumn>().ToArray())
{
if (drEntitity.AsEnumerable().All(dr => dr.IsNull(column) | string.IsNullOrEmpty( dr[column].ToString())))
drEntitity.Columns.Remove(column);
}

Categories