System.Data.SQLite

Login
This project makes use of Eagle, provided by Mistachkin Systems.
Eagle: Secure Software Automation
2011-09-12
20:43
Merge fix for ticket [ccfa69fc32] to trunk. check-in: 14a6302a28 user: mistachkin tags: trunk
20:41 Ticket [ccfa69fc32] Transaction on conflict behave status still Closed with 1 other change artifact: 99569dc0ad user: mistachkin
20:24 Ticket [ccfa69fc32]: 1 change artifact: 3e7fe221da user: mistachkin
20:23 Closed ticket [ccfa69fc32]. artifact: db283de021 user: mistachkin
19:34
Streamline the test case for ticket [ccfa69fc32] and make it more complete by not relying on sort ordering of the IDs. Closed-Leaf check-in: bba06f3a9f user: mistachkin tags: bug-ccfa69fc32
12:45 Ticket [ccfa69fc32] Transaction on conflict behave status still Pending with 1 other change artifact: 06934d3be8 user: mistachkin
11:23 Ticket [ccfa69fc32]: 1 change artifact: 71b8cc5b7c user: anonymous
08:47 Ticket [ccfa69fc32]: 1 change artifact: 39a0c20a08 user: anonymous
2011-09-11
15:37 Ticket [ccfa69fc32]: 2 changes artifact: 5683d82b91 user: mistachkin
15:36 Ticket [ccfa69fc32]: 1 change artifact: 17072e76a4 user: mistachkin
15:33
Add unit testing support for the fix to ticket [ccfa69fc32]. check-in: f4e1038098 user: mistachkin tags: bug-ccfa69fc32
14:33
Experimental fix for ticket [ccfa69fc32]. check-in: 42af4d17a5 user: mistachkin tags: bug-ccfa69fc32
13:42 Ticket [ccfa69fc32] Transaction on conflict behave status still Pending with 2 other changes artifact: 7c11684a11 user: mistachkin
13:40 Ticket [ccfa69fc32]: 1 change artifact: 3b66c3e85e user: mistachkin
13:09 Ticket [ccfa69fc32]: 1 change artifact: ede3099d1e user: mistachkin
11:07 Pending ticket [ccfa69fc32]. artifact: 6de8a8e08b user: mistachkin
2011-09-10
13:17 New ticket [ccfa69fc32]. artifact: 645fc9f771 user: anonymous

Ticket Hash: ccfa69fc3251fb3512e9f17eca7a4c8fab6302e1
Title: Transaction on conflict behave
Status: Closed Type: Code_Defect
Severity: Minor Priority: Immediate
Subsystem: Connection Resolution: Fixed
Last Modified: 2011-09-12 20:41:22
Version Found In: 1.0.75.0
Description:
When a transaction is running and an error occurs, ON CONFLICT clause not running default. For example when a transaction with 100 rows (inserts) is executed and row 50 raise a contraint exception, all rows above will not be executed.

SQLITE doku writes that "but changes caused by prior SQL statements within the same transaction are preserved and the transaction remains active".


mistachkin added on 2011-09-11 13:09:52 UTC:
This issue seems to involve the .NET Entity Framework and I am not an expert on that package; however, it seems that the TransactionScope class is normally unnecessary unless you have changes that span multiple object contexts (e.g. multiple SQLite databases), see:

http://stackoverflow.com/questions/794707/why-doesnt-transactionscope-work-with-entity-framework


mistachkin added on 2011-09-11 13:40:00 UTC:
Related: http://msdn.microsoft.com/en-us/library/bb738523.aspx


mistachkin added on 2011-09-11 13:42:27 UTC:
Also, there seems to be a problem with the SQLiteConnection.EnlistTransaction method that prevents TransactionScope from working with an ObjectContext if the code involved tries to access the database more than once from inside the same transaction.


mistachkin added on 2011-09-11 15:36:54 UTC:
An experimental fix for this issue is available in check-in [f4e1038098], with unit tests.

Would it be possible for you to test this fix in your environment and report back with the outcome of your testing?


anonymous added on 2011-09-12 08:47:13 UTC:
with this patch it works now like expected. The transaction raised the Exception if some commands fails, but other commands executed normal.


anonymous added on 2011-09-12 11:23:11 UTC:
After testing again error still exist. (preview comment is wrong)


mistachkin added on 2011-09-12 12:45:35 UTC:
Did you build the new DLLs from the source code on the "bug-ccfa69fc32" branch? If so, are you seeing precisely the same behavior as before or something slightly different now?


mistachkin added on 2011-09-12 20:23:38 UTC:

Ok, I think I've figured it out.  Thanks for your help troubleshooting.

Adding some diagnostic output to the System.Data.SQLite assembly, I see this output
(the SQL below is being prepared for execution):

PRAGMA legacy_file_format=OFF

PRAGMA synchronous=Normal

PRAGMA foreign_keys=OFF

BEGIN IMMEDIATE

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

SELECT
[c].[RegionID] AS [RegionID],
[c].[RegionDescription] AS [RegionDescription]
FROM [Regions] AS [c] LIMIT 1

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);

SQLite error (19): abort at 13 in [INSERT INTO [Territories]([TerritoryID], [TerritoryDescription], [RegionID])
 VALUES (@p0, @p1, @p2);]: PRIMARY KEY must be unique

A first chance exception of type 'System.Data.SQLite.SQLiteException' occurred in System.Data.SQLite.dll
A first chance exception of type 'System.Data.UpdateException' occurred in System.Data.Entity.dll

COMMIT

The program '[6896] testlinq.exe: Program Trace' has exited with code 0 (0x0).
The program '[6896] testlinq.exe: Managed (v4.0.30319)' has exited with code 0 (0x0). 

The above was produced using my test code with the list of IDs modified to include the
ones you added, as follows:

          long[] territoryIds = new long[] {
              1576, 1577, 1578, 1579, 1580, // NOTE: Success
              1581, 1730, 1833, 2116, 2139, // NOTE: Fail
              2140, 2141                    // NOTE: Skipped
          };

What happens deep down is that SQLite is returning an error code (19 or SQLITE_CONSTRAINT) from
the sqlite3_step() native function when attempting to INSERT the 16th row during the SaveChanges
step (i.e. the first ID violating the unique constraint, 1581).

This error causes the System.Data.SQLite code to throw an exception, terminating the rest of
the SaveChanges step because while SQLite defaults to continuing with the transaction, it does
abort the statement in progress causing the error and neither System.Data.SQLite nor the Entity
Framework has any way of knowing that it should ignore this failure.

Subsequently, the changes up until that point are successfully committed by the transaction scope;
however, the other changes are not.

The only real solution to this problem is changing the ON CONFLICT value to IGNORE;
however, I'm not sure how to get the Entity Framework to add that to its INSERT
statement(s) as I do not know enough about it or even if it is possible to do so.

Unfortunately, there is currently no way to change the default ON CONFLICT value (i.e.
it is always set on a per-statement basis).