hIpPy
hIpPy

Reputation: 5085

MSMQ and LINQ entity object serialization exception

I have a question regarding Msmq and serialization of linq entities.

I have a Primary MessageQueue and an Error MessageQueue. One process sends items to the Primary queue using the Send method below. Second process receives the items from the Primary queue in batches. The second process, on exceptions, sends items to the Error queue. During this, I get an System.ObjectDisposedException exception.

I'm using LINQ-sql and the Item object is an entity which is serializable (DataContext's Serialization Mode is Unidirectional).

In the dbml, the Item entity has an association to a Source entity (see the Item.get_Source() line in the stacktrace). I'm guessing that the ObjectDisposedException exception occurs when the Item's Source's getter is called. The Item's SourceID is populated even before it is sent to the Primary MessageQueue. It seems LINQ tries to access that lazy-loaded Source using the DataContext and throws the ObjectDisposedException. I'm not sure what's different between sending the items to the primary queue versus error queue.

Any ideas?

Stacktrace:

System.InvalidOperationException was caught
  Message=There was an error generating the XML document.
  Source=System.Xml
  StackTrace:
       at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
       at System.Xml.Serialization.XmlSerializer.Serialize(Stream stream, Object o, XmlSerializerNamespaces namespaces)
       at System.Messaging.XmlMessageFormatter.Write(Message message, Object obj)
       at System.Messaging.Message.AdjustToSend()
       at System.Messaging.MessageQueue.SendInternal(Object obj, MessageQueueTransaction internalTransaction, MessageQueueTransactionType transactionType)
       at namespace.Data.ImportServices.Msmq.MsmqProcessor`1.Send(MessageQueue q, List`1 items) in D:\Workspace\namespace.Data\ImportServices\Msmq\MsmqProcessor.cs:line 95
  InnerException: System.ObjectDisposedException
       Message=Cannot access a disposed object.
Object name: 'DataContext accessed after Dispose.'.
       Source=System.Data.Linq
       ObjectName=DataContext accessed after Dispose.
       StackTrace:
            at System.Data.Linq.DataContext.CheckDispose()
            at System.Data.Linq.DataContext.GetTable(Type type)
            at System.Data.Linq.CommonDataServices.GetDataMemberQuery(MetaDataMember member, Expression[] keyValues)
            at System.Data.Linq.CommonDataServices.DeferredSourceFactory`1.ExecuteKeyQuery(Object[] keyValues)
            at System.Data.Linq.CommonDataServices.DeferredSourceFactory`1.Execute(Object instance)
            at System.Data.Linq.CommonDataServices.DeferredSourceFactory`1.DeferredSource.GetEnumerator()
            at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
            at System.Data.Linq.EntityRef`1.get_Entity()
            at namespace.Data.Item.get_Source() in D:\Workspace\namespace.Data\DB.designer.cs:line 4757
            at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterItemMsmq.Write25_Item(String n, String ns, Item o, Boolean isNullable, Boolean needType)
            at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterItemMsmq.Write26_ItemMsmq(String n, String ns, ItemMsmq o, Boolean isNullable, Boolean needType)
            at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterItemMsmq.Write27_ItemMsmq(Object o)
       InnerException: 

Code:

Send items to queue.

void Send(MessageQueue q, List<T> items)
{
    using (q)
    {
        ((XmlMessageFormatter)q.Formatter).TargetTypes = new Type[] { typeof(T) };
        foreach (var item in items)
            q.Send(item); // <-- ex occurs while sending to Error message queue
    }
}

Receive items from queue and process items using the callback. On exception send items to Error queue.

void Receive(MessageQueue q, Action<List<T>> processCallback)
{
    List<T> items = null;
    try
    {
        items = GetNextBatchItems(q);
        processCallback(items);
    }
    catch (Exception ex)
    {
        // sent error messages to the Error queue
        var errorQ = _queueFactory.GetErrorQueue(q);
        Send(errorQ, items);
    }
}

Get next batch items from queue.

List<T> GetNextBatchItems(MessageQueue q)
{
    var items = new List<T>();
    var batchCount = _queueFactory.GetBatchCount(q);
    ((XmlMessageFormatter)q.Formatter).TargetTypes = new Type[] { typeof(T) };
    while (items.Count < batchCount)
    {
        var message = q.Receive();
        if (message.Body is T)
            items.Add((T)message.Body);
    }
    return items;
}

Upvotes: 1

Views: 1260

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1062530

I suspect this is because you are using XmlSerializer, when the serialization hooks in LINQ-to-SQL are intended for DataContractSerializer. The difference is that the latter supports pre-/post-serialization callbacks, which are used to help disable cascade loading.

My suspicion is that the serialization is causing navigation properties to lazily-load as the serializer crawls through the model. Using DataContractSerializer would avoid this. Alternatively, consider fully loading your model (at least the reachable parts) before serialising it.

If the model tries to lazily-load, but the underlying connection/data-context is no longer available, it will fail.

Another, far better approach:

don't serialize complex models with lazy loading etc

Instead, IMO, use your data-context to populate a dumb, ignorant plain POCO/DTO model, that knows nothing and does nothing, except act as a simple data that is really easy to serialize. This approach works much better in my experience (and when it comes to serialization, that is no small amount of experience). As a side benefit, because this will be your POCO/DTO, you can trivially configure it to be suitable for any serializer you like.

Upvotes: 4

Related Questions