UpdateOnlyReturning() and DeleteReturning() return the rows affected by an UPDATE or DELETE in the same statement:
// The updated rows, as they are after the update
List<Order> shipped = db.UpdateOnlyReturning(() => new Order { Status = "Shipped" },
where: x => x.Status == "Packed");
// The deleted rows
List<Session> expired = db.DeleteReturning<Session>(x => x.ExpiresAt < DateTime.UtcNow);
Without them, getting the affected rows needs a separate query before or after the write, which costs another round trip and can see different rows if the data changes between the two statements.
Updating rows​
Update the fields in the expression and get back every column of each updated row:
var updated = db.UpdateOnlyReturning(() => new Book { Price = 20m }, where: x => x.Author == "J.R.R. Tolkien");
foreach (var book in updated)
{
Console.WriteLine($"{book.Title} now costs {book.Price}"); // all columns are populated
}
Use a SqlExpression for more complex conditions:
var q = db.From<Book>().Where(x => x.Genre == Genre.History && x.Available);
var updated = db.UpdateOnlyReturning(() => new Book { Available = false }, q);
An empty list is returned when no rows match.
Deleting rows​
var deleted = db.DeleteReturning<Book>(x => !x.Available);
Queries with joins are supported:
// Books reviewed by Bob
var q = db.From<Book>()
.Join<BookReview>((b, r) => b.Id == r.BookId)
.Where<BookReview>(r => r.Reviewer == "Bob");
var deleted = db.DeleteReturning(q);
Returning only selected columns​
Rows are returned with all their columns by default. Use returning to only read back the columns you need, e.g. for
tables with many or large columns. Other properties of the returned rows aren't populated:
var updated = db.UpdateOnlyReturning(() => new Book { Price = 20m },
where: x => x.Author == "J.R.R. Tolkien",
returning: x => new { x.Id, x.Title });
UPDATE "Book" SET "Price"=@Price WHERE ("Author" = @0) RETURNING "Id", "Title"
-- @0 = 'J.R.R. Tolkien', @Price = 20
Select a single column to just get the ids of the affected rows:
var deleted = db.DeleteReturning<Book>(x => !x.Available, returning: x => x.Id);
var ids = deleted.Map(x => x.Id);
DELETE FROM "Book" WHERE "Available"=0 RETURNING "Id"
It's available on every overload, including queries and the async APIs:
var q = db.From<Book>().Where(x => x.Genre == Genre.Science);
var updated = await db.UpdateOnlyReturningAsync(() => new Book { Price = 5m }, q,
returning: x => new { x.Title, x.Price });
Taking items from a queue​
Deleting and returning rows in one statement means the database guarantees each row is only returned to one caller, which makes it a simple way to take work from a queue table without locking:
// Only this worker receives these rows
List<EmailJob> jobs = db.DeleteReturning<EmailJob>(x => x.Queue == "emails" && x.RunAt <= DateTime.UtcNow);
foreach (var job in jobs)
{
await SendEmailAsync(job);
}
For long-running work that must survive crashes, mark rows as claimed with UpdateOnlyReturning() instead and
delete them when they've been processed:
var claimed = db.UpdateOnlyReturning(() => new EmailJob { ClaimedBy = workerId, ClaimedAt = DateTime.UtcNow },
where: x => x.ClaimedBy == null && x.Queue == "emails");
Async​
var updated = await db.UpdateOnlyReturningAsync(() => new Book { Price = 9m }, where: x => x.Genre == Genre.Science);
var deleted = await db.DeleteReturningAsync(db.From<Book>().Where(x => x.Price == 9m));
Which APIs modify objects​
OrmLite follows a consistent convention for when database generated values are written back to the objects you pass in:
| API | Modifies the object |
|---|---|
Save / SaveAll |
Yes: the auto-incremented Id and [RowVersion] |
Upsert / UpsertAll |
Yes: the auto-incremented Id, [RowVersion] and [ReturnOnInsert] fields |
Insert / InsertAll |
Only opt-in [AutoId] and [ReturnOnInsert] fields |
Update / UpdateOnly / Delete |
No |
UpdateOnlyReturning / DeleteReturning |
No, they return new rows |
Save() and Upsert() persist a given object and keep it in sync with its row, so it can be used directly in later
optimistic concurrency updates. Upsert() returns these values with RETURNING or OUTPUT in the same statement,
see Database generated values.
Returning values from Insert​
The values of a connection's write rules
are known before a row is written, so they're always set on the object. For values populated by the database,
Insert() doesn't modify the object unless its Data Model opts in with attributes:
public class Order
{
// Populated after Insert: a new Guid is generated for each row
[AutoId]
public Guid Id { get; set; }
// Not modified by Insert
public string Status { get; set; }
// Populated after Insert: the value set by the database default
[Default(OrmLiteVariables.SystemUtc), ReturnOnInsert]
public DateTime CreatedAt { get; set; }
}
var order = new Order { Status = "New" };
db.Insert(order);
order.Id; // the generated Guid
order.CreatedAt; // set by the database
[ReturnOnInsert] fields are returned in the same INSERT statement using RETURNING on PostgreSQL, SQLite and
Firebird and OUTPUT INSERTED on SQL Server. When a Data Model has [ReturnOnInsert] fields, its auto-incremented
Primary Key is also populated.
For other auto-incremented Primary Keys, use selectIdentity to return the new Id without modifying the object:
var id = db.Insert(new Customer { Name = "Alice" }, selectIdentity: true);
Or use Save(), which populates the new Id on the object:
var customer = new Customer { Name = "Alice" };
db.Save(customer);
customer.Id; // the new Id
RDBMS support​
| RDBMS | SQL |
|---|---|
| PostgreSQL | UPDATE ... RETURNING / DELETE ... RETURNING |
| SQLite 3.35+ | UPDATE ... RETURNING / DELETE ... RETURNING |
| SQL Server | UPDATE ... OUTPUT INSERTED.* / DELETE ... OUTPUT DELETED.* |
| MySQL, MariaDB, Oracle, Firebird | NotSupportedException |
Unsupported RDBMS throw a NotSupportedException before anything is updated or deleted.
WARNING
SQL Server doesn't allow an OUTPUT clause on tables with enabled triggers, as the rows can't be returned without
also writing them to a table (OUTPUT ... INTO). Use UpdateOnly() or Delete() on those tables instead.