Reputation: 3717
I have a DataTable which contains columns, some of which have a data type which is the type of a class in my project, so the column would be added to the table thus:
DataColumn column = _dataTable.Columns.Add("myColumnName", typeof(MyClass));
The problem comes when I create a DataView from the DataTable and try to set a RowFilter:
_dataView.RowFilter = "[myColumnName] = \"xyz\"";
This throws an exception "Cannot perform '=' operation on MyClass and System.String".
MyClass implements IComparable as well as Equals(object obj), ToString() and all the equality and comparison operators, but this is obviously not enough. I even tried adding a conditional branch to CompareTo(object obj) to accept an obj that is a string, but to no avail, it never even hits CompareTo.
Presumably the expression parser in DataView is simply unable to work out how to compare a MyClass to a string.
I would be grateful for any suggestions.
As requested by Cee McSharpFace, here is the relevant part of my class:
public class PartNumber : IXmlSerializable, IComparable<PartNumber>, IArithmetic, IEquatable<PartNumber>, IEquatable<string>, IComparable<string>
{
public int CompareTo(PartNumber other)
{
int result = 0;
if (other is null)
{
result = 1;
}
else
{
DoActualComparison(other);
}
return result;
}
public int CompareTo(string other)
{
return ToString().CompareTo(other);
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
public override bool Equals(object obj)
{
return !(obj is null) &&
obj is PartNumber &&
this == ((PartNumber)obj);
}
public static bool operator ==(PartNumber x, PartNumber y)
{
return x.CompareTo(y) == 0;
}
public static bool operator !=(PartNumber x, PartNumber y)
{
return !(x == y);
}
public static bool operator >(PartNumber x, PartNumber y)
{
return x.CompareTo(y) > 0;
}
public static bool operator <(PartNumber x, PartNumber y)
{
return x.CompareTo(y) < 0;
}
public bool Equals(string other)
{
return ToString().Equals(other);
}
public bool Equals(PartNumber other)
{
return Equals((object)other);
}
public static bool operator ==(PartNumber lhs, string rhs)
{
return lhs.ToString() == rhs;
}
public static bool operator !=(PartNumber lhs, string rhs)
{
return lhs.ToString() != rhs;
}
public object Add(object x)
{
return this;
}
public object Subtract(object x)
{
return this;
}
public object Multiply(object x)
{
return this;
}
public object Divide(object x)
{
return this;
}
public override string ToString()
{
return MakeObjectIntoString();
}
}
Upvotes: 1
Views: 303
Reputation: 8726
Having a column of type PartNumber
maps to the internal StorageType.Object
in the datatable.
The binary operations evaluator does not support any of its operators on this storage type:
Reference source, this is where it throws the exception you've got. The list of supported types is listed in the switch statement a few dozen lines further up.
Workaround 1:
Add a second column where you store just the PartNumber.ToString()
result, precomputed, to use in the query.
To make your code compile, and for demonstration purposes, I've added a private string field for the part number, which I return in its ToString
override:
public struct PartNumber : IComparable<PartNumber>, IEquatable<PartNumber>, IEquatable<string>, IComparable<string>
{
private readonly string actualPartnumber;
public PartNumber(string samplePartnumber) => this.actualPartnumber = samplePartnumber;
/* ... */
public override string ToString() => actualPartnumber;
}
Usage:
var dataTable = new DataTable();
var column = dataTable.Columns.Add("myColumnName", typeof(PartNumber));
var columnStr = dataTable.Columns.Add("myColumnNameStr", typeof(String));
var newRow1 = dataTable.NewRow();
newRow1["myColumnName"] = new PartNumber("abc");
newRow1["myColumnNameStr"] = newRow1["myColumnName"].ToString();
dataTable.Rows.Add(newRow1);
var newRow2 = dataTable.NewRow();
newRow2["myColumnName"] = new PartNumber("xyz");
newRow2["myColumnNameStr"] = newRow2["myColumnName"].ToString();
dataTable.Rows.Add(newRow2);
var dataView = new DataView(dataTable)
{
RowFilter = "[myColumnNameStr] = 'xyz'"
};
Console.WriteLine(dataView.Count);
--> this returns one row, as you would expect.
Workaround 2
The Convert function can deal with StorageType.Object
and is able to convert a PartNumber
instance to a string using its ToString
properly. So this would also work:
var dataTable = new DataTable();
dataTable.Columns.Add("myColumnName", typeof(PartNumber));
var newRow1 = dataTable.NewRow();
newRow1["myColumnName"] = new PartNumber("abc");
dataTable.Rows.Add(newRow1);
var newRow2 = dataTable.NewRow();
newRow2["myColumnName"] = new PartNumber("xyz");
dataTable.Rows.Add(newRow2);
var dataView = new DataView(dataTable)
{
RowFilter = "CONVERT([myColumnName], System.String) = 'xyz'"
};
Console.WriteLine(dataView.Count);
--> I tested it, and it also returns the correct (and only) match.
Upvotes: 1