C# Change Object Value Through Another Class - c#

I'm trying to change an object value from other class. Ideally, I want to pass the object as parameter into constructor/method. What I've read so far, object behaves as reference when passed as parameter, and parameter value are copied locally for use in the method's body. So here are few configuration that I tested:
Case #1. Failed
class Processor
{
DataTable table;
public Processor(DataTable table)
{
this.table = table;
}
public void InitializeTable()
{
if (table != null)
{
// Fill data into DataTable.
}
}
}
static void Main(string[] args)
{
DataTable mainTable = new DataTable();
Processor processor = new Processor(mainTable);
processor.InitializeTable();
// mainTable still empty
}
I thought Processor table holds the same reference to mainTable, but by the end of Main execution mainTable was still EMPTY while Processor table was filled.
Case #2. Failed
public Processor(ref DataTable table)
{
this.table = table;
}
I've tried using ref signature, but result still the same (mainTable was EMPTY).
Case #3. Failed
public void InitializeTable(DataTable table)
{
// Fill data into table
}
I've removed the constructor and feed mainTable into InitializeTable() method, and result still the same (mainTable was EMPTY).
Case #4. Works!
public void InitializeTable(ref DataTable table)
{
// Fill data into table
}
Finally this works! Feeding ref mainTable into InitializeTable now fills mainTable successfully. What are the explanation behind this? Why constructor didn't have the same reference to mainTable? Why ref keyword still needed when passing object as parameter already means passing its reference?

Credit goes to David:
Then that explains the behavior. You're re-assigning the local table
variable to a new DataTable instance (returned by .SelectArray()). So
it no longer refers to the same instance as the mainTable variable and
no longer modifies that instance.

Related

filtering before filling a TableAdapter

So i have a DataSet to query a table on an Oracle Database. The table is very large and has 3.5 million entries. However later in the code I filter this table on a few hundred entries that are necessary.
AgileDataSet agileDataSet = new AgileDataSet();
AgileDataSetTableAdapters.UserDataTableAdapter userDataTableAdapter = new AgileDataSetTableAdapters.UserDataTableAdapter();
userDataTableAdapter.Fill(agileDataSet.UserData);
var l=agileDataSet.UserData.Where(x=>x.ID==1234);
Due to the large amount of entries the Fill() method takes forever. Is there a way to add a conditions to the fill method at runtime. Adding a permanent WHERE clause to the TableAdapter's SQL Statement in the DataSet Designer is not an option, because I do not know beforehand which elements are needed.
Ok so I implemented a hacky "solution" and created a new partial class for the TableAdapter. In this class I generated an adapted Fill-Function to use whenever I need to get a subset of the whole DBTable in my DataTable
partial class UserDataTableAdapter
{
public virtual int FillByCICList(AgileDataSet.UserDataDataTable dataTable, List<long> cics)
{
this.CommandCollection[0].CommandText += String.Format(" WHERE vu.C_IC IN ({0})", String.Join(", ", cics));
this.Adapter.SelectCommand = this.CommandCollection[0];
if ((this.ClearBeforeFill == true))
{
dataTable.Clear();
}
int returnValue = this.Adapter.Fill(dataTable);
this.InitCommandCollection();
return returnValue;
}
}
This function takes a list of c_ics as additional input and adds a where condition to the base commanttext used for the standard fill function.
Calling it would look sth like this:
List<long> c_ics = new List<long>{224,484,966,695,918};
userDataTableAdapter.FillByCICList(agileDataSet.UserData, c_ics);
I am sure that there is a better solution to this, but that is all I got so far

My Table Adapter is returning null even after checking that the query is correct

I use a created query to return a row of values from a key, I have checked on the query executor but when running the program it is returning null. Please help me I'm starting to lose my mind.
I've tried debugging but the problem is coming from the fill data query and it is returning null.
// Add ref. to the Dataset
ViewMedia viewMediaSetInstance = new ViewMedia();
// Create an empty Table
ViewMedia.ViewMediaDataTable viewMediaTable = new ViewMedia.ViewMediaDataTable();
// Create the Adapter we are going to use to populate the Table
Model.ViewMediaTableAdapters.ViewMediaTableAdapter MediaTableAdapter = new Model.ViewMediaTableAdapters.ViewMediaTableAdapter();
//Use query
MediaTableAdapter.findByPublishYear(viewMediaTable, publishYear);
//It doesn't find the row
//I don't seem to find a solution even when the query returns the values in the executer.
//viewMediaTable.Rows.Count == null so doesn't get inside
if (viewMediaTable.Rows.Count > 0)
{
DataRow selectedUser = viewMediaTable.Rows[0];
media = new Media(Int32.Parse(selectedUser["MediaID"].ToString()), selectedUser["Title"].ToString(), Int32.Parse(selectedUser["PublishYear"].ToString()));
return media;
}
else
{
return null;
}
I'm expecting to return the row of data to display in a book display.
This is wonky; you've got strongly typed data rows with proper typed properties for everything, yet you're treating them as generic datarows with string column names, holding objects that you're tostring()ing and parsing. It was never intended to be used this way.
Your code should look more like this:
ViewMedia viewMediaSetInstance = new ViewMedia();
ViewMedia.ViewMediaDataTable viewMediaTable = new ViewMedia.ViewMediaDataTable();
Model.ViewMediaTableAdapters.ViewMediaTableAdapter MediaTableAdapter = new Model.ViewMediaTableAdapters.ViewMediaTableAdapter();
MediaTableAdapter.findByPublishYear(viewMediaTable, publishYear);
//don't need to access the .Rows property to get the count
if (viewMediaTable.Count > 0)
{
//don't need to access the .Rows property to get the data
ViewMediaRow selectedUser = viewMediaTable[0];
media = new Media(selectedUser.MediaID, selectedUser.Title, selectedUser.PublishYear);
return media;
}
else
{
return null;
}
I also disagree with your assertion in the code comments that datatable.Rows.Count is null. A datatable's .Count property is an integer regardless of whether a strongly or weakly typed datatable is in use; it can never be null
You don't need to create a dataset, if you enable creating a method that returns a datatable. Typically these methods are called GetDataByXXX and the fill methods are called FillByXXX (you've called your fill method findByPublishYear). It's configured on the following screen of the TA wizard:
image courtesy of nullskull.com
This can simplify your code to just the following (add a reference to Model.ViewMediaTableAdapters):
var mDT = new ViewMediaTableAdapter().GetDataByPublishYear(publishYear);
Media m = null;
if (mDT.Count > 0)
media = new Media(mDT[0].MediaID, mDT[0].Title, mDT[0].PublishYear);
return m;
Most critically here, DON'T access the first row via the .Rows collection (viewMediaDataTable.Rows[0]) because that returns a base type DataRow. Access it directly via the default property indexer on the strongly typed datatable (viewMediaDataTable[0]) as this will return an object of type ViewMediaRow which in turn has nicely named, typed properties for all the columns in the datatable
viewMediaDataTable[0].PublishYear; //ViewMediaRow is a strongly typed class that has this property as an int
viewMediaDataTable.Rows[0].PublishYear; //"DataRow does not contain a definition for 'PublishYear'"
Now, if any of the data items in the row is null and the setting of the column upon null is "throw exception" you will see an exception of type StrongTypingException- it happens when you have some column that is of a type that cannot be null (such as an int) but it's null because the database query didn't return a value for that row. You were hitting this with your code because som int column (for example) was DBNull.Value and the only thing a datarow will do whn you try to access an int column tha tis DBNull, is throw an exception. By specifying SELECT * in the query, you caused the query to return a value for the column; by not selecting a column the datatable will always treat it as null. You can still also encounter a strongtypingexception if the value of the data in the database is also null. In these cases, use the IsXXXNull() methods to determine if the property is null before trying to access it (causing a strongtypingexception)
Edit: Found the error, I was supposed to get all the columns from the query (using *) and then in the code take what I need otherwise null values get returned.

Column does not belong to table c#

I am getting an error when trying to create a list of objects. I am calling a web service which returns a different number of columns for certain products.
I have a class to store these values which hold all columns available, but when receiving data for products that are missing 1 column it fails with the error:
Column 'x' does not belong to table y.
Since I know column does not exist I need a replacement value if it does not exist. I have about 50 different columns so putting each one of them in an (if column exist) is something I would like to avoid!
Sample code, dprow is the Object template which holds all available columns
dprow.currency = row.Field<string>("currency");
dprow.categoryCode = row.Field<string>("categoryCode");
This is under the assumption that your row is a DataRow, but the concept applies regardless. You can write an extension method that will check if the column exists first:
public static class DataRowExtensions
{
public static T TryGetField<T>(this DataRow row, string fieldName)
{
return row.Table.Columns.Contains(fieldName) ? row.Field<T>(fieldName) : default(T);
}
}
Usage example: dprow.currency = row.TryGetField<string>("currency");
Take the table you get back from the web service (in my case I'm just saying create a brand new one) and then create a dictionary with all the columns that you want to have present, and what the default value should be. If that column isn't already in the table, then add it to the table with the default value.
DataTable dt = new DataTable();
Dictionary<string, object> columnDefaultValues = new Dictionary<string, object>(){
{"currency", "some default"},
{"categoryCode", "some other default"},
{"another", 43}
};
foreach (var keyPair in columnDefaultValues) {
if (!dt.Columns.Contains(keyPair.Key)) {
dt.Columns.Add(new DataColumn(keyPair.Key) { DefaultValue = keyPair.Value });
}
}

Extention method not assigning value

I have a custom sort method for my dataset. I call it like this:
ds.Sort("column");
where ds is of type dataset. Then I sort the first table (sorting code itself is not relevant as the breakpoint shows the result is correct). My sort method looks like this:
public static void Sort(this DataSet ds, string column)
{
DataSet newDs = ds.Copy();
//sorting occurs
ds = newDs.Copy();
} // <- breakpoint
At the place of breakpoint both ds and newDs have everything sorted like it should. However once I move forward and get to the line after calling this sort method, ds is no longer sorted. I also tried the same with simply doing
ds.Clear();
in that Sort method and this time it worked. Am I not allowed to assign a value to the object? Is it possible to do this in some way?
Am I not allowed to assign a value to the object?
You're not assigning a value to an object. You're assigning a value to a variable... and that variable is a parameter. Changes to that parameter value are not seen outside the method, as C# uses pass-by-value by default. So for example, consider this code:
static void Foo(string input)
{
input = "In Foo";
}
...
string text = "hello";
Foo(text);
Console.WriteLine(text); // Still prints "hello"
If you don't understand that, read my article on parameter passing for more details.
You probably want something like this:
public static DataSet CopyAndSort(this DataSet ds, string column)
{
DataSet newDs = ds.Copy();
//sorting occurs
return newDs; // No need to copy it *again*.
}
Then you can use:
ds = ds.CopyAndSort("column");
The difference between
ds.Clear();
and
ds = newDs.Copy();
is that the first operates on the DataSet object that you have passed in through a reference ds, while the second one re-assigns a new object to that variable, making the original object inaccessible to your extension method.
You can use Merge method as a backdoor for filling the original data set with sorted data:
ds.Reset();
ds.Merge(newDs);
This will work, because the data would be copied into the original DataSet.

Working with DataGridViewRow.DataBoundItem - transform to Enumerable Object?

I have a DataGridView with
myGridView.DataSource = GetSomeData()
// method called
public IQueryable GetSomeData()
{
var source = from r in records
select r;
return source; // made correction from 'r' to 'source'
}
GetSomeData() fills the DataGridView as expected. The user will select a row to edit which then passes the row data to a form. Since the DataGridViewRow.DataBoundItem is an anonymous type, how can I pass DataBoundItem?
I half expected that my DataBoundItem would be IQueryable - incorrect. Here is the info from the debugger on the DataBoundItem property:
DataBoundItem { CustomerID = "3133",
Last_Name = "Smith", First_Name =
"John", AccountNumber = "JS3133",
ActiveYN = True } < Anonymous Type >
Once the data is passed to the new form, I would like to do something like:
txtFName.Text = SomeEnumeratedObject["First_Name"];
txtLName.Text = SomeEnumeratedObject["Last_Name"];
Any ideas on how I can do this? It would be even better, IMO, if the controls on the new form could some how be bound to SomeEnumeratedObject.
Would it possible to query the DataBoundItem with LINQ?
Edit:
Changed method:
public DataView GetSomeData()
{
var source = from r in records
select r;
DataTable table = ToDataTable(myContext, source);
return new DataView(table);
}
See complete solution here.
You could pass it as dynamic and access the properties that way:
public void Test(dynamic item)
{
MessageBox.Show(string.Format("{0} : {1}", item.First_Name, item.Last_Name));
textBox1.DataBindings.Add("Text", _item, "First_Name");
textBox2.DataBindings.Add("Text", _item, "Last_Name");
}
One thing to consider is that properties on anonymous types are read only, so your user will not be able to edit the values that way. From the C# Language Spec:
The members of an anonymous type are a sequence of read-only properties inferred from the anonymous object initializer used to create an instance of the type.
You should consider declaring a type for this instead of using an anonymous type. That way you'll also get the benefit of intellisense and be able to rename/refactor your properties without any problems.
What type of objects are in records? You should be able to cast DataGridViewRow.DataBoundItem to whatever type of object records holds.
Say records is a list of Customer objects. You should be able to go:
txtFName.Text = ((Customer)DataGridViewRow.DataBoundItem).First_Name;
txtLName.Text = ((Customer)DataGridViewRow.DataBoundItem).Last_Name;
If that isn't possible, then I think you will have to use reflection:
Type type = DataGridViewRow.DataBoundItem.GetType();
String firstName = (String)type.GetProperty("First_Name")
.GetValue(DataGridViewRow.DataBoundItem, null);
Both kevev22 & adrift had the right idea: create a class to hold the data. Since I can transform an IQueryable resultset to a DataTable, it only made sense to do that:
public DataView GetSomeData()
{
var source = from r in records
select r;
DataTable table = ToDataTable(myContext, source);
return new DataView(table);
}
Now:
myDGView.DataSource = GetSomeData();
And, when a datagrid row is selected, you can cast the DataBoundItem to DataRow.
FYI - to transform an IQueryable resultset to a DataTable:
public DataTable ToDataTable(DataContext context, IQueryable source)
{
DataTable table = new DataTable();
{
adapter.SelectCommand = context.GetCommand(source);
sqlCommand.Connection.Open();
adapter.FillSchema(table, SchemaType.Source);
adapter.Fill(table);
}
return table;
}

Categories

Resources