views:

34

answers:

1

I am creating an mssql database table, "Orders", that will contain a varchar(50) field, "Value" containing a string that represents a slightly complex data type, "OrderValue".

I am using a linqtosql datacontext class, which automatically types the "Value" column as a string.

I gave the "OrderValue" class implicit conversion operators to and from a string, so I can easily use implicit conversion with the linqtosql classes like this:

// get an order from the orders table
MyDataContext db = new MyDataContext();
Order order = db.Orders(o => o.id == 1);

// use implicit converstion to turn the string representation of the order
// value into the complex data type.
OrderValue value = order.Value;

// adjust one of the fields in the complex data type
value.Shipping += 10;

// use implicit conversion to store the string representation of the complex
// data type back in the linqtosql order object
order.Value = value;

// save changes
db.SubmitChanges();

However, I would really like to be able to tell the linqtosql class to type this field as "OrderValue" rather than as "string". Then I would be able to avoid complex code and re-write the above as:

// get an order from the orders table
MyDataContext db = new MyDataContext();
Order order = db.Orders(o => o.id == 1);

// The Value field is already typed as the "OrderValue" type rather than as string.
// When a string value was read from the database table, it was implicity converted
// to "OrderValue" type.
order.Value.Shipping += 10;

// save changes
db.SubmitChanges();

In order to achieve this desired goal, I looked at the datacontext designer and selected the "Value" field of the "Order" table.

Then, in properties, I changed "Type" to "global::MyApplication.OrderValue".

The "Server Data Type" property was left as "VarChar(50) NOT NULL"

The project built without errors.

However, when reading from the database table, I was presented with the following error message:

Could not convert from type 'System.String' to type 'MyApplication.OrderValue'.
    at System.Data.Linq.DBConvert.ChangeType(Object value, Type type)
    at Read_Order(ObjectMaterializer`1 )
    at System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext()
    at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
    at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
    at Example.OrdersProvider.GetOrders() 
    at ... etc

From the stack trace, I believe this error is happening while reading the data from the table. When presented with converting a string to my custom data type, even though the implicit conversion operators are present, the DBConvert class gets confused and throws an error.

Is there anything I can do to help it not get confused and do the implicit conversion?

A: 

Interesting problem... I managed to do it by extending the generated data context partial class. In there I created a separate "typed" object which is set on load of the original data and replaces the original data on save...

namespace LinqSQLTest
{
    partial class DataClasses1DataContext
    {

    }

    partial class Order
    {
        public OrderValue TypedValue { get; set; }

        partial void OnLoaded()
        {
            TypedValue = this.Value;
        }

        partial void OnValidate(System.Data.Linq.ChangeAction action)
        {
            if (action == System.Data.Linq.ChangeAction.Insert ||
                action == System.Data.Linq.ChangeAction.Update)
            {
                if (TypedValue != null) Value = TypedValue;
            }
        }        
    }    
}

To use it, you'd access your "Value" through the "TypedValue" property instead - not ideal, but it's the only way I can see of doing it.

private void button1_Click(object sender, RoutedEventArgs e)
{
    DataClasses1DataContext dc = new DataClasses1DataContext();
    Order order = new Order();
    OrderValue value = new OrderValue();
    value.OrderID=22;
    value.OtherStuff="stuff";
    order.Value= value;
    dc.Orders.InsertOnSubmit(order);
    dc.SubmitChanges();

    var incomingOrder = dc.Orders.Where(x => x.ID == 32).FirstOrDefault();
    Console.WriteLine("ID: {0} \r\n Stuff: {1}", incomingOrder.TypedValue.OrderID, incomingOrder.TypedValue.OtherStuff);

    incomingOrder.TypedValue.OrderID += 10;
    dc.SubmitChanges();

    var incomingOrder2 = dc.Orders.Where(x => x.ID == 32).FirstOrDefault();
    Console.WriteLine("ID: {0} \r\n Stuff: {1}", incomingOrder2.TypedValue.OrderID, incomingOrder2.TypedValue.OtherStuff);

}
Marc
Hi Marc, Thanks for you reply. You obviously understand the problem perfectly, and have created your own work-around. Nifty use of the partial "OnLoaded" and "OnValidate" methods; I would have used a full implementation of the new property "get" and "set" methods.With your suggestion in mind as a solution that can be used, I am going to take a look at the source code of the DBConvert class and see if there's something I'm missing. For example, what if I can make my complex type implement an IConvertible<> interface or something? cheers!
bboyle1234
Well having used a reflector to read System.Data.Ling.DBConvert, I found that it does a bit of checking itself for some custom Linq object types, then sends the job off to System.Convert. System.Convert checks for the IConvertible interface. Of course, the IConvertible interface doesn't help with conversion to my custom data type, since it deals exclusively with native data types, string, integer, double, etc.There is no checking anywhere for implicit or explicit conversion operators, and there seems to be no "IAssignableFrom" interface either. Oh well.
bboyle1234
next step will be to see if I can override type conversion the datacontext class before it gets to the point of using DBConvert
bboyle1234
I couldn't see any other extensibility points when I looked - once I saw it disappear inside System.Convert I figured it was pretty much game over! You can make the work around a bit safer by marking the original field as "Private" (in the LINQ-DB designer) - at least that forces access through the new psuedo-property for everything so you have a better chance of catching problems.
Marc