Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Thomas Dolberg 74 posts 95 karma points
    Mar 14, 2012 @ 20:06
    Thomas Dolberg
    0

    Getting collection was modified enumeration operation error in Linq2Umbraco

    I am using Linq2Umbraco to retrieve data. But sometimes I get an error saying:

    "collection was modified enumeration operation"

    and the only way to resolve the error is to restart the IIS.

    The error appears in different places in my code, but always when retrieving something through Linq2Umbraco. 

    I cache Linq2Umbraco for performance. In order to keep the cached LinqContext fresh,  I remove the context from the cache when publishing a document (see code below). I haven't been able to recreate the error, but it just happens sometimes one my server. At first I thought it might have something to do with publishing a node, which might cause the Linq2Umbraco-context to be out of sync, but I haven't been able to prove that. Any ideas why ?

     

    thanks

    Thomas

     public partial class LinqDataContext : IDisposable

        {

            private static object createContextLock = new object();

            public static LinqDataContext Instance

            {

                get

                {

                    if (HttpContext.Current != null)

                    {

                        var context = HttpContext.Current.Cache["LinqDataContext"] as LinqDataContext;

                        if (context == null)

                        {

                            lock (createContextLock)

                            {

                                if (context == null)

                                {

                                    context = new LinqDataContext();

                                    HttpContext.Current.Cache["LinqDataContext"] = context;

                                    //to ensure the cache items are always fresh, we clear the context when changes are made to a document

                                    Document.AfterNew += new EventHandler<umbraco.cms.businesslogic.NewEventArgs>(Document_AfterNew);

                                    Document.AfterPublish += new Document.PublishEventHandler(Document_AfterPublish);

                                    Document.AfterUnPublish += new Document.UnPublishEventHandler(Document_AfterUnPublish);

                                    Document.AfterDelete += new Document.DeleteEventHandler(Document_AfterDelete);

                                    Document.AfterMoveToTrash += new Document.MoveToTrashEventHandler(Document_AfterMoveToTrash);

                                }

                            }

                        }

                        return context;

                    }

                    else

                        return new LinqDataContext();

                }

            }

     

            public void Dispose()

            {

                Document.AfterNew -= new EventHandler<umbraco.cms.businesslogic.NewEventArgs>(Document_AfterNew);

                Document.AfterPublish -= new Document.PublishEventHandler(Document_AfterPublish);

                Document.AfterUnPublish -= new Document.UnPublishEventHandler(Document_AfterUnPublish);

                Document.AfterDelete -= new Document.DeleteEventHandler(Document_AfterDelete);

                Document.AfterMoveToTrash -= new Document.MoveToTrashEventHandler(Document_AfterMoveToTrash);

                base.Dispose();

            }

            public static void RefreshFromCache()

            {

                if (HttpContext.Current != null)

                    HttpContext.Current.Cache.Remove("LinqDataContext");

            }

     

            static void Document_AfterMoveToTrash(Document sender, umbraco.cms.businesslogic.MoveToTrashEventArgs e)

            {

                RefreshFromCache();

            }

     

            static void Document_AfterDelete(Document sender, umbraco.cms.businesslogic.DeleteEventArgs e)

            {

                RefreshFromCache();

            }

     

            static void Document_AfterUnPublish(Document sender, umbraco.cms.businesslogic.UnPublishEventArgs e)

            {

                RefreshFromCache();

            }

     

            static void Document_AfterPublish(Document sender, umbraco.cms.businesslogic.PublishEventArgs e)

            {

                RefreshFromCache();

            }

     

            static void Document_AfterNew(object sender, umbraco.cms.businesslogic.NewEventArgs e)

            {

                RefreshFromCache();

            }

        }

     

  • James Diacono 16 posts 435 karma points
    Sep 20, 2012 @ 01:18
    James Diacono
    0

    It's because the LinqToUmbraco data context is not threadsafe.  So, if one request hits a MyDocType collection, it will lazy load it.  However, if (while lazy loading) another request/thread hits the same collection, it will try to load the collection again, which will result in this exception.

    I have not found a solution except to create one context per request.  This has the added advantage of keeping your data fresh.

    Having said that, I am getting this exception (more occasionally) when not caching the context at all...soooo...I dunno.

  • James Diacono 16 posts 435 karma points
    Sep 27, 2012 @ 07:33
    James Diacono
    0

    Ah ha!  I have a solution.  You can lock each of the properties ('trees') on your data context by extending the partial data context class.

    For you, it would look like this:


    public partial class LinqDataContext : UmbracoDataContext
    {
        private static readonly Dictionary<Type, object> _treeLockers = new Dictionary<Type, object>();
        /// <summary>
        /// Threadsafe override of LoadTree.  Locks all calls to any tree.
        /// </summary>
        protected new Tree<TDocTypeBase> LoadTree<TDocTypeBase>()
            where TDocTypeBase : DocTypeBase, new()
        {
            // Ensure a locker for this doctype exists
            var docType = typeof(TDocTypeBase);
            if (!_treeLockers.ContainsKey(docType))
            {
                lock (_treeLockers)
                {
                    // Ensure only a SINGLE locker for this doctype can exist
                    if (!_treeLockers.ContainsKey(docType))
                    {
                        _treeLockers[docType] = new object();
                    }
                }
            }
            // Safely retrieve the tree
            Tree<TDocTypeBase> tree;
            lock (_treeLockers[docType])
            {
                tree = base.LoadTree<TDocTypeBase>();
            }
            return tree;
        }
    }

    I've load tested this while republishing the site and individual nodes, and it appears that the threading exceptions go away!  Hurrah!

Please Sign in or register to post replies

Write your reply to:

Draft