使用C#中的SQLite.NET获取最后插入的ID

30

我有一个简单的问题,但解决方案并不简单...当前,我正在将一些数据插入到数据库中,就像这样:

kompenzacijeDataSet.KompenzacijeRow kompenzacija = kompenzacijeDataSet.Kompenzacije.NewKompenzacijeRow();
kompenzacija.Datum = DateTime.Now;
kompenzacija.PodjetjeID = stranka.id;
kompenzacija.Znesek = Decimal.Parse(tbZnesek.Text);

kompenzacijeDataSet.Kompenzacije.Rows.Add(kompenzacija);

kompenzacijeDataSetTableAdapters.KompenzacijeTableAdapter kompTA = new kompenzacijeDataSetTableAdapters.KompenzacijeTableAdapter();
kompTA.Update(this.kompenzacijeDataSet.Kompenzacije);

this.currentKompenzacijaID = LastInsertID(kompTA.Connection);
最后一行很重要。为什么我要提供一个连接?因为有一个 SQLite 函数叫做 last_insert_rowid(),你可以调用它并获得最后插入的 ID。问题是它绑定在一个连接上,而 .NET 似乎为每个数据集操作重新打开和关闭连接。我认为从表适配器获取连接会改变这种情况。但事实并非如此。是否有人知道如何解决这个问题?也许从哪里获取一个不变的连接?或者还有更加优雅的解决方案吗?谢谢。编辑:这也是事务的一个问题,如果想要使用事务,我需要相同的连接,所以这也是一个问题...
11个回答

89

使用C# (.net 4.0)和SQLite,SQLiteConnection类有一个属性LastInsertRowId,它等于最近插入(或更新)元素的主整数键。

如果表没有主整数键,则返回rowID(在这种情况下,rowID列会自动创建)。

更多信息请参见https://www.sqlite.org/c3ref/last_insert_rowid.html

至于将多个命令封装到单个事务中,任何在事务开始之后输入且在提交之前的命令都是一个事务的一部分。

long rowID;
using (SQLiteConnection con = new SQLiteConnection([datasource])
{
    SQLiteTransaction transaction = null;
    transaction = con.BeginTransaction();

    ... [execute insert statement]

    rowID = con.LastInsertRowId;

    transaction.Commit()
}

6
需要更多赞。就我所见,这是新应用程序的正确方向。 - Manuel Hoffmann
SQLiteAsyncConnection 没有那个属性,但是如果您传入一个要保存的对象,该对象的主 ID 属性将会被更新。 - Ted Nyberg

56
select last_insert_rowid();

你需要将其作为标量查询执行。

string sql = @"select last_insert_rowid()";
long lastId = (long)command.ExecuteScalar(sql); // Need to type-cast since `ExecuteScalar` returns an object.

2
rowid 应该转换为 Int64 类型: var result = (Int64)command.ExecuteScalar(sql); - Johnny
我发现这个答案不起作用。以下答案(由Alex Smith提供)有效。 - ttom

9

我正在使用Microsoft.Data.Sqlite包,但是我没有看到LastInsertRowId属性。但是,您不必创建第二个查询以获取最后一个id。相反,将两个SQL语句组合成一个字符串。

string sql = @"
    insert into MyTable values (null, @name);  
    select last_insert_rowid();";

using (var cmd = conn.CreateCommand()) {
    cmd.CommandText = sql;
    cmd.Parameters.Add("@name", SqliteType.Text).Value = "John";
    int lastId = Convert.ToInt32(cmd.ExecuteScalar());
}

2
Dotnet 5.0 w Microsoft.Data.Sqlite pkg 5.0.1。这是唯一对我有效的解决方案,因为SqliteConnection类上不存在“LastInsertRowId”属性。 - Daniel Szabo
这个问题也非常有帮助,解释了为什么这种方法有效: https://www.codeproject.com/Questions/830792/sqlite-Last-Insert-Rowid-returns-always - Daniel Szabo

8

last_insert_rowid()是解决问题的一部分。它返回行号,而不是实际的ID。

cmd = CNN.CreateCommand();
cmd.CommandText = "SELECT last_insert_rowid()";
object i = cmd.ExecuteScalar();

cmd.CommandText = "SELECT " + ID_Name + " FROM " + TableName + " WHERE rowid=" + i.ToString();
i = cmd.ExecuteScalar();

1
这似乎不是真的。我创建了一个带有“id INTEGER PRIMARY KEY”的表,它总是返回最后插入行的生成ID,而不是行号。 - Jez
我同意上面的评论。不需要第二个查询。 - Ernie Thomason

3

似乎有关于Microsoft的参考和SQLite的参考的答案,这就是一些人能够使LastInsertRowId属性正常工作而其他人无法做到的原因。

个人而言,我不使用PK,因为它只是rowid列的别名。使用rowid比创建一个PK要快约两倍。如果我有一个TEXT列作为PK,我仍然会使用rowid,并将文本列设置为唯一的。(仅适用于SQLite 3.对于v1和v2,需要自己创建,因为vacuum将更改rowid编号)

话虽如此,从最后插入的记录中获取信息的方法如下代码所示。由于该函数针对自身进行左连接,因此为了速度,我将其限制为1,即使您没有这样做,也只会有来自主SELECT语句的1条记录。

SELECT my_primary_key_column FROM my_table
WHERE rowid in (SELECT last_insert_rowid() LIMIT 1);

2
< p > SQLiteConnection 对象有一个属性,因此不需要额外的查询。 在 INSERT 之后,您只需使用用于 INSERT 命令的 SQLiteConnection 对象的 LastInsertRowId 属性即可。 LastInsertRowId 属性的类型为 Int64。 当然,正如您已经知道的那样,要使自动递增起作用,表上的主键必须设置为 AUTOINCREMENT 字段,这是另一个话题。


实际上,这并不完全正确。AUTOINCREMENT 仅保证 id 不会被重复使用。INTEGER PRIMARY KEY 字段也会返回自动递增的 RowId,但如果该值以前已被删除,则可能会被重复使用。更多信息请参见:https://www.sqlite.org/autoinc.html - Michael Silver
1
感谢,在使用Net Core版本的库的上下文中非常有帮助。SQLiteAsyncConnection.InsertAsync方法调用返回一个Task<int>作为主键ID。 - Timothy Lee Russell
不确定那是否正确,就我所看,它仍然返回受影响的行数。 - Ted Nyberg

1
    database = new SQLiteConnection(databasePath);

    public int GetLastInsertId()
    {
        return (int)SQLite3.LastInsertRowid(database.Handle);
    }

0
在 EF Core 5 中,您可以在对象本身中获取 ID,而无需使用任何“最后插入”的方法。
例如:
var r = new SomeData() { Name = "New Row", ...};

dbContext.Add(r);

dbContext.SaveChanges();

Console.WriteLine(r.ID);

你可以获得新的ID,而无需考虑使用正确的连接或线程安全等问题。


0
如果您正在使用Microsoft.Data.Sqlite包,则在SqliteConnection类中不包括LastInsertRowId属性,但是您仍然可以通过使用底层的SQLitePCL库调用last_insert_rowid函数。这是一个扩展方法:
using Microsoft.Data.Sqlite;
using SQLitePCL;

public static long GetLastInsertRowId(this SqliteConnection connection)
{
    var handle = connection.Handle ?? throw new NullReferenceException("The connection is not open.");
    return raw.sqlite3_last_insert_rowid(handle);
}

0
# How about just running 2x SQL statements together using Execute Scalar?
# Person is a object that has an Id and Name property

var connString = LoadConnectionString(); // get connection string
using (var conn = new SQLiteConnection(connString)) // connect to sqlite
{

    // insert new record and get Id of inserted record
    var sql = @"INSERT INTO People (Name) VALUES (@Name);
                SELECT Id FROM People
                ORDER BY Id DESC";

    var lastId = conn.ExecuteScalar(sql, person);
}

其实那并不是一个坏的解决方案,但根据具体情况实现起来可能会给数据库带来更多的工作量。但是,在插入多个项目时可能会非常有用。 - Felix K.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接