安装数据库驱动
需要访问什么数据库,就安装对应的 FreeSql.Provider.XX
,也可直接安装 FreeSql.All
Package Name | 说明 |
---|---|
FreeSql.Provider.MySql | 基于 MySql.Data(Oracle 官方) |
FreeSql.Provider.MySqlConnector | 基于 MySqlConnector(开源社区,推荐++) MySQL, MariaDB, Percona, Amazon Aurora, Azure Database for MySQL, Google Cloud SQL for MySQL, OceanBase, Doris, Tidb 等等 |
FreeSql.Provider.PostgreSQL | 基于 PostgreSQL 9.5+ |
FreeSql.Provider.SqlServer | 基于 SqlServer 2005+ |
FreeSql.Provider.SqlServerForSystem | 基于 System.Data.SqlClient + SqlServer 2005+ |
FreeSql.Provider.Sqlite | 基于 System.Data.SQLite.Core |
FreeSql.Provider.SqliteCore | 基于 Microsoft.Data.Sqlite.Core,需安装 bundle_xxx |
FreeSql.Provider.Duckdb | 基于 DuckDB.NET.Data.Full |
FreeSql.Provider.ClickHouse | 基于 ClickHouse.Client |
FreeSql.Provider.QuestDb | 基于 Npgsql 和 RestApi |
FreeSql.Provider.Oracle | |
FreeSql.Provider.OracleOledb | 基于 Oledb 解决 US7ASCII 中文乱码问题 |
FreeSql.Provider.Firebird | |
FreeSql.Provider.MsAccess | |
FreeSql.Provider.Dameng | 基于 达梦数据库 |
FreeSql.Provider.ShenTong | 基于 神舟通用数据库 |
FreeSql.Provider.KingbaseES | 基于 人大金仓数据库 |
FreeSql.Provider.GBase | 基于 南大通用GBase数据库 |
FreeSql.Provider.Xugu | 基于 虚谷数据库 |
FreeSql.Provider.Odbc | 基于 ODBC |
FreeSql.Provider.Custom | 自定义适配 SqlServer2000, PolarDB, KunDB, 其它国产数据库 等等 |
创建数据库连接
asp.net core
csharp
var builder = WebApplication.CreateBuilder(args);
Func<IServiceProvider, IFreeSql> fsqlFactory = r =>
{
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=freedb.db")
.UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}"))
.UseAutoSyncStructure(true) //自动同步实体结构到数据库,只有CRUD时才会生成表
.Build();
return fsql;
};
builder.Services.AddSingleton<IFreeSql>(fsqlFactory);
WebApplication app = builder.Build();
.net freamework
csharp
//注意:class DB<T> 泛型类不适合定义 static 单例
public class DB
{
static Lazy<IFreeSql> sqliteLazy = new Lazy<IFreeSql>(() =>
{
var fsql = new FreeSql.FreeSqlBuilder()
.UseMonitorCommand(cmd => Trace.WriteLine($"Sql:{cmd.CommandText}"))
.UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=freedb.db")
.UseAutoSyncStructure(true) //自动同步实体结构到数据库,只有CRUD时才会生成表
.Build();
return fsql;
});
public static IFreeSql Sqlite => sqliteLazy.Value;
}
注意:谨慎、谨慎、谨慎在生产环境中使用 UseAutoSyncStructure
配置数据库
csharp
public class DbConfig
{
public string DbKey { get; set; }
public FreeSql.DataType DataType { get; set; }
public bool SyncStructure { get; set; } = false;
public string ConnectionString { get; set; }
}
var fsql = new FreeSqlCloud<string>();
foreach (var item in dbConfigs)
{
fsql.Register(item.DbKey, () => new FreeSqlBuilder()
.UseConnectionString(item.DataType, item.ConnectionString)
.UseAutoSyncStructure(item.SyncStructure)
.Build());
}
操作数据库
** FreeSqlCloud
必须定义成单例模式**
new FreeSqlCloud<DbEnum>()
多连接管理
new FreeSqlCloud<DbEnum>("myapp")
开启 TCC/SAGA 事务生效
FreeSqlCloud
的访问方式和 IFreeSql
一样:
csharp
fsql.Select<T>();
fsql.Insert<T>();
fsql.Update<T>();
fsql.Delete<T>();
切换数据库:
fsql.Change(DbEnum.db3).Select<T>();
//同一线程,或异步await 后续 fsql.Select/Insert/Update/Delete 操作是 db3
fsql.Use(DbEnum.db3).Select<T>();
//单次有效
表与实体
csharp
[Table(Name ="rswl_user_info")]
public class UserModel
{
[Column(IsPrimary =true,Identity=true)]
public int UserId { get; set; }
public string UserName { get; set; }
}
Repository
仓储
安装 FreeSql.DdContext
Select/Attach
快照对象,Update
只更新变化的字段;- Insert 插入数据,适配各数据库优化执行
ExecuteAffrows/ExecuteIdentity/ExecuteInserted
; - 级联保存、级联删除(一对一、一对多、多对多);
- 仓储 + 工作单元设计模式,风格简洁、统一;
临时仓储,用完就扔掉
csharp
var curd=fsql.GetRepository<T>();
泛型仓储
c#
//先看入门文档注入 IFreeSql
services.AddFreeRepository();
//在控制器使用泛型仓储
public SongsController(IBaseRepository<Song> songRepository)
{
}
继承仓储
c#
//先看入门文档注入 IFreeSql
services.AddFreeRepository(typeof(SongRepository).Assembly); //如果没有继承的仓储,第二个参数不用传
//使用继承的仓储
public SongsController(SongRepository repo1, TopicRepository repo2)
{
}
public class SongRepository : BaseRepository<Song>
{
public SongRepository(IFreeSql fsql) : base(fsql) {}
//在这里增加 CURD 以外的方法
}
更新
c#
var repo=fsql.GetRepository<User>();
//方式一:先查询,再更新
var item=repo.Where(a=>a.Id==1).First();
item.Name="xxx";
repo.Update(item);
//方式二:使用Attach
var item=new User{Id=1};
repo.Attach(item);
item.Name="xxx";
repo.Update(item);
获取变化属性
c#
repo.CompareState(item);
/// <summary>
/// 比较实体,计算出值发生变化的属性,以及属性变化的前后值
/// </summary>
/// <param name="newdata">最新的实体对象,它将与附加实体的状态对比</param>
/// <returns>key: 属性名, value: [旧值, 新值]</returns>
Dictionary<string, object[]> CompareState(TEntity newdata);
实例:
c#
services.AddSingleton(fsql);
services.AddScoped(typeof(IBaseRepository<>), typeof(Repository<>));
services.AddScoped(typeof(IBaseRepository<,>), typeof(Repository<,>));
public class Repository<TEntity, TKey> : BaseRepository<TEntity, TKey> where TEntity : class
{
public MyRepository(IFreeSql fsql) : base(fsql)
{
}
}
public class Repository<TEntity> : Repository<TEntity, long> where TEntity : class
{
public Repository(IFreeSql fsql) : base(fsql) { }
}
方法 | 返回值 | 参数 | 说明 |
---|---|---|---|
AsType | void | Type | 改变仓储正在操作的实体类型 |
Get | TEntity | TKey | 根据主键,查询数据 |
Find | TEntity | TKey | 根据主键,查询数据 |
Delete | int | TKey | 根据主键删除数据 |
Delete | int | Lambda | 根据 lambda 条件删除数据 |
Delete | int | TEntity | 删除数据 |
Delete | int | IEnumerable<TEntity> | 批量删除数据 |
DeleteCascadeByDatabase | List<object> | Lambda | 根据导航属性递归数据库删除数据 |
Insert | - | TEntity | 插入数据,若实体有自增列,插入后的自增值会填充到实体中 |
Insert | - | IEnumerable<TEntity> | 批量插入数据 |
Update | - | TEntity | 更新数据 |
Update | - | IEnumerable<TEntity> | 批量更新数据 |
InsertOrUpdate | - | TEntity | 插入或更新数据 |
FlushState | - | 无 | 清除状态管理数据 |
Attach | - | TEntity | 附加实体到状态管理,可用于不查询就更新或删除 |
Attach | - | IEnumerable<TEntity> | 批量附加实体到状态管理 |
AttachOnlyPrimary | - | TEntity | 只附加实体的主键数据到状态管理 |
BeginEdit | - | List<TEntity> | 准备编辑一个 List 实体 |
EndEdit | int | 无 | 完成编辑数据,进行保存动作 |
csharp
TEntity Get(TKey id);
TEntity Find(TKey id);
int Delete(TKey id);
IDataFilter<TEntity> DataFilter { get; }
ISelect<TEntity> Select { get; }
ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp);
ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp);
TEntity Insert(TEntity entity);
List<TEntity> Insert(IEnumerable<TEntity> entitys);
/// <summary>
/// 清空状态数据
/// </summary>
void FlushState();
/// <summary>
/// 附加实体,可用于不查询就更新或删除
/// </summary>
/// <param name="entity"></param>
void Attach(TEntity entity);
void Attach(IEnumerable<TEntity> entity);
/// <summary>
/// 附加实体,并且只附加主键值,可用于不更新属性值为null或默认值的字段
/// </summary>
/// <param name="data"></param>
IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data);
/// <summary>
/// 比较实体,计算出值发生变化的属性,以及属性变化的前后值
/// </summary>
/// <param name="newdata">最新的实体对象,它将与附加实体的状态对比</param>
/// <returns>key: 属性名, value: [旧值, 新值]</returns>
Dictionary<string, object[]> CompareState(TEntity newdata);
int Update(TEntity entity);
int Update(IEnumerable<TEntity> entitys);
TEntity InsertOrUpdate(TEntity entity);
/// <summary>
/// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
/// 场景:在关闭级联保存功能之后,手工使用本方法<para></para>
/// 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus")<para></para>
/// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
/// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
/// </summary>
/// <param name="entity">实体对象</param>
/// <param name="propertyName">属性名</param>
void SaveMany(TEntity entity, string propertyName);
IUpdate<TEntity> UpdateDiy { get; }
int Delete(TEntity entity);
int Delete(IEnumerable<TEntity> entitys);
int Delete(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 根据设置的 OneToOne/OneToMany/ManyToMany 导航属性,级联查询所有的数据库记录,删除并返回它们
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行<para></para>
/// 场景:winform 加载表数据后,一顿添加、修改、删除操作之后,最后才点击【保存】<para></para><para></para>
/// 示例:https://github.com/dotnetcore/FreeSql/issues/397<para></para>
/// 注意:* 本方法只支持单表操作,不支持导航属性级联保存
/// </summary>
/// <param name="data"></param>
void BeginEdit(List<TEntity> data);
/// <summary>
/// 完成编辑数据,进行保存动作<para></para>
/// 该方法根据 BeginEdit 传入的数据状态分析出添加、修改、删除 SQL 语句<para></para>
/// 注意:* 本方法只支持单表操作,不支持导航属性级联保存
/// </summary>
/// <param name="data">可选参数:手工传递最终的 data 值进行对比<para></para>默认:如果不传递,则使用 BeginEdit 传入的 data 引用进行对比</param>
/// <returns></returns>
int EndEdit(List<TEntity> data = null);
Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default);
Task<List<TEntity>> InsertAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default);
Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
Task<int> UpdateAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default);
Task<TEntity> InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default);
Task<int> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default);
Task<int> DeleteAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default);
Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
Task<List<object>> DeleteCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
工作单元
csharp
using (var uow = fsql.CreateUnitOfWork())
{
await uow.Orm.Insert(item).ExecuteAffrowsAsync(); //uow.Orm API 和 IFreeSql 一样
await uow.Orm.Ado.ExecuteNoneQueryAsync(sql);
await fsql.Insert(item)... //错误的用法,不在一个事务
var repo1 = uow.GetRepository<Song>();
await repo1.InsertAsync(item);
uow.Commit();
}
**提示:uow 范围内,尽量别使用 fsql 对象,以免不处在一个事务 **
查询
csharp
//OneToOne、ManyToOne
fsql.Select<Tag>().Where(a => a.Parent.Parent.Name == "English").ToList();
//OneToMany
fsql.Select<Tag>().IncludeMany(a => a.Tags, then => then.Where(sub => sub.Name == "foo")).ToList();
//ManyToMany
fsql.Select<Song>()
.IncludeMany(a => a.Tags, then => then.Where(sub => sub.Name == "foo"))
.Where(s => s.Tags.Any(t => t.Name == "Chinese"))
.ToList();
//Other
fsql.Select<YourType>()
.Where(a => a.IsDelete == 0)
.WhereIf(keyword != null, a => a.UserName.Contains(keyword))
.WhereIf(role_id > 0, a => a.RoleId == role_id)
.Where(a => a.Nodes.Any(t => t.Parent.Id == t.UserId))
.Count(out var total)
.Page(page, size)
.OrderByDescending(a => a.Id)
.ToList()
分页
csharp
var list = fsql.Select<Topic>()
.Where(a => a.Id > 10)
.Count(out var total) //总记录数量
.Page(1, 20)
.ToList();
Count(out var total) 是同步方法,原因是 out 不支持异步,如果介意可以单独执行如下:
csharp
var select = fsql.Select<Topic>().Where(a => a.Id > 10);
var total = await select.CountAsync();
var list = await select.Page(1, 20).ToListAsync();
一对多
有设置导航属性关系的(支持一对多、多对多):
csharp
fsql.Select<Tag>().IncludeMany(a => a.Goods).ToList();
未设置导航属性关系的,临时指定关系(只支持一对多):
csharp
fsql.Select<Goods>().IncludeMany(a => a.Comment.Where(b => b.TagId == a.Id));
只查询每项子集合的前几条数据,避免像 EfCore 加载所有数据导致 IO 性能低下(比如某商品下有 2000 条评论):
csharp
fsql.Select<Goods>().IncludeMany(a => a.Comment.Take(10));
在 Dto 上做映射 IncludeMany:
csharp
//定义临时类,也可以是 Dto 类
class Dto {
public int TypeId { get; set; }
[Navigate(nameof(TypeId))]
public List<Goods> GoodsList { get; set; }
}
//查询 Goods 商品表,分类1、分类2、分类3 各10条数据
var dto = new [] { 1,2,3 }.Select(a => new Dto { TypeId = a }).ToList();
dto.IncludeMany(d => d.GoodsList.Take(10).Where(gd => gd.TypeId == d.TypeId));
//执行后,dto 每个元素.GoodsList 将只有 10条记录
查询子集合表的部分字段,避免子集合字段过多的问题:
csharp
fsql.Select<Tag>().IncludeMany(a => a.Goods.Select(b => new Goods { Id = b.Id, Title = b.Title }));
//只查询 goods 表 id, title 字段,再作填充
WhereCascade
多表查询时,像 isdeleted 每个表都给条件,挺麻烦的。WhereCascade 使用后生成 sql 时,所有表都附上这个条件。
如:
csharp
fsql.Select<t1>()
.LeftJoin<t2>(...)
.WhereCascade(x => x.IsDeleted == false)
.ToList();
得到的 SQL:
sql
SELECT ...
FROM t1
LEFT JOIN t2 on ... AND (t2.IsDeleted = 0)
WHERE t1.IsDeleted = 0
实体可附加表达式时才生效,支持子表查询。单次查询使用的表数目越多收益越大。
WhereDynamicFilte
ISelect.WhereDynamicFilter
方法实现动态过滤条件(与前端交互),支持的操作符:
- Contains/StartsWith/EndsWith/NotContains/NotStartsWith/NotEndsWith:包含/不包含,`like '%xx%'`,或者 `like 'xx%'`,或者 `like '%xx'`
- Equal/NotEqual:等于/不等于
- GreaterThan/GreaterThanOrEqual:大于/大于等于
- LessThan/LessThanOrEqual:小于/小于等于
- Range:范围查询
- DateRange:日期范围,有特殊处理 value[1] + 1
- Any/NotAny:是否符合 value 中任何一项(直白的说是 SQL IN)
- Custom:自定义解析
csharp
DynamicFilterInfo dyfilter = JsonConvert.DeserializeObject<DynamicFilterInfo>(@"
{
""Logic"": ""And"",
""Filters"":
[
{ ""Field"": ""id"", ""Operator"": ""Equals"", ""Value"": 1 },
{
""Logic"": ""Or"",
""Filters"":
[
{ ""Field"": ""id"", ""Operator"": ""Equals"", ""Value"": 2 },
{ ""Field"": ""id"", ""Operator"": ""Equals"", ""Value"": 3 }
]
}
]
}");
fsql.Select<Region>().WhereDynamicFilter(dyfilter).ToList();
//WHERE id = 1 AND (id = 2 OR id = 3)
更新
csharp
//批量更新
fsql.Update<Entity>()
.SetSource(list) //要更新的多个数据
.IgnoreColumns(x=>new {x.ignoreColumnName}) //忽略更新的列
.ExecuteAffrowsAsync();