{
"LiteOrm": {
"Default": "DefaultConnection",
"DataSources": [
{
"Name": "DefaultConnection",
"ConnectionString": "Server=(local);Database=TestDB;User Id=sa;Password=123456;",
"Provider": "System.Data.SqlClient.SqlConnection, System.Data.SqlClient",
"SqlBuilder": null,
"KeepAliveDuration": "00:10:00",
"PoolSize": 16,
"MaxPoolSize": 100,
"ParamCountLimit": 2000,
"SyncTable": false,
"ReadOnlyConfigs": [
{
"ConnectionString": "Server=replica;Database=TestDB;User Id=sa;Password=123456;",
"KeepAliveDuration": null,
"PoolSize": null,
"MaxPoolSize": null,
"ParamCountLimit": null
}
]
}
]
}
}
| Field | Type | Default | Description |
|---|---|---|---|
Default |
string |
— | Default data source name |
DataSources[].Name |
string |
— | Data source name (referenced by the DataSource parameter on [Table]) |
DataSources[].ConnectionString |
string |
— | Database connection string |
DataSources[].Provider |
string |
— | Fully qualified connection type name in the format TypeName, AssemblyName |
DataSources[].SqlBuilder |
string |
null |
Fully qualified SQL builder type name (optional; auto-matched from Provider when omitted) |
DataSources[].KeepAliveDuration |
TimeSpan |
00:10:00 |
Connection keep-alive duration (00:00:00 = unlimited) |
DataSources[].PoolSize |
int |
16 |
Maximum number of cached connections in the pool |
DataSources[].MaxPoolSize |
int |
100 |
Maximum concurrent connection limit |
DataSources[].ParamCountLimit |
int |
2000 |
Maximum SQL parameter count (0 = unlimited) |
DataSources[].SyncTable |
bool |
false |
Whether to automatically synchronize table creation |
DataSources[].ReadOnlyConfigs[] |
array |
[] |
Read-only replica configuration list (read/write splitting); omitted fields inherit from the primary data source |
// Basic registration
builder.Host.RegisterLiteOrm();
// Registration with options
builder.Host.RegisterLiteOrm(options =>
{
options.RegisterScope = true; // Default is true; automatically manages the DI scope lifecycle
options.Assemblies = new[] { typeof(MyService).Assembly }; // Restrict scanned assemblies (defaults to scanning all)
options.RegisterSqlBuilder("DefaultConnection", new MySqlBuilder()); // Register by data source name
options.RegisterSqlBuilder(typeof(SqlConnection), new MySqlBuilder()); // Register by connection type
});
[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("Age")]
public int Age { get; set; }
[Column("DeptId")]
[ForeignType(typeof(Department), Alias = "Dept")]
public int? DeptId { get; set; }
}
// View model (used for association queries)
public class UserView : User
{
[ForeignColumn(typeof(Department), Property = "DeptName")]
public string? DeptName { get; set; }
}
// Entity type == view type (EntityService<T>, equivalent to EntityService<T, T>)
public interface IUserService
: IEntityService<User>, IEntityServiceAsync<User>,
IEntityViewService<User>, IEntityViewServiceAsync<User>
{ }
public class UserService : EntityService<User>, IUserService { }
// Entity type != view type (EntityService<T, TView>; TView must inherit from T)
public interface IUserService
: IEntityService<User>, IEntityServiceAsync<User>,
IEntityViewService<UserView>, IEntityViewServiceAsync<UserView>
{ }
public class UserService : EntityService<User, UserView>, IUserService { }
| Method | Return type |
|---|---|
Insert(T entity) |
bool |
Update(T entity) |
bool |
UpdateOrInsert(T entity) |
bool |
Delete(T entity) |
bool |
DeleteID(object id, params string[] tableArgs) |
bool |
Delete(LogicExpr expr, params string[] tableArgs) |
int |
Update(UpdateExpr expr, params string[] tableArgs) |
int |
BatchInsert(IEnumerable<T> entities) |
void |
BatchUpdate(IEnumerable<T> entities) |
void |
BatchUpdateOrInsert(IEnumerable<T> entities) |
void |
BatchDelete(IEnumerable<T> entities) |
void |
Batch(IEnumerable<EntityOperation<T>> entities) |
void |
| Method | Return type |
|---|---|
InsertAsync(T entity, CancellationToken ct = default) |
Task<bool> |
UpdateAsync(T entity, CancellationToken ct = default) |
Task<bool> |
UpdateOrInsertAsync(T entity, CancellationToken ct = default) |
Task<bool> |
DeleteAsync(T entity, CancellationToken ct = default) |
Task<bool> |
DeleteIDAsync(object id, string[] tableArgs = null, CancellationToken ct = default) |
Task<bool> |
DeleteAsync(LogicExpr expr, string[] tableArgs = null, CancellationToken ct = default) |
Task<int> |
UpdateAsync(UpdateExpr expr, string[] tableArgs = null, CancellationToken ct = default) |
Task<int> |
BatchInsertAsync(IEnumerable<T> entities, CancellationToken ct = default) |
Task |
BatchUpdateAsync(IEnumerable<T> entities, CancellationToken ct = default) |
Task |
BatchUpdateOrInsertAsync(IEnumerable<T> entities, CancellationToken ct = default) |
Task |
BatchDeleteAsync(IEnumerable<T> entities, CancellationToken ct = default) |
Task |
BatchAsync(IEnumerable<EntityOperation<T>> entities, CancellationToken ct = default) |
Task |
| Method | Return type |
|---|---|
GetObject(object id, params string[] tableArgs) |
TView |
SearchOne(Expr expr, params string[] tableArgs) |
TView |
Search(Expr expr = null, params string[] tableArgs) |
List<TView> |
ForEach(Expr expr, Action<TView> func, params string[] tableArgs) |
void |
ExistsID(object id, params string[] tableArgs) |
bool |
Exists(Expr expr, params string[] tableArgs) |
bool |
Count(Expr expr = null, params string[] tableArgs) |
int |
| Method | Return type |
|---|---|
GetObjectAsync(object id, string[] tableArgs = null, CancellationToken ct = default) |
Task<TView> |
SearchOneAsync(Expr expr, string[] tableArgs = null, CancellationToken ct = default) |
Task<TView> |
SearchAsync(Expr expr = null, string[] tableArgs = null, CancellationToken ct = default) |
Task<List<TView>> |
ForEachAsync(Expr expr, Func<TView, Task> func, string[] tableArgs = null, CancellationToken ct = default) |
Task |
ExistsIDAsync(object id, string[] tableArgs = null, CancellationToken ct = default) |
Task<bool> |
ExistsAsync(Expr expr, string[] tableArgs = null, CancellationToken ct = default) |
Task<bool> |
CountAsync(Expr expr = null, string[] tableArgs = null, CancellationToken ct = default) |
Task<int> |
These methods come from
LambdaExprExtensionsand can be used without modifying the service class.
| Method | Return type |
|---|---|
Delete(Expression<Func<T, bool>> expression, params string[] tableArgs) |
int |
DeleteAsync(Expression<Func<T, bool>> expression, string[] tableArgs = null, CancellationToken ct = default) |
Task<int> |
Search(Expression<Func<TView, bool>> expression, string[] tableArgs = null) |
List<TView> |
Search(Expression<Func<IQueryable<TView>, IQueryable<TView>>> expression, string[] tableArgs = null) |
List<TView> |
SearchOne(Expression<Func<TView, bool>> expression, string[] tableArgs = null) |
TView |
SearchOne(Expression<Func<IQueryable<TView>, IQueryable<TView>>> expression, string[] tableArgs = null) |
TView |
Exists(Expression<Func<TView, bool>> expression, params string[] tableArgs) |
bool |
Count(Expression<Func<TView, bool>> expression, params string[] tableArgs) |
int |
SearchAsync(Expression<Func<TView, bool>> expression, string[] tableArgs = null, CancellationToken ct = default) |
Task<List<TView>> |
SearchAsync(Expression<Func<IQueryable<TView>, IQueryable<TView>>> expression, string[] tableArgs = null, CancellationToken ct = default) |
Task<List<TView>> |
SearchOneAsync(Expression<Func<TView, bool>> expression, string[] tableArgs = null, CancellationToken ct = default) |
Task<TView> |
SearchOneAsync(Expression<Func<IQueryable<TView>, IQueryable<TView>>> expression, string[] tableArgs = null, CancellationToken ct = default) |
Task<TView> |
ExistsAsync(Expression<Func<TView, bool>> expression, string[] tableArgs = null, CancellationToken ct = default) |
Task<bool> |
CountAsync(Expression<Func<TView, bool>> expression, string[] tableArgs = null, CancellationToken ct = default) |
Task<int> |
Additional note: Service query APIs are the
Exproverloads plus these Lambda extensions. If you needExprString, full SQL,SearchAs(...), or DataTable-oriented queries, switch to DAO.
| Method | Return type |
|---|---|
Insert(T entity) |
bool |
Update(T entity, object timestamp = null) |
bool |
Delete(T entity) |
bool |
Delete(LogicExpr expr) |
int |
Update(UpdateExpr expr) |
int |
BatchInsert(IEnumerable<T> entities) |
void |
BatchUpdate(IEnumerable<T> entities) |
void |
BatchDelete(IEnumerable<T> entities) |
void |
UpdateOrInsert(T entity) |
UpdateOrInsertResult |
BatchUpdateOrInsert(IEnumerable<T> entities) |
void |
InsertAsync(T entity, CancellationToken ct = default) |
Task<bool> |
UpdateAsync(T entity, object timestamp = null, CancellationToken ct = default) |
Task<bool> |
DeleteAsync(T entity, CancellationToken ct = default) |
Task<bool> |
DeleteAsync(LogicExpr expr, CancellationToken ct = default) |
Task<int> |
UpdateAsync(UpdateExpr expr, CancellationToken ct = default) |
Task<int> |
BatchInsertAsync(IEnumerable<T> entities, CancellationToken ct = default) |
Task |
BatchUpdateAsync(IEnumerable<T> entities, CancellationToken ct = default) |
Task |
BatchDeleteAsync(IEnumerable<T> entities, CancellationToken ct = default) |
Task |
UpdateOrInsertAsync(T entity, CancellationToken ct = default) |
Task<UpdateOrInsertResult> |
BatchUpdateOrInsertAsync(IEnumerable<T> entities, CancellationToken ct = default) |
Task |
DeleteByKeys(params object[] keys) |
bool |
DeleteByKeysAsync(object[] keys, CancellationToken ct = default) |
Task<bool> |
BatchDeleteByKeys(IEnumerable keys) |
void |
BatchDeleteByKeysAsync(IEnumerable keys, CancellationToken ct = default) |
Task |
EnumerableResult<T> supports: .ToList() / .ToListAsync() / .FirstOrDefault() / .FirstOrDefaultAsync() / .GetResult() / .GetResultAsync() / await foreach
| Method | Return type |
|---|---|
Search(Expr expr = null) |
EnumerableResult<T> |
Search(Expression<Func<IQueryable<T>, IQueryable<T>>> expr) |
EnumerableResult<T> |
Search(ref ExprString sqlBody, bool isFull = false) |
EnumerableResult<T> |
SearchAs<TResult>(SelectExpr selectExpr, Func<DbDataReader, TResult> readerFunc = null) |
EnumerableResult<TResult> |
SearchAs<TResult>(Expression<Func<IQueryable<T>, IQueryable<TResult>>> expr, Func<DbDataReader, TResult> readerFunc = null) |
EnumerableResult<TResult> |
SearchAs<TResult>(ref ExprString sqlBody) |
EnumerableResult<TResult> |
GetObject(params object[] keys) |
EnumerableResult<T> |
Count(Expr expr) |
ValueResult<int> |
Exists(object o) / Exists(T o) |
ValueResult<bool> |
ExistsKey(params object[] keys) |
ValueResult<bool> |
Exists(Expr expr) |
ValueResult<bool> |
DataTableResult supports: .GetResult() / .GetResultAsync()
| Method | Return type |
|---|---|
Search(Expr expr) |
DataTableResult |
Search(string[] propertyNames, Expr expr) |
DataTableResult |
Search(ref ExprString sqlBody, bool isFull = false) |
DataTableResult |
// Declarative
[Transaction]
public void Transfer() { ... }
// Manual
using var transaction = SessionManager.Current.BeginTransaction();
try { transaction.Commit(); }
catch { transaction.Rollback(); throw; }
[Table("Orders_{0}")]
public class Order : ObjectBase, IArged
{
public string[] GetArgs() => new string[] { (UserId % 10).ToString() };
}
[Table("Users", DataSource = "Secondary")]
public class User : ObjectBase { ... }
public interface IUserCustomDAO : IObjectViewDAO<UserView> { ... }
public class UserCustomDAO : ObjectViewDAO<UserView>, IUserCustomDAO { ... }
public interface IUserService : IEntityService<User>, IEntityServiceAsync<User> { ... }
public class UserService : EntityService<User>, IUserService { ... }
public interface ServiceFactory
{
IUserService UserService { get; }
IUserCustomDAO UserCustomDAO { get; }
}
services.AddServiceGenerator<ServiceFactory>();
var factory = scope.ServiceProvider.GetRequiredService<ServiceFactory>();
[ExceptionHook(typeof(OrderExceptionHook), Mode = ServiceExceptionHookMode.Notify)]
public interface IOrderService
{
Task SubmitAsync(long id);
}
[AutoRegister(Lifetime.Scoped, typeof(IServiceExceptionHook))]
public class OrderExceptionHook : IServiceExceptionHook
{
public void OnException(ServiceExceptionContext context)
{
// Access exception, method name, arguments, SQL stack, and more
}
}
[ExceptionHook] can be applied to methods, classes, and interfacesNotify is observe-only and must not swallow exceptionsHandle can convert the exception into a defined result through context.Handle(result)ExceptionHook runs before the global ServiceInvokeInterceptor.ExceptionHandling event| Attribute | Purpose |
|---|---|
[Table("TableName")] |
Specifies the table name; optional DataSource parameter |
[Column("ColName", IsPrimaryKey, IsIdentity)] |
Specifies the column name and property behavior |
[ForeignType(typeof(T), Alias, AutoExpand)] |
Specifies the foreign-key related type; AutoExpand extends relation paths |
[TableJoin(typeof(T), ForeignKeys, AliasName, AutoExpand)] |
Type-level relation definition supporting composite keys and path reuse |
[ForeignColumn(typeof(T), Property)] |
Column projected from a related table (for view models) |
[Transaction] |
Declarative transaction |
[ExceptionHook(typeof(THook), Mode = ...)] |
Declares a service exception hook for alerting or exception-to-result handling |
[AutoRegister] |
Automatically registers the type into the DI container |
| Style | Suitable for |
|---|---|
Lambda expression u => u.Age > 18 |
Simple conditions with compile-time type safety |
| Expr object (operators / fluent methods) | Complex conditions, dynamically accumulated conditions, and chained queries |
| ExprString interpolated string | Condition fragments or full SQL inside custom DAOs |
| Method | Return type | Description |
|---|---|---|
Expr.Prop("Name") |
PropertyExpr |
Property expression |
Expr.Prop("alias", "Name") |
PropertyExpr |
Property expression with a table alias |
Expr.Value(obj) |
ValueExpr |
Parameterized value |
Expr.Const(obj) |
ValueExpr |
Constant value (inlined into SQL, not parameterized) |
Expr.PropEqual("Name", value) |
LogicBinaryExpr |
Property equals value |
Expr.Func("ABS", expr) |
FunctionExpr |
Function call |
Expr.Aggregate("SUM", expr, isDistinct) |
FunctionExpr |
Aggregate function (IsAggregate=true) |
Expr.Concat(e1, e2) |
ValueSet |
CONCAT string composition (extension method) |
Expr.Lambda<T>(u => u.Age > 18) |
LogicExpr |
Converts Lambda to Expr |
Expr.From<T>(tableArgs) |
FromExpr |
Starting point for chained queries |
Expr.Sql("key", arg) |
GenericSqlExpr |
Dynamic SQL fragment |
Expr.Delete<T>(tableArgs) |
DeleteExpr |
Creates a DELETE expression |
Expr.If(condition, thenExpr, elseExpr) |
FunctionExpr |
IF expression |
Expr.Case(cases, elseExpr) |
FunctionExpr |
CASE expression |
Expr.Query<T>(expression) |
Expr |
Converts an IQueryable Lambda to Expr |
Expr.Query<T, TResult>(expression) |
Expr |
Converts an IQueryable Lambda with a return value to Expr |
Expr.Exists<T>(innerExpr, tableArgs) |
ForeignExpr |
EXISTS query on a related table |
Expr.Exists(type, innerExpr, tableArgs) |
ForeignExpr |
EXISTS query on a related table (explicit type) |
Expr.ExistsRelated<T>(innerExpr, tableArgs) |
ForeignExpr |
EXISTS query using automatic relation discovery |
Expr.ExistsRelated(type, innerExpr, tableArgs) |
ForeignExpr |
EXISTS query using automatic relation discovery (explicit type) |
Expr.Exists<T>(lambda) |
bool |
Used only inside Lambda expressions to build EXISTS queries (direct calls throw an exception) |
Expr.ExistsRelated<T>(lambda) |
bool |
Used only inside Lambda expressions to build automatic-related EXISTS queries (direct calls throw an exception) |
Additional rule:
PropertyExpr, TableExpr, ForeignExpr, FunctionExpr, SelectExpr, SelectItemExpr, CommonTableExpr, and GenericSqlExpr treat names and aliases as case-insensitive when comparing objects and calculating hash codes.Prop("User", "Name") and Prop("user", "name") are treated as the same expression, and aliases T0 and t0 are treated as the same alias.Operators on PropertyExpr / ValueTypeExpr:
| Operator | Description | Return type |
|---|---|---|
== != > < >= <= |
Comparison | LogicExpr |
+ - * / % |
Arithmetic | ValueTypeExpr |
-expr ~expr |
Unary negation / bitwise NOT | ValueTypeExpr |
Operators on LogicExpr:
| Operator | Description | Return type |
|---|---|---|
& |
AND (returns the other side when left or right is null, useful for dynamic accumulation) | AndExpr |
\| |
OR (returns the other side when left or right is null, useful for dynamic accumulation) | OrExpr |
! |
NOT | NotExpr |
| Category | Methods |
|---|---|
| Comparison | .Equal(v) .NotEqual(v) .GreaterThan(v) .LessThan(v) .GreaterThanOrEqual(v) .LessThanOrEqual(v) |
| Set | .In(IEnumerable) .In(params items) .In(Expr) |
| Range | .Between(low, high) |
| String | .Like(pattern) .Contains(text) .StartsWith(text) .EndsWith(text) |
| Null | .IsNull() .IsNotNull() |
| Alias | .As("alias") → SelectItemExpr |
| Aggregate | .Count(isDistinct) .Sum() .Avg() .Max() .Min() |
| Sort | .Asc() .Desc() → OrderByItemExpr |
.And(right) .Or(right) .Not()
Expr.From<T>() is the entry point and supports the following chained calls in SQL clause order:
using static LiteOrm.Common.Expr;
var query = From<User>()
.Where(Prop("Age") > 18) // WhereExpr
.GroupBy(Prop("DeptId")) // GroupByExpr
.Having(Prop("Id").Count() > 5) // HavingExpr
.Select(Prop("DeptId"), // SelectExpr
Prop("Id").Count().As("Cnt"))
.OrderBy(Prop("DeptId").Asc()) // OrderByExpr
.Section(0, 20); // SectionExpr (skip, take)
SelectExpr can be used for IN subqueries:
using static LiteOrm.Common.Expr;
// IN subquery
var subQuery = From<Department>()
.Where(Prop("Name") == "IT")
.Select("Id");
var expr = Prop("DeptId").In(subQuery);
UpdateExpr / DeleteExpr (used by ObjectDAO.Delete(LogicExpr) and similar APIs):
using static LiteOrm.Common.Expr;
var update = new UpdateExpr(From<User>(), Prop("Id") == 1);
update.Set(("UserName", Value("NewName")), ("Age", Value(30)));
var delete = new DeleteExpr(From<User>(), Prop("Age") < 18);
The interpolated string handler lets you embed Expr objects directly into DAO methods that accept ExprString, such as Search(...) and SearchAs(...). Service query APIs do not expose ExprString:
using static LiteOrm.Common.Expr;
// Embedded Expr objects are automatically converted into parameterized SQL fragments
var result = dao.Search($"WHERE {Prop("DeptName") == deptName} AND {Prop("Age") > 18}");
// Use full SQL through DAO together with isFull: true
var table = dataViewDao.Search(
$"SELECT [Id], [UserName] FROM [Users] WHERE {Prop("Age")} > {minAge}",
isFull: true
);
When hand-writing table or column names in
ExprString, you can use[and]as provider-agnostic quote placeholders. LiteOrm rewrites them to the current database’s real identifier delimiters right before command execution.
ExprStringdoes not auto-expandSelectExpr.With(name)/CommonTableExprintoWITHSQL. When you need CTE, prefer the structuredExpr/SelectExprpath; if you must useExprString, handwrite the fullWITH ... SELECT ...SQL.
using static LiteOrm.Common.Expr;
// Dynamically accumulate conditions (& is null-safe)
LogicExpr condition = null;
if (minAge.HasValue) condition &= Prop("Age") >= minAge.Value;
if (deptId.HasValue) condition &= Prop("DeptId") == deptId.Value;
if (!string.IsNullOrEmpty(name)) condition &= Prop("UserName").Contains(name);
var users = await dao.Search(condition).ToListAsync();
// EXISTS query on a related table
var expr = Exists<Department>(Prop("Name") == "IT");
// Equivalent Lambda form (inside a Lambda query):
var expr = Lambda<User>(u => Exists<Department>(d => d.Name == "IT"));
// SearchAs selects a subset of fields
var result = dao.SearchAs(
From<User>()
.Where(Prop("Age") > 18)
.Select("Id", "UserName")
).ToList();
ExistsRelated filters the primary table by conditions on a related table without requiring the related fields to be explicitly exposed on the view model.
Relation matching order: forward relations first; if no forward relation is found, reverse relations are tried. Multiple relation paths are combined with OR .
using static LiteOrm.Common.Expr;
// Filter users by related department
var expr = ExistsRelated<Department>(Prop("Name") == "IT");
var users = await objectViewDAO.Search(expr).ToListAsync();
// Lambda form
var lambdaExpr = Lambda<User>(u => ExistsRelated<Department>(d => d.Name == "IT"));