0. 前言

前几天FreeSql的作者向我推荐了FreeSql框架,想让我帮忙写个文章介绍一下。嗯,想不到我也能带个货了。哈哈,开个玩笑~看了下觉得设计的挺有意思的,所以就谢了这篇文章。

简单介绍一下,FreeSql 是NCC组织的沙盒级项目,是一款功能强大的 ORM 组件,支持 .NET Core、.NET Framework 和 Xamarin。目前 FreeSql 支持以下数据库:MySQL、PostgreSQL、SqlServer、Oracle、Sqlite、Odbc、微软 Access 以及国产数据库达梦。

也就是说也是一个由国内优秀开发者维护的优秀项目,初步看了下功能很齐全。小伙伴们有时间可以取瞅瞅。下图是我从它GitHub仓库里复制过来的。可以看见支持的功能还是相当多的。

关于NCC社区,是.net core的一个开源社区,也是国内最大的.net core开源社区

1. 初步使用

照例,没安装就没有调用。所以,在创建项目之后,安装一下吧:

dotnet add package FreeSql

然后创建一个IFreeSql对象:

public class FreeSqlContext
{
   public static IFreeSql FreeSqlConnect { get; } =
       new FreeSql.FreeSqlBuilder()            .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=document.db")
       .UseAutoSyncStructure(true) //自动同步实体结构到数据库
       .Build();
}

因为官方要求将IFreeSql对象声明为单例模式,所以我在这里使用了静态属性。

这种写法是C#的一种语法糖,只有get表示该属性是一个只能读的属性(与只读属性有个微妙的差距),等号后面表示该属性第一次赋值的内容。

创建一个普通的Model类:

public class Model
{
   public int Id { get; set; }
   public int StringLength { get; set; }
   public string Name { get; set; }
}

1.1 简单插入

然后试一下插入数据:

var row = FreeSqlContext.FreeSqlConnect.Insert(new Model
{
   Name = "测试",
   StringLength = 10
}).ExecuteAffrows();

提示如图内容,需要我们手动安装一下FreeSql的SQLite驱动,安装之后:

dotnet add packages FreeSql.Provider.Sqlite

FreeSql针对各种受支持的数据库都单独开发了驱动包,统一命名为:

FreeSql.Provider.<数据库类型>

安装完成后,重新运行后顺利完成执行,顺便帮你把数据库也生成好了(这一点我感觉挺好的),同时生成了一个主键为Id的Model表:

create table Model
(
   Id           INTEGER
       primary key,
   StringLength INTEGER not null,
   Name         NVARCHAR(255)
);

1.2 简单查询

接下来简单的查询一下刚刚插入的数据:

var list = FreeSqlContext.FreeSqlConnect.Queryable<Model>().ToList();

可以发现,查询使用还是非常方便的。

1.3 简单更新

FreeSql的更新与其他框架相比略显复杂,这里先展示一种更新方式:

list[0].Name = "修改测试";
row = FreeSqlContext.FreeSqlConnect.Update<Model>().SetSource(list[0]).ExecuteAffrows();

先声明要更新的类型是Model,然后设置更新源。

1.4 简单删除

row = FreeSqlContext.FreeSqlConnect.Delete<Model>(new[] { list[0] }).ExecuteAffrows();

删除之前获取的数据。

简单的看,FreeSql设计的增删改查都是以命令的形式进行的,在实际调用ExcuteXXX之前数据并不会保存到数据库中。

2. 增删改查详解

在上一节中我们简单的使用了一下增删改查, 这一节将为大家详细分析一下FreeSql的增删改查。

2.1 新增

IInsert<T1> Insert<T1>() where T1 : class;
IInsert<T1> Insert<T1>(T1 source) where T1 : class;
IInsert<T1> Insert<T1>(IEnumerable<T1> source) where T1 : class;
IInsert<T1> Insert<T1>(List<T1> source) where T1 : class;
IInsert<T1> Insert<T1>(T1[] source) where T1 : class;

这是IFreeSql接口里声明的Insert方法,通过方法我们可以看到插入单数据插入以及多数据插入,并且返回一个IInsert<T1>的接口。当然也可以不传入数据直接获取一个IInsert接口实例。这几个方法很简单,我们就不在这多费时间了,然后跳进IInsert里,看一看里面有哪些方法吧。

先来这样一组方法:

IInsert<T1> AppendData(T1 source);
IInsert<T1> AppendData(T1[] source);
IInsert<T1> AppendData(IEnumerable<T1> source);

这些方法可以后续为IInsert继续添加数据,以便执行更多的插入。

IInsert<T1> IgnoreColumns(string[] columns);
IInsert<T1> IgnoreColumns(Expression<Func<T1, object>> columns);

设置在插入过程中忽略的列,设置之后这些列将不会插入到数据库中。其中Expression<Func<T1, object>>表示一个包含列名属性的匿名对象。

IInsert<T1> InsertColumns(string[] columns);
IInsert<T1> InsertColumns(Expression<Func<T1, object>> columns);

设置只插入这些列,其他的列将不会被插入。

通过调用以下方法将执行插入:

int ExecuteAffrows();// 返回受影响的列
long ExecuteIdentity();// 返回自增主键值

这个方法需要实体类的主键标记为自增(这部分内容见下一节)。如果启用了批量插入模式,该值将返回最后一个数据的主键值。

List<T1> ExecuteInserted();// 返回插入后的数据

这个方法官方标注只在Postgresql/SqlServer有效果。

这是插入基本内容,相对而言插入比较简单。

2.2 删除

这次换个顺序,因为删除的方法在这里相对简单一些。FreeSql对于单表的数据删除相对克制而谨慎。那么就让我们简单看一下如何进行删除吧。

IDelete<T1> Delete<T1>() where T1 : class;

设置泛型类型,创建一个删除器(我给起的名,官方没给起名,也就是一个IDelete接口实例)。

IDelete<T1> Delete<T1>(object dywhere) where T1 : class;

这个方法很有意思,支持的相当广泛。

以下是官方给的注释:

主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} |new{id=1}

根据实际表现来看,会删除对应主键的数据。如果传入的是实体的话,会自动分析对应实体的主键,然后把这个数据标记为待删除。

记住这种方式,因为在后续的Update中会用到。

IFreeSql中的删除都不会立即删除,都会返回一个IDelete实例,与IInsert一样需要手动调用ExcuteXXX方法。

那么我们来看一下IDelete里的方法:

IDelete<T1> Where(Expression<Func<T1, bool>> exp);
IDelete<T1> Where(string sql, object parms = null);
IDelete<T1> Where(T1 item);
IDelete<T1> Where(IEnumerable<T1> items);

简单看一下方法,可以通过方法和参数就能知道其中含义。

需要注意的是,如果使用exp 做批量删除的话,只能用实体类的属性作为条件,不能使用导航属性。

使用sql语句的话,可以使用参数化写法如下:Where("id = ?id", new { id = 1 }),如果有多个条件的话sql里用and拼接。

IDelete<T1> WhereDynamic(object dywhere, bool not = false);

这里的dywhere与Delete的dywhere一样,not 如果设置为true,则表示删除除此之外的对象。

FreeSql在设计删除模式时,如果在IFreeSql.Delete中传入参数,后续继续调用Where或者WhereDynamic的话,两次是以and 的形式拼接的条件:

list = FreeSqlContext.FreeSqlConnect.Queryable<Model>().ToList();
FreeSqlContext.FreeSqlConnect.Delete<Model>(list[0]).WhereDynamic(list[2]).ExecuteAffrows();
FreeSqlContext.FreeSqlConnect.Delete<Model>(list[0]).Where(t=>t.Id > 10).ExecuteAffrows();

分别生成了如下SQL语句:

DELETE FROM "Model" WHERE ("Id" = 1) AND ("Id" = 11)
----------------------------
DELETE FROM "Model" WHERE ("Id" = 1) AND ("Id" > 10)

额,所以调用删除的时候最好注意一下,因为条件冲突的话,可能数据不会发生任何变化。

执行删除:

int ExecuteAffrows();//返回被影响的行数
List<T1> ExecuteDeleted();// 返回被删除的数据,一样只有 Postgresql/SqlServer 有效果

2.3 更新

IUpdate<T1> Update<T1>() where T1 : class;
IUpdate<T1> Update<T1>(object dywhere) where T1 : class;

同样,开启一个更新器(获取一个IUpdate示例),这里dywhere与删除支持的内容是一样的。不过,有一点不同的是:

row = FreeSqlContext.FreeSqlConnect.Update<Model>(list[0]).ExecuteAffrows();

不会有任何数据发生更改。嗯,这点与Delete完全不一样。简单理解一下,在这里FreeSql只是解析了数据里的实体,但并没有从传入的实体解析出更新SQL语句。

接下来,进入IUpdate:

IUpdate<T1> UpdateColumns(string[] columns);
IUpdate<T1> UpdateColumns(Expression<Func<T1, object>> columns);
IUpdate<T1> IgnoreColumns(Expression<Func<T1, object>> columns);
IUpdate<T1> IgnoreColumns(string[] columns);

设置要更新的列和要忽略的列,两者互相冲突。

示例:

row = FreeSqlContext.FreeSqlConnect.Update<Model>(list[0]).UpdateColumns(new[] { "Name" }).ExecuteAffrows();

是不是觉得欢天喜地的觉得会更新了,答案很残酷,没有。依旧返回0。说到这里了,FreeSql在更新上,需要额外指定更新的数据来源:

IUpdate<T1> SetSource(T1 source);
IUpdate<T1> SetSource(IEnumerable<T1> source);

也就是,FreeSql会从source解析出需要更新的字段,然后使用Update/Ignore来设置只更新或忽略哪些列。

最终示例:

row = FreeSqlContext.FreeSqlConnect.Update<Model>(list[0])
   .SetSource(list[0]).UpdateColumns(new[] { "Name" }).ExecuteAffrows();
row = FreeSqlContext.FreeSqlConnect.Update<Model>(list[0])
   .SetSource(list).UpdateColumns(new[] { "Name" }).ExecuteAffrows();
row = FreeSqlContext.FreeSqlConnect.Update<Model>(new[] { list[0] ,list[1]})
   .SetSource(list[0]).UpdateColumns(new[] { "Name" }).ExecuteAffrows();
row = FreeSqlContext.FreeSqlConnect.Update<Model>(new[] { list[0], list[1] })
   .SetSource(list).UpdateColumns(new[] { "Name" }).ExecuteAffrows();

然后生成如下SQL:

UPDATE "Model" SET "Name" = @p_0 WHERE ("Id" = 1) AND ("Id" = 1)
--------------------------
UPDATE "Model" SET "Name" = CASE "Id"
WHEN 1 THEN @p_0
WHEN 10 THEN @p_1
WHEN 11 THEN @p_2
WHEN 12 THEN @p_3
WHEN 13 THEN @p_4
WHEN 14 THEN @p_5
WHEN 15 THEN @p_6
WHEN 16 THEN @p_7
WHEN 17 THEN @p_8
WHEN 18 THEN @p_9
WHEN 19 THEN @p_10
WHEN 20 THEN @p_11
WHEN 21 THEN @p_12
WHEN 22 THEN @p_13
WHEN 23 THEN @p_14
WHEN 24 THEN @p_15 END
WHERE ("Id" IN (1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)) AND ("Id" = 1)
--------------------
UPDATE "Model" SET "Name" = @p_0 WHERE ("Id" = 1) AND ("Id" = 1 OR "Id" = 10)
--------------------
UPDATE "Model" SET "Name" = CASE "Id"
WHEN 1 THEN @p_0
WHEN 10 THEN @p_1
WHEN 11 THEN @p_2
WHEN 12 THEN @p_3
WHEN 13 THEN @p_4
WHEN 14 THEN @p_5
WHEN 15 THEN @p_6
WHEN 16 THEN @p_7
WHEN 17 THEN @p_8
WHEN 18 THEN @p_9
WHEN 19 THEN @p_10
WHEN 20 THEN @p_11
WHEN 21 THEN @p_12
WHEN 22 THEN @p_13
WHEN 23 THEN @p_14
WHEN 24 THEN @p_15 END
WHERE ("Id" IN (1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)) AND ("Id" = 1 OR "Id" = 10)

可以看出,如果在生成IUpdate实例的时候,传入数据再使用SetSource进行更新会比较诡异。所以SetSource的正常使用方式是,获取IUpdate实例的时候,不传dywhere,直接获取一个空IUpdate。

那么dywhere该在什么时候使用呢?

row = FreeSqlContext.FreeSqlConnect.Update<Model>(list[0]).Set(t => t.StringLength + 1).ExecuteAffrows();

通过调用Set/SetDto/SetIf三种方法进行更新,当然了这三种方法并不局限于使用了dywhere参数。

IUpdate<T1> Set<TMember>(Expression<Func<T1, TMember>> exp);
IUpdate<T1> Set<TMember>(Expression<Func<T1, TMember>> column, TMember value);
IUpdate<T1> SetDto(object dto);
IUpdate<T1> SetIf<TMember>(bool condition, Expression<Func<T1, TMember>> exp);
IUpdate<T1> SetIf<TMember>(bool condition, Expression<Func<T1, TMember>> column, TMember value);

其中:

  • Expression

    <func> exp 表示在字段本身值的基础上进行操作</func
  • Expression

    <func> column, TMember value 表示将 column设置 value</func
  • object dto 一个包含要更新属性和值的匿名类,或者一个字典类型(键为要更新的列,值为对应列的值)

  • bool condition 表示满足条件则更新,否则将不进行更新

IUpdate也提供了Where模式:

IUpdate<T1> Where(Expression<Func<T1, bool>> exp);
IUpdate<T1> Where(string sql, object parms = null);
IUpdate<T1> Where(T1 item);
IUpdate<T1> Where(IEnumerable<T1> items);
IUpdate<T1> WhereDynamic(object dywhere, bool not = false);

最终更新应该如下:

FreeSqlContext.FreeSqlConnect.Update<Model>(list[0]).Set(t => t.StringLength + 1).ExecuteAffrows();
FreeSqlContext.FreeSqlConnect.Update<Model>(list[0]).SetDto(new { Name="测试2" }).ExecuteAffrows();
FreeSqlContext.FreeSqlConnect.Update<Model>(list[0]).SetIf(true, t => t.Name + 1).ExecuteAffrows();
// 或者以下模式
FreeSqlContext.FreeSqlConnect.Update<Model>()
               .Where(t => true)
               .Set(t => t.StringLength + 1)
               .ExecuteAffrows();
// 或者
FreeSqlContext.FreeSqlConnect.Update<Model>(1)
               .Set(t => t.StringLength + 1)
               .ExecuteAffrows();

执行更新:

int ExecuteAffrows();// 返回受影响的行数
List<T1> ExecuteUpdated();// 嗯, 只有 Postgresql/SqlServer 有效果

2.4 查询

FreeSql的查询有两种方式,一种是使用FreeSql的ISelect方法,一种是使用扩展出来的Queryable方法,两者最终返回是一样的,均返回了一个ISelect实例。

那先来悄悄看一下两个方法的声明吧:

ISelect<T1> Select<T1>() where T1 : class;
ISelect<T1> Select<T1>(object dywhere) where T1 : class;
// 扩展方法在  FreeSqlGlobalExtensions 类
public static ISelect<T> Queryable<T>(this IFreeSql freesql) where T : class;

其中有一个闪闪放光的 dywhere,与Update/Delete一样,也是通过传入的属性解析到主键值获取对应的数据。

那么进入ISelect一探究竟吧:

暂且忽略多个泛型支持的方法:

T1 First()
TDto First<TDto>();
TReturn First<TReturn>(Expression<Func<T1, TReturn>> select);
T1 ToOne();
TDto ToOne<TDto>();
TReturn ToOne<TReturn>(Expression<Func<T1, TReturn>> select);
  • First和ToOne都是返回第一条数据

  • TDto 表示要查询出来的字段合集,列名与数据表中一一对应

  • Expression

    <func> select 类型投影,通过lambda语句建立T1到TReturn之间的关系</func

返回多个:

List<T1> ToList(bool includeNestedMembers = false);
List<TDto> ToList<TDto>();
List<TReturn> ToList<TReturn>(Expression<Func<T1, TReturn>> select);
  • includeNestedMembers :false: 返回 2级 LeftJoin/InnerJoin/RightJoin 对象;true: 返回所有 LeftJoin/InnerJoin/RightJoin的导航数据

其他方法:

long Count();// 返回数目
ISelect<T1> Distinct();//去重
ISelect<T1> Skip(int offset);// 忽略几个
ISelect<T1> Take(int limit);// 获取前几个
ISelect<T1> OrderBy<TMember>(Expression<Func<T1, TMember>> column);// 排序
ISelect<T1> OrderBy<TMember>(bool condition, Expression<Func<T1, TMember>> column);// 排序
ISelect<T1> OrderByDescending<TMember>(Expression<Func<T1, TMember>> column); // 降序
ISelect<T1> OrderByDescending<TMember>(bool condition, Expression<Func<T1, TMember>> column);// 降序
decimal Sum<TMember>(Expression<Func<T1, TMember>> column);// 求和
double Avg<TMember>(Expression<Func<T1, TMember>> column);// 求平均数

设置查询条件:

ISelect<T1> Where(Expression<Func<T1, bool>> exp);
ISelect<T1> WhereIf(bool condition, Expression<Func<T1, bool>> exp);
ISelect<T1> Where(string sql, object parms = null);

注意与dywhere之间是并列关系。

关于查询FreeSql做了很多优化,更多内容可以查阅官方文档。到目前为止,这些方法已经可以满足一个项目的使用了。

3. 总结

FreeSql可以说结合了很多优秀的ORM框架内容,而且针对不同的方式分成了不同的插件形式,使主干可以轻装上阵。

这是官方文档中关于不同使用习惯的介绍。

关于FreeSql的基础内容就到这里了,如果对FreeSql有更多的需求的话,可以踊跃吐槽哦~~如果有小伙伴还想看的话,我将会继续为大家讲解的。


©著作权归作者所有:来自51CTO博客作者mb5fdb0f93c5ca2的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. C# 数据操作系列 - 16 SqlSugar 完结篇
  2. C# 数据操作系列 - 14 深入探索SqlSugar
  3. C# 数据操作系列 - 15 SqlSugar 增删改查详解
  4. C# 数据操作系列 - 13 SqlSugar 初探
  5. C# 数据操作系列 - 12 NHibernate的增删改查
  6. C# 数据操作系列 - 11 NHibernate 配置和结构介绍
  7. C# 数据操作系列 - 10 NHibernate初试
  8. C# 数据操作系列 - 18 让Dapper更强的插件
  9. C# 数据操作系列 - 17 Dapper ——号称可以与ADO.NET 同台飙车的

随机推荐

  1. Android layout
  2. gradle 配置文件 build.gradle 属性详解
  3. Android的WindowManager解析
  4. android设置背景半透明效果
  5. android 发送自定义广播以及接收自定义广
  6. Android布局概览
  7. Android录屏 MediaRecorder介绍
  8. 在android里面使用自定义字体
  9. MIPS android 编译总结
  10. Android数据与服务器交互的GET,POST,HTTP