System.Data.SQLite

Login
This project makes use of Eagle, provided by Mistachkin Systems.
Eagle: Secure Software Automation
Ticket Hash: 41aea496e05b1b4891786c3a05aa240f1705423e
Title: EF6 fails to insert row with GUID as PK column
Status: Closed Type: Code_Defect
Severity: Important Priority: Blocker
Subsystem: LINQ Resolution: Fixed
Last Modified: 2015-08-19 04:32:06
Version Found In: 1.0.97.0
User Comments:
anonymous added on 2015-08-04 17:50:47: (text/html)
<p>Hi,</p>

<p>I have the problem, that I cannot use GUID PKs in EF6, because EF6 creates SQL statements like:</p>

<pre>
INSERT INTO [TESTTABLE] ([ITEM]) VALUES ('test2');
SELECT [TESTTABLEID] FROM [TESTTABLE] WHERE last_rows_affected() > 0 AND [TESTTABLEID] = last_insert_rowid();
</pre>

<p>The table is defined as:</p>

<pre>
CREATE TABLE [TESTTABLE] (
    [TESTTABLEID] BLOB(16) PRIMARY KEY DEFAULT (GEN_UUID()),
    [ITEM] TEXT NOT NULL
);
</pre>

<p>The GEN_UUID function is a user defined scalar function.</p>

<p>I also tried the development version as of <a href="http://localhost:8080/timeline?c=dc0e4e31d4fe0575&unhide">dc0e4e31d4fe0575</a></p>.

<p>This is the complete source code:</p>

<pre>
    class Program
    {
        static void Main(string[] args)
        {
            Environment.SetEnvironmentVariable("AppendManifestToken_SQLiteProviderManifest", ";BinaryGUID=True;");
            TestGenUuid();
        }

        private static void TestGenUuid()
        {
            using (var conn = new SQLiteConnection("Data Source=:memory:;BinaryGUID=True"))
            {
                conn.Open();
                conn.BindFunction(new SQLiteFunctionAttribute("GEN_UUID", 0, FunctionType.Scalar), new GenUuidFunction());
                ExecuteScript(conn, @"
CREATE TABLE [TESTTABLE] (
    [TESTTABLEID] BLOB(16) PRIMARY KEY DEFAULT (GEN_UUID()),
    [ITEM] TEXT NOT NULL
);
INSERT INTO [TESTTABLE] ([ITEM]) VALUES ('test');
");
                var temp = ExecuteScalar(conn, "SELECT [TESTTABLEID] FROM [TESTTABLE] WHERE [ITEM]='test'");
                Debug.Assert(temp != null);
                var result = new Guid((byte[])temp);
                Debug.Assert(result != Guid.Empty);
                temp = ExecuteScalar(conn, @"
INSERT INTO [TESTTABLE] ([ITEM]) VALUES ('test2');
SELECT [TESTTABLEID] FROM [TESTTABLE] WHERE last_rows_affected() > 0 AND [TESTTABLEID] = last_insert_rowid();");
                Debug.Assert(temp != null);
                result = new Guid((byte[])temp);
                Debug.Assert(result != Guid.Empty);
            }
        }

        private static object ExecuteScalar(SQLiteConnection conn, string script)
        {
            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText = script;
                cmd.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
                return cmd.ExecuteScalar();
            }
        }

        private static void ExecuteScript(SQLiteConnection conn, string script)
        {
            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText = script;
                cmd.ExecuteNonQuery();
            }
        }

        public class GenUuidFunction : SQLiteFunction
        {
            public override object Invoke(object[] args)
            {
                return Guid.NewGuid().ToByteArray();
            }
        }
    }
</pre>

mistachkin added on 2015-08-04 22:56:17: (text/x-fossil-plain)
To clarify: Did this used to work in a previous version of System.Data.SQLite?

mistachkin added on 2015-08-18 18:45:01: (text/x-fossil-plain)
This query is incorrect:

SELECT [TESTTABLEID]
FROM [TESTTABLE]
WHERE last_rows_affected() > 0
AND [TESTTABLEID] = last_insert_rowid();

The type of TESTTABLEID is BLOB (i.e. GUID) and the type of last_insert_rowid()
is *always* INT64.  This query can never produce a valid result.

mistachkin added on 2015-08-18 18:47:14: (text/x-fossil-plain)
Try this instead:

SELECT [TESTTABLEID] FROM [TESTTABLE] WHERE rowid = last_insert_rowid();

mistachkin added on 2015-08-18 18:54:02: (text/x-fossil-plain)
Since this query is actually generated by the LINQ assembly, this is a problem;
however, this is not a new issue.

mistachkin added on 2015-08-18 19:03:29: (text/x-fossil-plain)
The solution to this problem is to modify the
DmlSqlGenerator.GenerateReturningSql method to
detect whether or not a table has a simple int64
primary key.  If it does, the last_insert_rowid()
function can be used; otherwise, all the key columns
for the primary key (composite or otherwise) must be
used to return the appropriate single row.

mistachkin added on 2015-08-19 04:24:19: (text/x-fossil-plain)
Fixed via branch check-in [8ea5fcd6b9d89425].  This fix will be included in the
1.0.98.0 release.

mistachkin added on 2015-08-19 04:32:06: (text/x-fossil-plain)
Now fixed on "preRelease" branch via check-in [c1baff799b6affc3].