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<T1, TMember>> exp 表示在字段本身值的基础上进行操作
  • Expression<Func<T1, TMember>> column, TMember value 表示将 column设置 value
  • 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<T1, TReturn>> select 类型投影,通过lambda语句建立T1到TReturn之间的关系

返回多个:

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有更多的需求的话,可以踊跃吐槽哦~~如果有小伙伴还想看的话,我将会继续为大家讲解的。

至此,2020-5-28 《C# 数据操作系列》暂时 完结(如果后续有其他好的ORM的话,还会继续更新的)。

更多内容烦请关注我的博客《高先生小屋》

C# 数据操作系列 - 19 FreeSql 入坑介绍的更多相关文章

  1. C# 数据操作系列 - 15 SqlSugar 增删改查详解

    0. 前言 继上一篇,以及上上篇,我们对SqlSugar有了一个大概的认识,但是这并不完美,因为那些都是理论知识,无法描述我们工程开发中实际情况.而这一篇,将带领小伙伴们一起试着写一个能在工程中使用的 ...

  2. C# 数据操作系列 - 6 EF Core 配置映射关系

    0. 前言 在<C# 数据操作系列 - 5. EF Core 入门>篇中,我们简单的通过两个类演示了一下EF增删改查等功能.细心的小伙伴可能看了生成的DDL SQL 语句,在里面发现了些端 ...

  3. C# 数据操作系列 - 8. EF Core的增删改查

    0.前言 到目前为止,我们看了一下如何声明EF Core的初步使用,也整体的看了下EF Core的映射关系配置以及导航属性的配置. 这一篇,我带大家分享一下,我在工作中需要的EF Core的用法. 1 ...

  4. C# 数据操作系列 - 12 NHibernate的增删改查

    0. 前言 上一篇<C# 数据操作系列 - 11 NHibernate 配置和结构介绍> 介绍了Nhibernate里的配置内容.这一篇将带领大家了解一下如何使用NHIbernate.之前 ...

  5. C# 数据操作系列 - 16 SqlSugar 完结篇

    0. 前言 前一篇我们详细的介绍了SqlSugar的增删改查,那些已经满足我们在日常工程开发中的使用了.但是还有一点点在开发中并不常用,但是却非常有用的方法.接下来让我们一起来看看还有哪些有意思的内容 ...

  6. C# 数据操作系列 - 0. 序言

    0. 前言 在上一个系列中,我们初步浏览了一下C#的基础知识.这句话的意思就是C#基础知识系列完结了,撒花.当然,并不是因为C#已经讲完了.正是因为我们轻轻地叩开了那扇门,才能看到门后面那瑰丽的世界. ...

  7. C# 数据操作系列 - 1. SQL基础操作

    0.前言 前篇介绍了一些数据库的基本概念和以及一些常见的数据库,让我们对数据库有了一个初步的认识.这一篇我们将继续为C#数据操作的基础填上一个空白-SQL语句. SQL(Structured Quer ...

  8. mysql 数据操作 多表查询 子查询 介绍

    子查询就是: 把一条sql语句放在一个括号里,当做另外一条sql语句查询条件使用 拿到这个结果以后 当做下一个sql语句查询条件mysql 数据操作  子查询 #1:子查询是将一个查询语句嵌套在另一个 ...

  9. C# 数据操作系列 - 9. EF Core 完结篇

    0.前言 <EF Core>实际上已经可以告一段落了,但是感觉还有一点点意犹未尽.所以决定分享一下,个人在实际开发中使用EF Core的一些经验和使用的扩展包. 1. EF Core的异步 ...

随机推荐

  1. 数学--博弈论--巴什博奕(Bash Game)

    终于也轮到我做游戏了,他们做了好几个月的游戏了. 巴什博弈: 两个人做游戏,取石子,一个人最多可以可以取M个,至少取1个,最后取完的赢. 显然,如果n=m+1,那么由于一次最多只能取m个,所以,无论先 ...

  2. The Preliminary Contest for ICPC Asia Xuzhou 2019 徐州网络赛 K题 center

    You are given a point set with nn points on the 2D-plane, your task is to find the smallest number o ...

  3. redis系列之1----redis简介以及linux上的安装

    redis简介 redis是NoSQL(No Only SQL,非关系型数据库)的一种,NoSQL是以Key-Value的形式存储数据.当前主流的分布式缓存技术有redis,memcached,ssd ...

  4. 【学习笔记】Shell-1 变量:命名规范、变量赋值/取值/取消、局部变量/全局变量、预设环境变量

    1.Shell变量 从变量的实质上来说,变量名是指向一片用于存储数据的内存空间. Shell变量是一种弱类型的变量,即声明变量时不需要指定其变量类型,也不需求遵循“先声明再使用”的规定,想用即可用. ...

  5. Day_09【常用API】扩展案例6_将用户给定的字符串首个字符大写,并分别加上"set"和"get"输出

    定义如下方法public static String getPropertyGetMethodName(String property) (1)该方法的参数为String类型,表示用户给定的成员变量的 ...

  6. MATLAB1127(传递函数)

    sys=tf(400,[1,50,0]) sys = 400 ---------- s^2 + 50 s 其中,tf()函数的用法.   传递函数 dsys=c2d(sys,ts,'z') dsys ...

  7. 设计模式GOF23之工厂模式01

    简单工厂模式和工厂方法模式 工厂模式核心:分工 简单工厂模式不符合OCP(Open-Closed Princinple)原则,扩展时需要更改原代码 工厂方法模式增加了类复杂度代码复杂度等,所以一般使用 ...

  8. [hdu1079]简单博弈

    题意:两个人玩游戏,给定一个日期,他们轮流选择日期,可以选择当前日期的下一天,如果下一个月也有这一天的话则也可以选择下一个月的这一天.超过某一日期的人输. 思路:以天为状态,则一共有300多万个左右的 ...

  9. 给bootstrap右边的菜单加上右键关闭

    <ul class="rightmenu"> <li data-type="closethis">关闭当前</li> < ...

  10. Linux之cat的使用介绍

                                                                                                cat选项分析 ...