Set table values equal to value in other table - c#

I have one table which functions as a viewModel. From this, it is supposed to be the source of my binding in my view. My object is to update this table/viewModel based on two other tables. I have one property from each of the table checklistTable and AnswerTable that is to be set in my viewModel checkViewModel.
At the moment I am querying the current elements I need from each of the tables, and trying to update the viewModel.
CodeBehind:
//Curent descriptions to be set in the viewModel
var currentDescription = (_check.Where(s => currentElements.Contains(s.defineId)).Select(s=> s.Description).ToList());
//Current colors to be set in the viewModel
var currentColors = (from current in _answer
where current.questionId == currentCheckId &&
current.buildingId == currentBuildingId
orderby current.dateReported descending
select current.backgroundColor).ToList();
After retrieving theese values, i try to update my viewModel, and this is where things go wrong:
for (int i =0 ; i < currentDescription.Count() - 1; i++)
{
currentViewTable.Description = currentDescription[i];
}
for (int i =0 ; i < currentColors.Count() - 1; i++)
{
currentViewTable.backgroundColor.Insert(i,currentColors[i]);
}
I get the error: reference not set to an instance of an object. Are there better ways to update my viewModel, of or any tips on what I am doing wrong?

I would first recommend to use a foreach loop instead of a regular for loop. I find it much easier to use foreach when working with lists, this will eliminate any clear "off by one" errors usually.
foreach (var description in currentDescription)
{
//do something given each element in the list
}
This seems to be a data integrity issue from what I can gather just looking at your code and given what the error is stating. I would recommend going into debug mode and looking in the Lists that you have generated to see if there are any indexes where the objects in your two lists are null, and handle these instances accordingly.

Related

DataRow.SetField() gives a null ref exception when adding data to a column I previously deleted then added back

UPDATE
I think I have found what is causing the issue here https://stackoverflow.com/a/5665600/19393524
I believe my issue lies with my use of .DefaultView. The post thinks when you do a sort on it it is technically a write operation to the DataTable object and might not propagate changes made properly or entirely. It is an interesting read and seems to answer my question of why passing valid data to a DataRow is throwing this exception AFTER I make changes to the datatable
UPDATE:
Let me be crystal clear. I have already solved my problem. I would just like to know why it is throwing an error. In my view the code should work and it does.. the first run through.
AFTER I have already deleted the column then added it back (run this code once)
When I debug my code line by line in Visiual studio and stop at the line:
data.Rows[i].SetField(sortColumnNames[k], value);
the row exists
the column exisits
value is not null
sortColumnNames[k] is not null and contains the correct column name
i is 0
Yet it still throws an exception. I would like to know why. What am I missing?
Sorry for the long explanation but this one needs some context unfortunately.
So my problem is this, I have code that sorts data in a DataTable object by column. The user picks the column they want to sort by and then my code sorts it.
I ran into an issue where I needed numbers to sort as numbers not strings (all data in the table is strings). eg (string sorting would result in 1000 coming before 500)
So my solution was to create a temporary column that uses the correct datatype so that numbers get sorted properly and the original string data of the number remains unchanged but is now sorted properly. This worked perfectly. I could sort string numeric data as numeric data without changing the formatting of the number or data type.
I delete the column I used to sort afterwards because I use defaultview to sort and copy data to another DataTable object.
That part all works fine the first time.
The issue is when the user needs to do a different sort on the same column. My code adds back the column. (same name) then tries to add values to the column but then I get a null reference exception "Object not set to an instance of an object"
Here is what I've tried:
I've tried using AcceptChanges() after deleting a column but this did nothing.
I've tried using column index, name, and column object returned by DataTable.Columns.Add() in the first parameter of SetField() in case it was somehow referencing the "old" column object I deleted (this is what I think the problem is more than likely)
I've tried changing the value of the .ItemArray[] directly but this does not work even the first time
Here is the code:
This is the how the column names are passed:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
data.DefaultView.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = data.DefaultView.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
This is where the temp columns are added:
private string[] AddSortColumns(string[] items)//adds columns to data that will be used to sort
//(ensures numbers are sorted as numbers and strings are sorted as strings)
{
string[] sortColumnNames = new string[items.Length];
for (int k = 0; k < items.Length; k++)
{
int indexOfOrginialColumn = Array.IndexOf(columns, items[k]);
Type datatype = CheckDataType(indexOfOrginialColumn);
if (datatype == typeof(double))
{
sortColumnNames[k] = items[k] + "Sort";
data.Columns.Add(sortColumnNames[k], typeof(double));
for (int i = 0; i < data.Rows.Count; i++)
{
//these three lines add the values in the original column to the column used to sort formated to the proper datatype
NumberStyles styles = NumberStyles.Any;
double value = double.Parse(data.Rows[i].Field<string>(indexOfOrginialColumn), styles);
bool test = data.Columns.Contains("QtySort");
data.Rows[i].SetField(sortColumnNames[k], value);//this is line that throws a null ref exception
}
}
else
{
sortColumnNames[k] = items[k];
}
}
return sortColumnNames;
}
This is the code that deletes the columns afterward:
private void RemoveSortColumns(string[] columnsToRemove)
{
for (int i = 0; i < columnsToRemove.Length; i++)
{
if (columnsToRemove[i].Contains("Sort"))
{
sortedData.Columns.Remove(columnsToRemove[i]);
}
}
}
NOTE:
I've been able to fix the problem by just keeping the column in data and just deleting the column from sortedData as I use .Clear() on the sorted table which seems to ensure the exception is not thrown.
I would still like an answer though as to why this is throwing an exception. If I use .Contains() on the line right before the one where the exception is thrown is says the column exists and returns true and in case anyone is wondering the params sortColumnNames[k] and value are never null either.
Your problem is probably here:
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
}
}
}
If you have 2 columns, and the first one matches the if, you will never look at the second.
This is because it will run:
i = 0
is i < columns.Count which is 2 => yes
is col[0].Contains("sort") true => yes
remove col[0]
i = 1
is i < columns.Count which is 1 => no
The solution is to readjust i after the removal
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
i--;//removed 1 element, go back 1
}
}
}
I fixed my original issue by changing a few lines of code in my SortByColumn() method:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
DataView userSelectedSort = data.AsDataView();
userSelectedSort.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = userSelectedSort.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
Instead of sorting on data.DefaultView I create a new DataView object and pass data.AsDataView() as it's value then sort on that. Completely gets rid of the issue in my original code. For anyone wondering I still believe it is bug with .DefaultView in the .NET framework that Microsoft will probably never fix. I hope this will help someone with a similar issue in the future.
Here is the link again to where I figured out a solution to my problem.
https://stackoverflow.com/a/5665600

Matching lisboxes items and creating result

I am creating an Exam system in c#. I am creating result, i have answers in a listbox1 and correct answers in another listbox2, my issue is values in the listboxes should be compared and result should be generated on its base. If half the values match student is pass otherwise fail.
My code for this is following but it does not work.
for(int intCount = 0; intCount < listBoxSanswers.Items.Count;intCount++)
{
for (int intSubCount = 0; intSubCount < listBoxActAnswers.Items.Count; intSubCount++)
{
if (listBoxActAnswers.Items[intCount].ToString() == listBoxActAnswers.Items[intSubCount].ToString())
{
listBox3.Items.Add(listBoxActAnswers.Items[intCount].ToString());
}
}
}
If you want to use your approach, than you have to change one of the two lists to listBoxSanswers
If you want a shorter way, without the loops, you can try this line:
listBox3.Items.AddRange(listBoxActAnswers.Items.Cast<string>().ToList().Intersect(listBoxSanswers.Items.Cast<string>().ToList()).ToArray());
EDIT:
Oh okay, so you have a DataTable as a DataSource.
Than you can do it this way:
listBox3.Items.AddRange(listBoxActAnswers.Items.Cast<DataRowView>().Select(r => r[0]).ToList().Intersect(listBoxSanswers.Items.Cast<DataRowView>().Select(r => r[0]).ToList()).ToArray());
Maybe you should adapt Select(r => r[0]) to the right column which is your DisplayMember.

c# collections and re-numbering not working as expected

Hi i'm trying to setup simple test data.
I simply want to take a collection which is smallish and make it bigger by add itself multiple times.
After I;ve added them together i want to re-number the property LineNumber
so that there are no duplicates and that it goes in order. 1,2,3,4....
no matter what i try it doesn't seem to work and i cant see the mistake.
var sampleTemplateLine = dataContext.TemplateFileLines.ToList();
*//tired this doesnt work either*
//List<TemplateFileLine> lineRange = new List<TemplateFileLine>();
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
var allProducts = sampleTemplateLine
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.ToList();
int i = 1;
foreach (var item in allProducts)
{
item.LineNumber = i;
i++;
}
this doesnt seem to work either
//re-number the line number
var total = allProducts.Count();
for (int i =0; i < total; i++)
{
allProducts[i].LineNumber = i+1;
}
PROBLEM: below RETURN 4 when i'm expecting 1
var itemthing = allProducts.Where(x => x.LineNumber == 17312).ToList();
You are adding the same objects multiple times. You wold have to add new objects or clone the ones you have.
The problem is they are pointing the same object. So if you change a property it changes all the pointed objects at the same
You can use Clone method if it exist, if not you can create your own Clone method like in this question.

Searching for record in c# Winform (Entity Framework)

I have a c# winform with textboxes, connected via Entity Framework to a table, called Candidates (it has 700 records).
I'm using a BindingSource named candidatesBindingSource. Everything works as I want.
There is just one thing. I'm trying to implement searching candidates with surnames. So i have a Textbox, called textSurname and a Button with this code
for searching through my records:
var searchResults = (from a in _context.Candidates where (a.Surname.Contains(textSurname.Text)) select a.Id).ToList();
if (searchResults.Count > 0)
{
// Id of a record in searchResults is correct
var position = searchResults[0];
// This line moves focus to a wrong record
candidatesBindingSource.Position = position; //
}
If a record is found, I can get its Id. And here I have a problem. How can I reposition my candidatesBindingSource to the
record with the Id from my searchResults? For example, if I have an Id = 2638, the code above repositions my candidatesBindingSource
to the last record. I'm suspecting that this part candidatesBindingSource.Position actualy works as recordcount (700 in my table)
and is unable to go to the record nr. 2638 (not to the record with this Id). Am I right? So how can I implement a GOTO record with my found Id?
Do I really have to use a For loop with MoveNext command to compare my searched Id with all Id's?
Any hint would be very appreciated.
Ok, so this is how you initialize you binding source
candidatesBindingSource.DataSource = _context.Candidates.ToList();
Then you don't need to search the database, you can search the data source list using the List.FindIndex method like this:
var candidateList = (List<Candidate>)candidatesBindingSource.DataSource;
var searchText = textSurname.Text;
var firstMatchIndex = candidateList.FindIndex(c => c.Surname.Contains(searchText));
if (firstMatchIndex >= 0)
candidatesBindingSource.Position = firstMatchIndex;
I think you should set to candidatesBindingSource.Position index of item and not id.
That post will help you to get index of item correctly, witout read whole data again.
Get Row Index in a list by using entity framework
Also you can try get index from your binding source.
If you create a list out of your context it will have the same indexing as the databinding you set on your form. To set your form to look at the result of your search you can use the a match from the FindIndex() method of the list, and then set your .Position to that index.
using (Candidates _context = new Candidates())
{
var candidateList = _context.Candidate.ToList();
var firstCandidateMatchIndex = candidateList.FindIndex(c =>
c.Surname.Contains(textSurname.Text));
if (firstCandidateMatchIndex >= 0)
candidateBindingSource.Position = firstCandidateMatchIndex;
}

AspectGetter to get object from list

I have a list of items that I would like to bind to my ObjectListView and I think AspectGetter needs to be used to achieve this. How would I go about doing this?
I have tried this to generate additional columns but I am still unable to bind the data to show list items
int count = 0;
foreach (var disk in vmObject.DisksList)
{
// create column with vhd+count
OLVColumn diskColumn = new OLVColumn("Attached VHD " + count, disk.Path);
// this lets you handle the model object directly
diskColumn.AspectGetter = delegate(object rowObject)
{
// check if that is the expected model type
if (rowObject is Model.HyperVTools.VMInfo)
{
// return the value of disklist
return ((Model.HyperVTools.VMInfo)rowObject).DisksList;
}
else
{
return "";
}
};
columnsList.Add(diskColumn);
count++;
}
objectListView2.Columns.AddRange(columnsList.Cast<System.Windows.Forms.ColumnHeader>().ToArray());
objectListView2.AddObject(vmObject);
The foreach loop is unnecessary. You only need to create a column once. The same goes for the AspectGetter.
As soon as you add objects to the OLV, it will call the AspectGetter delegates to get the values automatically and create the corresponding rows. I suggest you take another look at the tutorial / examples.

Categories