DataTable.GetChanges() -> Null Reference Exception in LookupNode.B

  • Thread starter Thread starter Guest
  • Start date Start date
G

Guest

Having an expression that includes a relation reference causes GetChanges()
to fail with an exception. A simple Console program to demonstrate the
problem follows. Is this a known bug? Is there another/better workaround?

Thanks,
David

using System;
using System.Data;
using System.Collections;

namespace ConAppOverflow
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
// Create a dataset.
DataSet ds = new DataSet("ds");
DataTable dt1, dt2;
DataRelation dr;

// Create two tables with [very] similar schema
// ID is the key
// CAT is an internal foreign key
// Ref is an external foreign key
// Text is text
dt1 = new DataTable("DT1");
dt1.Columns.Add("ID", System.Type.GetType("System.Int32"));
dt1.Columns.Add("CAT", System.Type.GetType("System.Int32"));
dt1.Columns.Add("REF", System.Type.GetType("System.Int32"));
dt1.Columns.Add("Text", System.Type.GetType("System.String"));
ds.Tables.Add(dt1);

//Add relation for internal foreign key
dr = new DataRelation("DT1CAT2ID", dt1.Columns["ID"], dt1.Columns["CAT"]);
ds.Relations.Add(dr);

// Add some data
dt1.Rows.Add(new object[] {1,1,10, "One"});
dt1.Rows.Add(new object[] {2,1,20, "Two"});
dt1.Rows.Add(new object[] {3,1,30, "Three"});


dt2 = new DataTable("DT2");
dt2.Columns.Add("ID", System.Type.GetType("System.Int32"));
dt2.Columns.Add("CAT", System.Type.GetType("System.Int32"));
dt2.Columns.Add("REF", System.Type.GetType("System.Int32"));
dt2.Columns.Add("Text", System.Type.GetType("System.String"));
ds.Tables.Add(dt2);

//Add relation for internal foreign key
dr = new DataRelation("DT2CAT2ID", dt2.Columns["ID"], dt2.Columns["CAT"]);
ds.Relations.Add(dr);

//Add relation for external foreign key
dr = new DataRelation("DT2REF2DT1ID", dt1.Columns["ID"],
dt2.Columns["REF"]);
ds.Relations.Add(dr);

// And add one more computed column with reference to our parent relation
dt2.Columns.Add("RefText", System.Type.GetType("System.String"),
"Parent(DT2REF2DT1ID).Text");

// Add some data
dt2.Rows.Add(new object[] {10,10,1, "Ten"});
dt2.Rows.Add(new object[] {20,10,2, "Twenty"});
dt2.Rows.Add(new object[] {30,10,3, "Thirty"});

// And accept it
ds.Tables["DT1"].AcceptChanges();
ds.Tables["DT2"].AcceptChanges();

// now, edit a row and watch the stack flow... over.
DataRow drow = ds.Tables[0].Rows[0];
drow.BeginEdit();
drow["Text"] = "*" + drow["Text"];
drow.EndEdit();

bool doWorkAroundForStackOverFlow = true;
bool doWorkAroundForLookupNodeBind = false;

if (!doWorkAroundForStackOverFlow)
{
if (ds.HasChanges((DataRowState.Modified)))
{
DataSet xds = ds.GetChanges(DataRowState.Modified);
ds.AcceptChanges();
}
}
else
{
if (ds.HasChanges(DataRowState.Modified))
{
// We can't do a ds.GetChanges because of an ADO.NET bug with foreign
keys.
for (int iTbl = 0; iTbl < ds.Tables.Count; iTbl++)
{
if (!doWorkAroundForLookupNodeBind)
{
DataTable xdt = ds.Tables[iTbl].GetChanges(DataRowState.Modified);
if (xdt != null)
{
// ... work with the changes
ds.Tables[iTbl].AcceptChanges();
}
}
else
{
// And we can't do dt.GetChanges because of another ADO.NET bug with
expression columns.
// All computed columns with relations must be removed from the table
prior to GetChanges
// being called and then put back - ugh!
DataTable dtTemp;
DataTable dt = ds.Tables[iTbl];
ArrayList expressions = new ArrayList();
ArrayList indexes = new ArrayList();
for (int iCol = 0; iCol < dt.Columns.Count; iCol++)
{
// Just do all expressions - not just the ones involving relations
if (dt.Columns[iCol].Expression != null &&
dt.Columns[iCol].Expression != String.Empty)
{
expressions.Add(dt.Columns[iCol].Expression);
indexes.Add(iCol);
dt.Columns[iCol].Expression = string.Empty;
}
}
dtTemp = dt.GetChanges(DataRowState.Modified);
for (int index = 0; index < expressions.Count; index++)
{
int iCol = (int)indexes[index];
dt.Columns[iCol].Expression = (string) expressions[index];
}
if (dtTemp != null)
{
// ... work with the changes
dt.AcceptChanges();
}
}
}
}
}
Console.WriteLine("Normal Termination (press <ENTER>)");
Console.ReadLine();
}
}
}
 
The dataset.GetChanges() stackoverflow issue has been fixed in V2.0

The table.GetChanges() now throws an EvaluateException instead of
NullReferenceException in V2.0
This throws for a very specify reason in that GetChanges calls
table.Clone() and if an expression references a relation to another table,
it can't bind the expression on the new table because only the single table
was cloned.

--
This posting is provided "AS IS", with no warranties, and confers no rights.
Please do not send email directly to this alias. This alias is for newsgroup
purposes only.

"Collaborative Data Services, FHCRC"
Having an expression that includes a relation reference causes
GetChanges()
to fail with an exception. A simple Console program to demonstrate the
problem follows. Is this a known bug? Is there another/better workaround?

Thanks,
David

using System;
using System.Data;
using System.Collections;

namespace ConAppOverflow
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
// Create a dataset.
DataSet ds = new DataSet("ds");
DataTable dt1, dt2;
DataRelation dr;

// Create two tables with [very] similar schema
// ID is the key
// CAT is an internal foreign key
// Ref is an external foreign key
// Text is text
dt1 = new DataTable("DT1");
dt1.Columns.Add("ID", System.Type.GetType("System.Int32"));
dt1.Columns.Add("CAT", System.Type.GetType("System.Int32"));
dt1.Columns.Add("REF", System.Type.GetType("System.Int32"));
dt1.Columns.Add("Text", System.Type.GetType("System.String"));
ds.Tables.Add(dt1);

//Add relation for internal foreign key
dr = new DataRelation("DT1CAT2ID", dt1.Columns["ID"], dt1.Columns["CAT"]);
ds.Relations.Add(dr);

// Add some data
dt1.Rows.Add(new object[] {1,1,10, "One"});
dt1.Rows.Add(new object[] {2,1,20, "Two"});
dt1.Rows.Add(new object[] {3,1,30, "Three"});


dt2 = new DataTable("DT2");
dt2.Columns.Add("ID", System.Type.GetType("System.Int32"));
dt2.Columns.Add("CAT", System.Type.GetType("System.Int32"));
dt2.Columns.Add("REF", System.Type.GetType("System.Int32"));
dt2.Columns.Add("Text", System.Type.GetType("System.String"));
ds.Tables.Add(dt2);

//Add relation for internal foreign key
dr = new DataRelation("DT2CAT2ID", dt2.Columns["ID"], dt2.Columns["CAT"]);
ds.Relations.Add(dr);

//Add relation for external foreign key
dr = new DataRelation("DT2REF2DT1ID", dt1.Columns["ID"],
dt2.Columns["REF"]);
ds.Relations.Add(dr);

// And add one more computed column with reference to our parent relation
dt2.Columns.Add("RefText", System.Type.GetType("System.String"),
"Parent(DT2REF2DT1ID).Text");

// Add some data
dt2.Rows.Add(new object[] {10,10,1, "Ten"});
dt2.Rows.Add(new object[] {20,10,2, "Twenty"});
dt2.Rows.Add(new object[] {30,10,3, "Thirty"});

// And accept it
ds.Tables["DT1"].AcceptChanges();
ds.Tables["DT2"].AcceptChanges();

// now, edit a row and watch the stack flow... over.
DataRow drow = ds.Tables[0].Rows[0];
drow.BeginEdit();
drow["Text"] = "*" + drow["Text"];
drow.EndEdit();

bool doWorkAroundForStackOverFlow = true;
bool doWorkAroundForLookupNodeBind = false;

if (!doWorkAroundForStackOverFlow)
{
if (ds.HasChanges((DataRowState.Modified)))
{
DataSet xds = ds.GetChanges(DataRowState.Modified);
ds.AcceptChanges();
}
}
else
{
if (ds.HasChanges(DataRowState.Modified))
{
// We can't do a ds.GetChanges because of an ADO.NET bug with foreign
keys.
for (int iTbl = 0; iTbl < ds.Tables.Count; iTbl++)
{
if (!doWorkAroundForLookupNodeBind)
{
DataTable xdt = ds.Tables[iTbl].GetChanges(DataRowState.Modified);
if (xdt != null)
{
// ... work with the changes
ds.Tables[iTbl].AcceptChanges();
}
}
else
{
// And we can't do dt.GetChanges because of another ADO.NET bug with
expression columns.
// All computed columns with relations must be removed from the table
prior to GetChanges
// being called and then put back - ugh!
DataTable dtTemp;
DataTable dt = ds.Tables[iTbl];
ArrayList expressions = new ArrayList();
ArrayList indexes = new ArrayList();
for (int iCol = 0; iCol < dt.Columns.Count; iCol++)
{
// Just do all expressions - not just the ones involving relations
if (dt.Columns[iCol].Expression != null &&
dt.Columns[iCol].Expression != String.Empty)
{
expressions.Add(dt.Columns[iCol].Expression);
indexes.Add(iCol);
dt.Columns[iCol].Expression = string.Empty;
}
}
dtTemp = dt.GetChanges(DataRowState.Modified);
for (int index = 0; index < expressions.Count; index++)
{
int iCol = (int)indexes[index];
dt.Columns[iCol].Expression = (string) expressions[index];
}
if (dtTemp != null)
{
// ... work with the changes
dt.AcceptChanges();
}
}
}
}
}
Console.WriteLine("Normal Termination (press <ENTER>)");
Console.ReadLine();
}
}
}
 
Ah, that explains why relations on the single table don't cause the problem.
I still think this is a bug which should be addressed. Perhaps this is a case
where MissingSchemaAction could be reused. Even the new error, "Can't find
the parent relation 'DT2REF2DT1ID'" is less than optimally helpful to someone
who doesn't know that DataTable.Clone() is being called under the covers.

I also think the documentation should be beafed up to talk about the problem
in DataTable.Clone(), and also to reference DataTable.Clone() in each DataSet
and DataTable operation that results in DataTable.Clone() being invoked
internally.

In any case, thanks for your insight!
David


Mark Ashton said:
The dataset.GetChanges() stackoverflow issue has been fixed in V2.0

The table.GetChanges() now throws an EvaluateException instead of
NullReferenceException in V2.0
This throws for a very specify reason in that GetChanges calls
table.Clone() and if an expression references a relation to another table,
it can't bind the expression on the new table because only the single table
was cloned.

--
This posting is provided "AS IS", with no warranties, and confers no rights.
Please do not send email directly to this alias. This alias is for newsgroup
purposes only.

"Collaborative Data Services, FHCRC"
Having an expression that includes a relation reference causes
GetChanges()
to fail with an exception. A simple Console program to demonstrate the
problem follows. Is this a known bug? Is there another/better workaround?

Thanks,
David

using System;
using System.Data;
using System.Collections;

namespace ConAppOverflow
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
// Create a dataset.
DataSet ds = new DataSet("ds");
DataTable dt1, dt2;
DataRelation dr;

// Create two tables with [very] similar schema
// ID is the key
// CAT is an internal foreign key
// Ref is an external foreign key
// Text is text
dt1 = new DataTable("DT1");
dt1.Columns.Add("ID", System.Type.GetType("System.Int32"));
dt1.Columns.Add("CAT", System.Type.GetType("System.Int32"));
dt1.Columns.Add("REF", System.Type.GetType("System.Int32"));
dt1.Columns.Add("Text", System.Type.GetType("System.String"));
ds.Tables.Add(dt1);

//Add relation for internal foreign key
dr = new DataRelation("DT1CAT2ID", dt1.Columns["ID"], dt1.Columns["CAT"]);
ds.Relations.Add(dr);

// Add some data
dt1.Rows.Add(new object[] {1,1,10, "One"});
dt1.Rows.Add(new object[] {2,1,20, "Two"});
dt1.Rows.Add(new object[] {3,1,30, "Three"});


dt2 = new DataTable("DT2");
dt2.Columns.Add("ID", System.Type.GetType("System.Int32"));
dt2.Columns.Add("CAT", System.Type.GetType("System.Int32"));
dt2.Columns.Add("REF", System.Type.GetType("System.Int32"));
dt2.Columns.Add("Text", System.Type.GetType("System.String"));
ds.Tables.Add(dt2);

//Add relation for internal foreign key
dr = new DataRelation("DT2CAT2ID", dt2.Columns["ID"], dt2.Columns["CAT"]);
ds.Relations.Add(dr);

//Add relation for external foreign key
dr = new DataRelation("DT2REF2DT1ID", dt1.Columns["ID"],
dt2.Columns["REF"]);
ds.Relations.Add(dr);

// And add one more computed column with reference to our parent relation
dt2.Columns.Add("RefText", System.Type.GetType("System.String"),
"Parent(DT2REF2DT1ID).Text");

// Add some data
dt2.Rows.Add(new object[] {10,10,1, "Ten"});
dt2.Rows.Add(new object[] {20,10,2, "Twenty"});
dt2.Rows.Add(new object[] {30,10,3, "Thirty"});

// And accept it
ds.Tables["DT1"].AcceptChanges();
ds.Tables["DT2"].AcceptChanges();

// now, edit a row and watch the stack flow... over.
DataRow drow = ds.Tables[0].Rows[0];
drow.BeginEdit();
drow["Text"] = "*" + drow["Text"];
drow.EndEdit();

bool doWorkAroundForStackOverFlow = true;
bool doWorkAroundForLookupNodeBind = false;

if (!doWorkAroundForStackOverFlow)
{
if (ds.HasChanges((DataRowState.Modified)))
{
DataSet xds = ds.GetChanges(DataRowState.Modified);
ds.AcceptChanges();
}
}
else
{
if (ds.HasChanges(DataRowState.Modified))
{
// We can't do a ds.GetChanges because of an ADO.NET bug with foreign
keys.
for (int iTbl = 0; iTbl < ds.Tables.Count; iTbl++)
{
if (!doWorkAroundForLookupNodeBind)
{
DataTable xdt = ds.Tables[iTbl].GetChanges(DataRowState.Modified);
if (xdt != null)
{
// ... work with the changes
ds.Tables[iTbl].AcceptChanges();
}
}
else
{
// And we can't do dt.GetChanges because of another ADO.NET bug with
expression columns.
// All computed columns with relations must be removed from the table
prior to GetChanges
// being called and then put back - ugh!
DataTable dtTemp;
DataTable dt = ds.Tables[iTbl];
ArrayList expressions = new ArrayList();
ArrayList indexes = new ArrayList();
for (int iCol = 0; iCol < dt.Columns.Count; iCol++)
{
// Just do all expressions - not just the ones involving relations
if (dt.Columns[iCol].Expression != null &&
dt.Columns[iCol].Expression != String.Empty)
{
expressions.Add(dt.Columns[iCol].Expression);
indexes.Add(iCol);
dt.Columns[iCol].Expression = string.Empty;
}
}
dtTemp = dt.GetChanges(DataRowState.Modified);
for (int index = 0; index < expressions.Count; index++)
{
int iCol = (int)indexes[index];
dt.Columns[iCol].Expression = (string) expressions[index];
}
if (dtTemp != null)
{
// ... work with the changes
dt.AcceptChanges();
}
}
}
}
}
Console.WriteLine("Normal Termination (press <ENTER>)");
Console.ReadLine();
}
}
}
 
Back
Top