0

Windows CE および .NET Compact Framework 3.5 用のアプリケーションを開発しています。

コードはデバッグ モードで正常に実行されますが、モードをリリースに変更すると、さまざまな例外が発生します。オブジェクトの破棄やガベージコレクションが早すぎるなど、リリースモードで達成しようとしているいくつかの最適化コンパイラに関連していると思います。

次の方法で、エンティティを Sql Compact データベースに挿入しようとすると、2 つの例外がスローされます (ランダムだと思います)。

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;
        }
    }

例外 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()

例外 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()

例外をスローするメソッドを呼び出す単体テスト:

[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);
    }

予想によると、SqlCeResultSet は既に破棄されています。Dispose()オブジェクトのメソッドを呼び出したり、に設定したりしませんnull。なぜ処分されるのでしょうか?デバッグ モードでは問題なく動作するのに、リリース モードでは動作しないのはなぜですか?

どんなアイデアでも大歓迎です。

4

1 に答える 1

1

私の以前のコメントによると、行engineの後のテストにはへの参照がなく、 (暗黙の)int rows = engine.Add<TestInventory>(inventories);への唯一の参照は接続にアクセスされる行です。この行以降、エンジンへの参照はなくなるため、リリースモードではGCの対象になります。(私の提案が問題を修正したとすると)どこか(あなたの、またはおそらく他の場所)にファイナライザーがあり、実行中に接続が破棄される可能性があるようです。(デバッグモードでは、デバッグを簡素化するために、オブジェクトがスコープを終了するまでオブジェクトの有効期間が延長されます。これが、この問題がリリースモードでのみ発生する理由である可能性があります)enginethisAdd<T>(List<T>)MORMEngineAdd<>()

の呼び出し中にエンジンが存続することを保証するためAdd<>()に、次のアプローチが機能しているように見えます。

...
        watch.Start();

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

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

        watch.Stop();
...

engineまたは、できれば、using()ステートメントを使用して明示的に破棄します。

[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);
    }
}
于 2013-01-29T09:00:35.890 に答える