Skip to content

安装数据库驱动

需要访问什么数据库,就安装对应的 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) { }
}
方法返回值参数说明
AsTypevoidType改变仓储正在操作的实体类型
GetTEntityTKey根据主键,查询数据
FindTEntityTKey根据主键,查询数据
DeleteintTKey根据主键删除数据
DeleteintLambda根据 lambda 条件删除数据
DeleteintTEntity删除数据
DeleteintIEnumerable<TEntity>批量删除数据
DeleteCascadeByDatabaseList<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 实体
EndEditint完成编辑数据,进行保存动作
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; }
  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();