0

I am developing an application for Windows CE and .NET Compact Framework 3.5.

The code runs fine in Debug mode but when I change the mode to Release I get various exceptions. I think it is related to some optimizations compiler trying to achieve in Release mode, like disposing and garbage collecting an object too early.

Following method, trying to insert entities to Sql Compact database, throws two exceptions(I think it is randomly):

public int Add<T>(List<T> entities)
    {
        int rowsEffected = 0;
        EntityMetadata metadata = GetMetadata<T>();
        using (SqlCeCommand command = new SqlCeCommand())
        {
            command.Connection = connection;
            command.CommandType = CommandType.TableDirect;
            command.CommandText = metadata.EntityMapAttribute.TableName;
            SqlCeResultSet set = command.ExecuteResultSet(ResultSetOptions.Scrollable | ResultSetOptions.Updatable);

            // Get generated Id, used in the loop below
            command.CommandType = CommandType.Text;
            command.CommandText = "SELECT @@IDENTITY";
            foreach (var entity in entities)
            {
                SqlCeUpdatableRecord record = set.CreateRecord();
                PropertyMetadata pkPropertyMetadata = null;
                foreach (var prop in metadata.PropertyMetadataList)
                {
                    if (prop.Attribute.IsPK)
                    {
                        // Identify PK Property, avoid setting values (db automatically sets id)
                        pkPropertyMetadata = prop;
                    }
                    else
                    {
                        object columnValue = prop.GetAccesssorDelegates<T>().Getter(entity);
                        record.SetValue(prop.Attribute.ColumnNumber, columnValue);
                    }
                }
                set.Insert(record);

                // Get Id of the inserted entity
                if (pkPropertyMetadata != null)
                {
                    object rawid = command.ExecuteScalar();
                    object convertedId = Convert.ChangeType(rawid, pkPropertyMetadata.Attribute.ColumnType, null);
                    pkPropertyMetadata.GetAccesssorDelegates<T>().Setter(entity, convertedId);
                }
                rowsEffected++;
            }
            return rowsEffected;
        }
    }

Exception 1:

Test 'M:Babil04_Mobil.Tests.ORMTests.Engine_Works' failed: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at System.Data.SqlServerCe.NativeMethods.ExecuteQueryPlan(IntPtr pTx, IntPtr pQpServices, IntPtr pQpCommand, IntPtr pQpPlan, IntPtr prgBinding, Int32 cDbBinding, IntPtr pData, Int32& recordsAffected, ResultSetOptions& cursorCapabilities, IntPtr& pSeCursor, Int32& fIsBaseTableCursor, IntPtr pError)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommandText(IntPtr& pCursor, Boolean& isBaseTableCursor)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteScalar()
MORMEngine.cs(182,0): at MORM.MORMEngine.Add[T](List`1 entities)
Tests\ORMTests.cs(187,0): at Babil04_Mobil.Tests.ORMTests.Engine_Works()

Exception 2:

Test 'M:Babil04_Mobil.Tests.ORMTests.Can_Insert_Multiple' failed: Cannot access a disposed object.
Object name: 'SqlCeResultSet'.
    System.ObjectDisposedException: Cannot access a disposed object.
    Object name: 'SqlCeResultSet'.
    at System.Data.SqlServerCe.SqlCeResultSet.CreateRecord()
    MORMEngine.cs(162,0): at MORM.MORMEngine.Add[T](List`1 entities)
    Tests\ORMTests.cs(187,0): at Babil04_Mobil.Tests.ORMTests.Can_Insert_Multiple()

The unit test that calls the method throwing exception:

[Test]
    public void Can_Insert_Multiple()
    {
        MORMEngine engine = new MORMEngine(connectionString);
        engine.OpenConnection();

        List<TestInventory> inventories = new List<TestInventory>();
        for (int i = 0; i < 10000; i++)
        {
            inventories.Add(new TestInventory
            {
                Code = "test" + i
            });
        }

        Stopwatch watch = new Stopwatch();
        watch.Start();

        int rows = engine.Add<TestInventory>(inventories);

        watch.Stop();
        Console.WriteLine("Completed in {0} ms.", watch.ElapsedMilliseconds);
        Assert.That(rows == 10000);
    }

Expcetions say that SqlCeResultSet is already disposed. Neither I call Dispose() method on the object nor set it to null. Why does it get disposed? Why does it run fine in Debug mode but not in Release mode?

Any ideas would be appreciated.

10
  • I'm still thinking about the answer... but, just to let you know... I think your rows are affected Commented Jan 28, 2013 at 15:07
  • Two suggestions: 1. Rename your "set" variable to something else, just to make sure it isn't somehow confused with the keyword get/set (definitely shouldn't matter). 2. Turn off optimizations in release mode and test it again in release without optimizations (project properties) Commented Jan 28, 2013 at 15:10
  • 1
    I'm not going to commit this as an answer, but I'd be concerned by the fact that you're re-using command to fetch the insert ID, whilst still using the result set from the initial query. It may be that when you re-use it, the first result set becomes eligible GC and is finalized. I would suggest creating a second SqlCeCommand solely for running the "SELECT @@IDENTITY" query, and see if that makes any difference. Commented Jan 28, 2013 at 15:30
  • @Iridium I think you might be right. Though I think it doesn't have anything to do with GC, the set certainly won't be eligible for GC, because there is a local variable referencing it. Commented Jan 28, 2013 at 16:54
  • Another thing to note is that there appear to be no references to engine in the test after the call to Add<...>(...), as a result in Release mode it's likely eligible for GC immediately after the command.Connection = connection; line in Add<>() (which is the last implicit this reference I see). Does MORMEngine implement a finalizer? If so, does that finalizer dispose the connection? Commented Jan 28, 2013 at 17:33

1 Answer 1

1

Per my earlier comments - it appears that there are no references to engine in the test after the int rows = engine.Add<TestInventory>(inventories); line, and the only reference to engine (via an implicit this) in Add<T>(List<T>) is the line where the connection is accessed. Subsequent to this line, there are no further references to the engine, and so in Release mode it will become eligible for GC. It appears (given that my suggestion corrected the issue) that there may be a finalizer somewhere (either in your MORMEngine, or perhaps elsewhere) that is causing the connection to be disposed whilst Add<>() is still running. (In Debug mode, the lifetimes of objects is extended until they exit scope so as to simplify debugging, which is likely why this issue only appears in Release mode)

To ensure that the engine remains alive throughout the call to Add<>(), the following approaches appear to work:

...
        watch.Start();

        int rows = engine.Add<TestInventory>(inventories);

        GC.KeepAlive(engine); // Ensure that the engine remains alive until Add completes

        watch.Stop();
...

Or preferably explicitly dispose the engine with a using() statement:

[Test]
public void Can_Insert_Multiple()
{
    using (MORMEngine engine = new MORMEngine(connectionString))
    {
        engine.OpenConnection();

        List<TestInventory> inventories = new List<TestInventory>();
        for (int i = 0; i < 10000; i++)
        {
            inventories.Add(new TestInventory
            {
                Code = "test" + i
            });
        }

        Stopwatch watch = new Stopwatch();
        watch.Start();

        int rows = engine.Add<TestInventory>(inventories);

        watch.Stop();
        Console.WriteLine("Completed in {0} ms.", watch.ElapsedMilliseconds);
        Assert.That(rows == 10000);
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.