This page focuses on LiteOrm’s write operations: insert, update, delete, upsert, and batching. For query capabilities, please refer to the Query Guide.
var user = new User
{
UserName = "admin",
Age = 30,
CreateTime = DateTime.Now,
DeptId = 1
};
bool success = await userService.InsertAsync(user);
await userService.BatchInsertAsync(users);
LiteOrm.Demo\Data\DbInitializer.cs uses batch inserts to initialize departments, users, and sales records. This pattern is ideal for seed data initialization, demo data generation, or pre-import data preparation:
var depts = new List<Department>
{
new() { Id = 1, Name = "Headquarters" },
new() { Id = 2, Name = "R&D Center", ParentId = 1 },
new() { Id = 3, Name = "Marketing", ParentId = 1 }
};
await deptService.BatchInsertAsync(depts);
var users = new List<User>
{
new() { Id = 1, UserName = "Admin", Age = 35, CreateTime = DateTime.Now, DeptId = 1 },
new() { Id = 2, UserName = "Lead Dev", Age = 32, CreateTime = DateTime.Now, DeptId = 2 }
};
await userService.BatchInsertAsync(users);
bool success = await userService.UpdateOrInsertAsync(user);
Console.WriteLine(success); // true indicates successful execution
If you need to distinguish between insert and update, use the DAO layer’s UpdateOrInsertResult instead.
await userService.BatchUpdateOrInsertAsync(users);
The following example is extracted from LiteOrm.Tests\ServiceTests.cs: the same batch contains entities that need updating (already exist) and entities that need inserting (new).
var users = new List<TestUser>
{
new TestUser { Name = "Upsert A", Age = 10, CreateTime = DateTime.Now },
new TestUser { Name = "Upsert B", Age = 20, CreateTime = DateTime.Now }
};
await service.BatchInsertAsync(users);
var existingUser = users[0];
existingUser.Age = 15; // Update existing record
var newUser = new TestUser
{
Name = "Upsert C",
Age = 30,
CreateTime = DateTime.Now
};
await service.BatchUpdateOrInsertAsync(new[] { existingUser, newUser });
After execution, Upsert A will be updated and Upsert C will be inserted.
var user = await userService.SearchOneAsync(u => u.Id == 1);
user.UserName = "admin_v2";
await userService.UpdateAsync(user);
foreach (var user in users)
{
user.Age += 1;
}
await userService.BatchUpdateAsync(users);
LiteOrm.Demo\Data\DbInitializer.cs queries departments first, then modifies managers in bulk, and finally commits in one batch:
var updateDepts = new List<Department>();
async Task MarkManager(int deptId, int managerId)
{
var dept = await deptService.GetObjectAsync(deptId);
if (dept != null)
{
dept.ManagerId = managerId;
updateDepts.Add(dept);
}
}
await MarkManager(1, 1);
await MarkManager(2, 2);
await MarkManager(4, 6);
await deptService.BatchUpdateAsync(updateDepts);
This pattern is suitable for admin backend scenarios where you “read entities first, modify multiple objects, then batch commit.”
timestampIf you want an update to verify that the version read earlier still matches the current database row, declare a timestamp column and use the ObjectDAO<T> overloads Update(entity, timestamp) or UpdateAsync(entity, timestamp).
[Table("Users")]
public class User : ObjectBase
{
[Column("Id", IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
[Column("UserName")]
public string? UserName { get; set; }
[Column("Version", IsTimestamp = true)]
public int Version { get; set; }
}
var dao = serviceProvider.GetRequiredService<ObjectDAO<User>>();
var viewDao = serviceProvider.GetRequiredService<ObjectViewDAO<User>>();
var user = await viewDao.GetObject(1).FirstOrDefaultAsync();
int originalVersion = user.Version;
user.UserName = "admin_v2";
user.Version = originalVersion + 1; // the value on the entity is written back
bool updated = await dao.UpdateAsync(user, originalVersion);
if (!updated)
{
Console.WriteLine("Concurrency conflict: the row was changed by someone else.");
}
Important behavior details:
Version value on the entity is the new value written to the database.timestamp argument is the original value and is added to the WHERE clause for concurrency checking.false usually means the primary key matched but the timestamp no longer matched.IEntityService<T> / IEntityServiceAsync<T> update methods do not expose a timestamp overload. For optimistic concurrency, use ObjectDAO<T> directly or wrap it in a custom service.BatchUpdate / BatchUpdateAsync do not automatically apply timestamp concurrency checks.timestamp Update Example from TestsSee:
LiteOrm.Tests\ObjectDAOTests.csLiteOrm.Tests\Models\TestTimestampUser.csusing static LiteOrm.Common.Expr;
await objectDao.UpdateAsync(
Update<User>()
.Where(Prop("Age") < 18)
.Set(
("Age", Value(18)),
("CreateTime", Value(DateTime.Now))
)
);
LiteOrm.Demo\Demos\UpdateExprDemo.cs demonstrates several typical uses of UpdateExpr:
using static LiteOrm.Common.Expr;
var update = new UpdateExpr(new TableExpr(typeof(User)), Prop("UserName") == "UpdateDemo_Bob")
.Set(("Age", Const(35)));
int affected = await userService.UpdateAsync(update);
You can also write arithmetic expressions or function expressions directly in the SET clause:
using static LiteOrm.Common.Expr;
var agePlusFive = new UpdateExpr(new TableExpr(typeof(User)), Prop("UserName") == "UpdateDemo_Carol")
.Set(("Age", Prop("Age") + Const(5)));
var rename = new UpdateExpr(new TableExpr(typeof(User)), Prop("UserName") == "UpdateDemo_Bob")
.Set(("UserName", Func("CONCAT", Prop("UserName"), Const("_v2"))));
var user = await userService.SearchOneAsync(u => u.Id == 1);
await userService.DeleteAsync(user);
await userService.DeleteAsync(1);
await userService.BatchDeleteAsync(users);
await userService.BatchDeleteIDAsync(new[] { 1, 2, 3 });
LiteOrm.Tests\ServiceTests.cs has a complete validation cycle suitable for copying:
using static LiteOrm.Common.Expr;
var users = new List<TestUser>
{
new TestUser { Name = "Batch 1", Age = 10, CreateTime = DateTime.Now },
new TestUser { Name = "Batch 2", Age = 20, CreateTime = DateTime.Now }
};
await service.BatchInsertAsync(users);
var inserted = await viewService.SearchAsync(Lambda<TestUser>(u => u.Name!.StartsWith("Batch")));
foreach (var user in inserted)
user.Age += 5;
await service.BatchUpdateAsync(inserted);
await service.BatchDeleteAsync(inserted);
This example is ideal for validating that batch interfaces cover the entire path of “insert → update → delete.”
using static LiteOrm.Common.Expr;
await userService.DeleteAsync(u => u.CreateTime < DateTime.Today.AddYears(-1));
await objectDao.Delete(Prop("Age") < 18 & Prop("UserName").StartsWith("Temp"));
The following example is extracted from sharding tests, but the delete conditions themselves apply to regular tables as well:
int deleted = await service.DeleteAsync(
l => l.Amount > 400 && l.Event == "DeleteEvent",
tableArgs: new[] { "202401" }
);
For non-sharded tables, simply remove the tableArgs parameter.
| Method Type | Common Return Value | Meaning |
|---|---|---|
Insert/Update/Delete |
bool |
Whether execution was successful. |
| Conditional update/delete | int |
Number of affected rows. |
Service layer UpdateOrInsert |
bool |
Whether execution was successful. |
DAO layer UpdateOrInsert |
UpdateOrInsertResult |
Indicates whether this was an insert or update. |
IEntityService<T> / IEntityServiceAsync<T>Insert / InsertAsyncUpdate / UpdateAsyncDelete / DeleteAsyncBatchInsert / BatchInsertAsyncBatchUpdate / BatchUpdateAsyncBatchDelete / BatchDeleteAsyncUpdateOrInsert / UpdateOrInsertAsyncIf you also need conditional search, pagination, Exists, Count, etc., please refer to the Query Guide.
In addition to homogeneous operations like BatchInsertAsync, BatchUpdateAsync, and BatchDeleteAsync, LiteOrm also supports putting different operations into the same batch.
var newUser = new TestUser { Name = "Mixed 1", Age = 10, CreateTime = DateTime.Now };
var ops = new List<EntityOperation<TestUser>>
{
new EntityOperation<TestUser> { Entity = newUser, Operation = OpDef.Insert },
new EntityOperation<TestUser> { Entity = existingUser, Operation = OpDef.Delete }
};
await service.BatchAsync(ops);
This example from LiteOrm.Tests\ServiceTests.cs is suitable for “insert new batch of data while deleting old data” sync migration scenarios.
Expr.Update<T>() and DAO.