Hi!

I have an instance of EntitySetBase class and want to get its count. The corresponding property throws an exception: "Collection was modified; enumeration operation may not execute." Is there another way?

Thanks


Updated at 03.11.2009 23:05:58

I don't know TItem in my case. I just have some collection of entities.


Updated at 03.11.2009 23:33:24

This bug is being reproduced if I try to call ToArray() of some sub collections. For example, I have an instance of BlogPost. I execute blogPost.Comments.ToArray(), and get this exception.


Updated at 03.11.2009 23:56:04

Yes, there is no transaction. I don't want to use transaction, I set ReadUncommitted isolation level. And I really don't understand why should I use transactions :?


Updated at 04.11.2009 0:25:54

If you write web application, you must just enable SessionManager module to get them provided automatically. See our ASP.NET sample (mainly, its web.config) for details.

I had been trying. SessionManager locks my DB when I just trying to make some insertions. I couldn't make it work although spent a lot of time. I almose say good-bye to hope of success with DO. All worked well with MS linq to sql, but I wanted to try your library. ...... experimenter :(


Updated at 04.11.2009 1:07:32

> SessionManager locks my DB when I just trying to make some insertions. What does this exactly mean? Locking the whole DB seems simply impossible...

Very simple example.

[HierarchyRoot]
    public abstract class AbstractObject : Entity
    {
        [Field, Key]
        public Guid Id { get; private set; }
    }
    public abstract class BlogPost : AbstractObject
    {
        public BlogPost()
        {
        }
        public BlogPost(Guid id)
            : base(id)
        {
        }

        [Field, Association(PairTo = "Post", OnOwnerRemove = OnRemoveAction.Cascade)]
        public EntitySet<BlogPostPage> Pages { get; private set; }
    }
    public sealed class Article : BlogPost
    {
        public Article()
        {
        }
        public Article(Guid id)
            : base(id)
        {
        }
    }
    [HierarchyRoot]
    [KeyGenerator(null)]
    public sealed class BlogPostPage : Entity
    {
        public BlogPostPage(BlogPost post, int number)
            : base(post, number)
        {
        }

        [Key(0), Field]
        public BlogPost Post { get; private set; }

        [Key(1), Field]
        public int Number { get; private set; }
    }

I look at the example and use SessionManager.

public Article CreateArticle(string title, string[] pages)
        {
            Guid id = Guid.NewGuid();

            using (var transactionScope = Transaction.Open())
            {
                using (var region = Validation.Disable())
                {
                    var article = new Article(id)
                    {
                        ....
                    };

                    for (int i = 0; i < pages.Length; i++)
                    {
                        var page = new BlogPostPage(article, i)
                        {
                            .....
                        };
                    }

                    region.Complete();
                }

                transactionScope.Complete();
            }

            return Query<Article>.Single(id);
        }

After this code execution I receive an Article object, and if I use ReadUncommitted isolation level, I see it in DB. But simple SELECT * FROM Article doesn't work because of lock. Of course, transaction is to be rollbacked.

This is not the only example.

This thread was imported from our support forum. The original discussion may contain more detailed answer. Original topic by buck.

asked Nov 03 '09 at 17:04

Editor's gravatar image

Editor
46156156157

Could you publish a stack trace?

Alternative way is to cast to appropriate IQueryable<titem> and get its count property. Will lead to query, but likely, it's ok as temporary workaround.

(Nov 03 '09 at 17:04) Alex Yakunin Alex%20Yakunin's gravatar image

One Answer:

Likely, this happens because there is no transaction around this operation. In fact, you're:

  • Getting collection enumerator. This is a transactional operation; moreover, enumerator will remember internal collection state version to ensure it is maintained the same during the subsequent enumeration.

  • Getting next item (calling enumerator.MoveNext). This is not transactional operation itself, but it validates state version inside (and this is a transactional method). And everything fail here, since version becomes different: collection gets reloaded because stale transactional data it caches is already expired in current transaction.


Concerning transactions: well, most likely, you should use them. They provide isolation & atomicity at least, and normally this is what really necessary. I'll give few links on this tomorrow.

If you write web application, you must just enable SessionManager module to get them provided automatically. See our ASP.NET sample (mainly, its web.config) for details.


Concerning "no transactions": you'll notice DO4 anyway creates them for you, but in this case their boundaries are defined by top-level SessionBound methods you call. This ensures isolation and data consistency inside these methods - that's one of warranties DO4 provides by default.

What's clear: we need to finish this part of manual ASAP...

Concetning TItem & IQueryable<t> type: you can detect them ~ by the following way:

var entitySetItemType = entitySet.Field.ItemType;
var queryableType = typeof(IQueryable<>).MakeGenericType(entitySetItemType);

Everything else - e.g. appropriate cast & Count() call, can also be done either via reflection or inside generic method called via reflection. Tricky, but... Anyway, that's just for your info, since the best way to handle this issue it to do this inside a transaction. Trick with IQueryable is completely unnecessary.


> I had been trying.

Yes, I remember...

> SessionManager locks my DB when I just trying to make some insertions.

What does this exactly mean? Locking the whole DB seems simply impossible...

What's clear is that DO4's behavior related to transactional data processing is absolutely unclear for many people. Its short explanation is very simple: DO4 always exposes exactly the same results as you'd see by running the same queries in the same conditions (transactional boundaries, etc.). Currently it does not cache any fetched data longer than your current transaction continues (although DisconnectedState & caches will violate this). This is completely different to what many other ORMs do: they cache everything until you explicitly ask them to forget this.

So e.g. this code will work in DO4:

Person p1, p2;
Key p1Key;
using (Session.Open(domain)) {
  var p1 = new Person("p1"); // transaction in session 1
  p1Key = p1.Key; // no transaction: Key property getter does not require it
}
using (Session.Open(domain)) {
  var p2 = Query<Person>.Single(p1Key); // transaction in session 2
}
// So p1 & p2 describe the same Entity in different Sessions
p1.Name = "p2"; // transaction in session 1
Assert.AreEqual("p2", p2.Name); // transaction in session 2

In most of ORMs last assertion would fail (let's think there are auto transactions as well, or we set the same boundaries manually there).


So do I understand that sequence is the following one: 1. The transaction in CreateArticle code below is completed. 2. Query "SELECT * FROM Article" executed from e.g. SQL Server Management Studio after is waiting for release of some lock.

If so, you must check which particular transaction is not completed yet - any allocated locks are released on completion of transaction they were acquired in.

And about your code: 1) In web application generally you shouldn't create transactions manually. By default they're anyway provided by SessionManager (1 per each web request; transaction starts right before your first query in provided Session). 2) If there is no 1), there would be actually two transactions in your code: one is explicit, and another one is implicit one - it wraps Query<article>.Single(id). But with 1) there must be anyway just a single transaction for the whole web request (the outermost one; your inner transactions are ignored).

General recommendation: turn on logging & run SQL Server profiler (turn on transaction-related events there - by default they aren't displayed) to see what happens.

To turn on logging, you must add this to your Web.config:

<configSections>
    ...
    <section name="Xtensive.Core.Diagnostics"
      type="Xtensive.Core.Diagnostics.Configuration.ConfigurationSection, Xtensive.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=93a6c53d77a5296c" />
  </configSections>
  <Xtensive.Core.Diagnostics>
    <logs>
      <log name="" provider="File" fileName="C:\Log.txt"/>
    </logs>
  </Xtensive.Core.Diagnostics>

answered Nov 03 '09 at 23:51

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412

So clearly, we need manual ASAP...

(Nov 03 '09 at 23:51) Alex Yakunin Alex%20Yakunin's gravatar image
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

Subscription:

Once you sign in you will be able to subscribe for any updates here

Tags:

×574

Asked: Nov 03 '09 at 17:04

Seen: 5,287 times

Last updated: Nov 03 '09 at 17:04

powered by OSQA