update readme

This commit is contained in:
28810 2020-02-29 13:05:48 +08:00
parent 86b6608bcc
commit 157dbc283e
14 changed files with 455 additions and 1712 deletions

View File

@ -1,97 +0,0 @@
# FreeSql 简介
FreeSql 是轻量化、可扩展和跨平台版的 .NETStandard 数据访问技术实现。
FreeSql 可用作对象关系映射程序 (O/RM),以便于开发人员能够使用 .NETStandard 对象来处理数据库,不必经常编写大部分数据访问代码。
FreeSql 支持 MySql/SqlServer/PostgreSQL 数据库技术实现。
## 模型
FreeSql 使用模型执行数据访问,模型由实体类表示数据库表或视图,用于查询和保存数据。 有关详细信息,请参阅创建模型。
可从现有数据库生成实体模型,提供 IDbFirst 生成实体模型。
或者手动创建模型,基于模型创建或修改数据库,提供 ICodeFirst 同步结构的 API甚至可以做到开发阶段自动同步
```csharp
using FreeSql.DataAnnotations;
using System;
public class Blog
{
[Column(IsIdentity = true, IsPrimary = true)]
public int BlogId { get; set; }
public string Url { get; set; }
public int Rating { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
```
## 声明
```csharp
var connstr = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;" +
"Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10";
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, connstr)
.UseSlave("connectionString1", "connectionString2") //使用从数据库,支持多个
.UseLogger(null) //使用日志,不指定默认输出控制台 ILogger
.UseCache(null) //使用缓存,不指定默认使用内存 IDistributedCache
.UseAutoSyncStructure(true) //自动同步实体结构到数据库
.UseSyncStructureToLower(true) //转小写同步结构
.Build();
```
注意: IFreeSql 在项目中应以单例声明,而不是在每次使用的时候创建。
## 查询
```csharp
var blogs = fsql.Select<Blog>
.Where(b => b.Rating > 3)
.OrderBy(b => b.Url)
.ToList();
```
## 插入
```csharp
var blog = new Blog { Url = "http://sample.com" };
blog.BlogId = (int)fsql.Insert<Blog>()
.AppendData(blog)
.ExecuteIdentity();
```
## 更新
```csharp
fsql.Update<Blog>()
.Set(b => b.Url, "http://sample2222.com")
.Where(b => b.Url == "http://sample.com")
.ExecuteAffrows();
```
## 删除
```csharp
fsql.Delete<Blog>()
.Where(b => b.Url == "http://sample.com")
.ExecuteAffrows();
```
## 后续步骤
有关介绍性教程,请参阅 [FreeSql 入门]()。

View File

@ -1,28 +0,0 @@
# FreeSql 入门
## 安装
FreeSql 是一个 .NET Standard 2.0 库,支持 .NET Framework 4.6.1 或 .NET Core 或更高版本的应用程序。
```shell
dotnet add package FreeSql
```
或者
```shell
Install-Package FreeSql
```
## 入门教程
FreeSql 可基于现有数据库创建模型,也可基于模型创建数据库。 提供的教程演示了这两种方法。
* .NET Core 控制台应用
- * 新建数据库
* ASP.NET Core 应用
- * 新建数据库
- * 现有数据库

View File

View File

@ -1,193 +0,0 @@
# CodeFirst
## 类型映射
| csharp | MySql | SqlServer | PostgreSQL | oracle |
| - | - | - | - | - |
| bool \| bool? | bit(1) | bit | bool | number(1) |
| sbyte \| sbyte? | tinyint(3) | smallint | int2 | number(4) |
| short \| short? | smallint(6) | smallint | int2 | number(6) |
| int \| int? | int(11) | int | int4 | number(11) |
| long \| long? | bigint(20) | bigint | int8 | number(21) |
| byte \| byte? | tinyint(3) unsigned | tinyint | int2 | number(3) |
| ushort \| ushort? | smallint(5) unsigned | int | int4 | number(5) |
| uint \| uint? | int(10) unsigned | bigint | int8 | number(10) |
| ulong \| ulong? | bigint(20) unsigned | decimal(20,0) | numeric(20,0) | number(20) |
| double \| double? | double | float | float8 | float(126) |
| float \| float? | float | real | float4 | float(63) |
| decimal \| decimal? | decimal(10,2) | decimal(10,2) | numeric(10,2) | number(10,2) |
| Guid \| Guid? | char(36) | uniqueidentifier | uuid | char(36 CHAR) |
| TimeSpan \| TimeSpan? | time | time | time | interval day(2) to second(6) |
| DateTime \| DateTime? | datetime | datetime | timestamp | timestamp(6) |
| DateTimeOffset \| DateTimeOffset? | - | - | datetimeoffset | timestamp(6) with local time zone |
| Enum \| Enum? | enum | int | int4 | number(16) |
| FlagsEnum \| FlagsEnum? | set | bigint | int8 | number(32) |
| byte[] | varbinary(255) | varbinary(255) | bytea | blob |
| string | varchar(255) | nvarchar(255) | varchar(255) | nvarchar2(255) |
| MygisPoint | point | - | - | - |
| MygisLineString | linestring | - | - | - |
| MygisPolygon | polygon | - | - | - |
| MygisMultiPoint | multipoint | - | - | - |
| MygisMultiLineString | multilinestring | - | - | - |
| MygisMultiPolygon | multipolygon | - | - | - |
| BitArray | - | - | varbit(64) | - |
| NpgsqlPoint \| NpgsqlPoint? | - | - | point | - |
| NpgsqlLine \| NpgsqlLine? | - | - | line | - |
| NpgsqlLSeg \| NpgsqlLSeg? | - | - | lseg | - |
| NpgsqlBox \| NpgsqlBox? | - | - | box | - |
| NpgsqlPath \| NpgsqlPath? | - | - | path | - |
| NpgsqlPolygon \| NpgsqlPolygon? | - | - | polygon | - |
| NpgsqlCircle \| NpgsqlCircle? | - | - | circle | - |
| (IPAddress Address, int Subnet) \| (IPAddress Address, int Subnet)? | - | - | cidr | - |
| IPAddress | - | - | inet | - |
| PhysicalAddress | - | - | macaddr | - |
| NpgsqlRange\<int\> \| NpgsqlRange\<int\>? | - | - | int4range | - |
| NpgsqlRange\<long\> \| NpgsqlRange\<long\>? | - | - | int8range | - |
| NpgsqlRange\<decimal\> \| NpgsqlRange\<decimal\>? | - | - | numrange | - |
| NpgsqlRange\<DateTime\> \| NpgsqlRange\<DateTime\>? | - | - | tsrange | - |
| PostgisPoint | - | - | geometry | - |
| PostgisLineString | - | - | geometry | - |
| PostgisPolygon | - | - | geometry | - |
| PostgisMultiPoint | - | - | geometry | - |
| PostgisMultiLineString | - | - | geometry | - |
| PostgisMultiPolygon | - | - | geometry | - |
| PostgisGeometry | - | - | geometry | - |
| PostgisGeometryCollection | - | - | geometry | - |
| Dictionary<string, string> | - | - | hstore | - |
| JToken | - | - | jsonb | - |
| JObject | - | - | jsonb | - |
| JArray | - | - | jsonb | - |
| 数组 | - | - | 以上所有类型都支持 | - |
> 以上类型和长度是默认值,可手工设置,如 string 属性可指定 [Column(DbType = "varchar(max)")]
```csharp
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10")
.UseAutoSyncStructure(true)
.UseMonitorCommand(
cmd => {
Console.WriteLine(cmd.CommandText);
}, //监听SQL命令对象在执行前
(cmd, traceLog) => {
Console.WriteLine(traceLog);
}) //监听SQL命令对象在执行后
.Build();
```
### 自动同步实体结构【开发环境必备】
自动同步实体结构到数据库,程序运行中检查实体表是否存在,然后创建或修改
```csharp
fsql.CodeFirst.IsAutoSyncDataStructure = true;
```
> 此功能默认为开启状态,发布正式环境后,请修改此设置
> 虽然【自动同步实体结构】功能开发非常好用,但是有个坏处,就是数据库后面会很乱,没用的字段一大堆
### 手工同步实体结构
| 实体&表对比 | 添加 | 改名 | 删除 |
| - | - | - | - |
| | √ | √ | X |
| 实体属性&字段对比 | 添加 | 修改可空 | 修改自增 | 修改类型 | 改名 | 删除 |
| - | - | - | - | - | - | - |
| | √ | √ | √ | √ | √ | X |
> 为了保证安全,不提供删除字段
1、提供方法对比实体与数据库中的变化部分
```csharp
var t1 = mysql.CodeFirst.GetComparisonDDLStatements<Topic>();
class Topic {
[Column(IsIdentity = true, IsPrimary = true)]
public int Id { get; set; }
public int Clicks { get; set; }
public TestTypeInfo Type { get; set; }
public string Title { get; set; }
public DateTime CreateTime { get; set; }
public ushort fusho { get; set; }
}
```
```sql
CREATE TABLE IF NOT EXISTS `cccddd`.`Topic` (
`Id` INT(11) NOT NULL AUTO_INCREMENT,
`Clicks` INT(11) NOT NULL,
`Title` VARCHAR(255),
`CreateTime` DATETIME NOT NULL,
`fusho` SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (`Id`)
) Engine=InnoDB CHARACTER SET utf8;
```
2、指定实体的表名
指定 Name 后,实体类名变化不影响数据库对应的表
```csharp
[Table(Name = "tb_topic111")]
class Topic {
//...
}
```
3、无指定实体的表名修改实体类名
指定数据库旧的表名修改实体命名时同时设置此参数为修改之前的值CodeFirst才可以正确修改数据库表否则将视为【创建新表】
```csharp
[Table(OldName = "Topic")]
class Topic2 {
//...
}
```
```sql
ALTER TABLE `cccddd`.`Topic` RENAME TO `cccddd`.`Topic2`;
```
4、修改属性的类型
把 Id 类型改为 uint 后
```sql
ALTER TABLE `cccddd`.`Topic2` MODIFY `Id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT;
```
```csharp
[Column(DbType = "varchar(128)")]
public string Title { get; set; }
```
```sql
ALTER TABLE `cccddd`.`Topic2` MODIFY `Title2` VARCHAR(128);
```
5、指定属性的字段名
这样指定后,修改实体的属性名不影响数据库对应的列
```csharp
[Column(Name = "titl2")]
public string Title { get; set; }
```
6、无指定属性的字段名修改属性名
指定数据库旧的列名修改实体属性命名时同时设置此参数为修改之前的值CodeFirst才可以正确修改数据库字段否则将视为【新增字段】
```csharp
[Column(OldName = "Title2")]
public string Title { get; set; }
```
```sql
ALTER TABLE `cccddd`.`Topic2` CHANGE COLUMN `Title2` `Title` VARCHAR(255);
```
7、提供方法同步结构
```csharp
var t2 = fsql.CodeFirst.SyncStructure<Topic>();
//同步实体类型到数据库
```

View File

@ -1,109 +0,0 @@
# DbFirst
```csharp
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10")
.Build();
```
### 获取所有数据库
```csharp
var t1 = fsql.DbFirst.GetDatabases();
//返回字符串数组, ["cccddd", "test"]
```
### 获取指定数据库的表信息
```csharp
var t2 = fsql.DbFirst.GetTablesByDatabase(fsql.DbFirst.GetDatabases()[0]);
//返回包括表、列详情、主键、唯一键、索引、外键
```
# 生成器
生成器是基于 dbfirst 开发的辅助工具,适用老项目一键生成实体。生成器采用模板的方式,作者实现了三种生成模板:
| 模板名称 | 路径 | 类型映射 | 外键导航属性 | 缓存管理 | 失血 | 贫血 | 充血 |
| ------------- | - | - |- | - |- | - |- |
| simple-entity | ../Templates/MySql/simple-entity | √ | X | X | √ | X | X |
| simple-entity-navigation-object | ../Templates/MySql/simple-entity-navigation-object | √ | √ | X | √ | X | X |
| rich-entity-navigation-object | ../Templates/MySql/rich-entity-navigation-object | √ | √ | √ | X | √ | X |
> 更多模板逐步开发中。。。
```csharp
//创建模板生成类实现
var gen = new FreeSql.Generator.TemplateGenerator();
gen.Build(fsql.DbFirst,
@"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity", //模板目录(事先下载)
@"C:\Users\28810\Desktop\新建文件夹 (9)", //生成后保存的目录
"cccddd" //数据库
);
```
## 模板语法
```html
<html>
<head>
<title>{#title}</title>
</head>
<body>
<!--绑定表达式-->
{#表达式}
{##表达式} 当表达式可能发生runtime错误时使用性能没有上面的高
<!--可嵌套使用同一标签最多支持3个指令-->
{include ../header.html}
<div @for="i 1, 101">
<p @if="i === 50" @for="item,index in data">aaa</p>
<p @else="i % 3 === 0">bbb {#i}</p>
<p @else="">ccc {#i}</p>
</div>
<!--定义模块,可以将公共模块定义到一个文件中-->
{module module_name1 parms1, 2, 3...}
{/module}
{module module_name2 parms1, 2, 3...}
{/module}
<!--使用模块-->
{import ../module.html as myname}
{#myname.module_name(parms1, 2, 3...)}
<!--继承-->
{extends ../inc/layout.html}
{block body}{/block}
<!--嵌入代码块-->
{%
for (var a = 0; a < 100; a++)
print(a);
%}
<!--条件分支-->
{if i === 50}
{elseif i > 60}
{else}
{/if}
<!--三种循环-->
{for i 1,101} 可自定义名 {for index2 表达式1 in 表达式2}
{for item,index in items} 可选参数称 index
可自定义名 {for item2, index99 in 数组表达式}
{for key,item,index on json} 可选参数 item, index
可自定义名 {for key2, item2, index99 in 对象表达式}
{/for}
<!--不被解析-->
{miss}
此块内容不被bmw.js解析
{/miss}
</body>
</html>
```

View File

@ -1,82 +0,0 @@
# 删除数据
| 方法 | 返回值 | 参数 | 描述 |
| - | - | - | - |
| Where | \<this\> | Lambda | 表达式条件,仅支持实体基础成员(不包含导航对象) |
| Where | \<this\> | string, parms | 原生sql语法条件Where("id = ?id", new { id = 1 }) |
| Where | \<this\> | T1 \| IEnumerable<T1> | 传入实体或集合,将其主键作为条件 |
| WhereExists | \<this\> | ISelect | 子查询是否存在 |
| ToSql | string | | 返回即将执行的SQL语句 |
| ExecuteAffrows | long | | 执行SQL语句返回影响的行数 |
| ExecuteDeleted | List\<T1\> | | 执行SQL语句返回被删除的记录 |
### 测试代码
```csharp
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10")
.Build();
IDelete<Topic> delete => fsql.Delete<Topic>();
[Table(Name = "tb_topic")]
class Topic {
[Column(IsIdentity = true, IsPrimary = true)]
public int Id { get; set; }
public int Clicks { get; set; }
public TestTypeInfo Type { get; set; }
public string Title { get; set; }
public DateTime CreateTime { get; set; }
}
```
### 动态条件
```csharp
Delete<Topic>(object dywhere)
```
dywhere 支持
* 主键值
* new[] { 主键值1, 主键值2 }
* Topic对象
* new[] { Topic对象1, Topic对象2 }
* new { id = 1 }
```csharp
var t1 = fsql.Delete<Topic>(new[] { 1, 2 }).ToSql();
//DELETE FROM `tb_topic` WHERE (`Id` = 1 OR `Id` = 2)
var t2 = fsql.Delete<Topic>(new Topic { Id = 1, Title = "test" }).ToSql();
//DELETE FROM `tb_topic` WHERE (`Id` = 1)
var t3 = fsql.Delete<Topic>(new[] { new Topic { Id = 1, Title = "test" }, new Topic { Id = 2, Title = "test" } }).ToSql();
//DELETE FROM `tb_topic` WHERE (`Id` = 1 OR `Id` = 2)
var t4 = fsql.Delete<Topic>(new { id = 1 }).ToSql();
//DELETE FROM `tb_topic` WHERE (`Id` = 1)
```
### 删除条件
```csharp
var t5 = delete.Where(a => a.Id == 1).ToSql().Replace("\r\n", "");
//DELETE FROM `tb_topic` WHERE (`Id` = 1)
var t6 = delete.Where("id = ?id", new { id = 1 }).ToSql().Replace("\r\n", "");
//DELETE FROM `tb_topic` WHERE (id = ?id)
var item = new Topic { Id = 1, Title = "newtitle" };
var t7 = delete.Where(item).ToSql().Replace("\r\n", "");
//DELETE FROM `tb_topic` WHERE (`Id` = 1)
var items = new List<Topic>();
for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 });
var t8 = delete.Where(items).ToSql().Replace("\r\n", "");
//DELETE FROM `tb_topic` WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))
```
### 执行命令
| 方法 | 返回值 | 参数 | 描述 |
| - | - | - | - |
| ExecuteAffrows | long | | 执行SQL语句返回影响的行数 |
| ExecuteDeleted | List\<T1\> | | 执行SQL语句返回被删除的记录 |

View File

@ -1,177 +0,0 @@
# 表达式函数
| 表达式 | MySql | SqlServer | PostgreSQL | Oracle | 功能说明 |
| - | - | - | - | - | - |
| a ? b : c | case when a then b else c end | case when a then b else c end | case when a then b else c end | case when a then b else c end | a成立时取b值否则取c值 |
| a ?? b | ifnull(a, b) | isnull(a, b) | coalesce(a, b) | nvl(a, b) | 当a为null时取b值 |
| 数字 + 数字 | a + b | a + b | a + b | a + b | 数字相加 |
| 数字 + 字符串 | concat(a, b) | cast(a as varchar) + cast(b as varchar) | case(a as varchar)\|\| b | a\|\| b | 字符串相加a或b任意一个为字符串时 |
| a - b | a - b | a - b | a - b | a - b | 减
| a * b | a * b | a * b | a * b | a * b | 乘
| a / b | a / b | a / b | a / b | a / b | 除
| a % b | a % b | a % b | a % b | mod(a,b) | 模
> 等等...
### 数组
| 表达式 | MySql | SqlServer | PostgreSQL | Oracle | 功能说明 |
| - | - | - | - | - | - |
| a.Length | - | - | case when a is null then 0 else array_length(a,1) end | - | 数组长度 |
| 常量数组.Length | - | - | array_length(array[常量数组元素逗号分割],1) | - | 数组长度 |
| a.Any() | - | - | case when a is null then 0 else array_length(a,1) end > 0 | - | 数组是否为空 |
| 常量数组.Contains(b) | b in (常量数组元素逗号分割) | b in (常量数组元素逗号分割) | b in (常量数组元素逗号分割) | b in (常量数组元素逗号分割) | IN查询 |
| a.Contains(b) | - | - | a @> array[b] | - | a数组是否包含b元素 |
| a.Concat(b) | - | - | a \|\| b | - | 数组相连 |
| a.Count() | - | - | 同 Length | - | 数组长度 |
### 字典 Dictionary<string, string>
| 表达式 | MySql | SqlServer | PostgreSQL | Oracle | 功能说明 |
| - | - | - | - | - | - |
| a.Count | - | - | case when a is null then 0 else array_length(akeys(a),1) end | - | 字典长度 |
| a.Keys | - | - | akeys(a) | - | 返回字典所有key数组 |
| a.Values | - | - | avals(a) | - | 返回字典所有value数组 |
| a.Contains(b) | - | - | a @> b | - | 字典是否包含b
| a.ContainsKey(b) | - | - | a? b | - | 字典是否包含key
| a.Concat(b) | - | - | a \|\| b | - | 字典相连 |
| a.Count() | - | - | 同 Count | - | 字典长度 |
### JSON JToken/JObject/JArray
| 表达式 | MySql | SqlServer | PostgreSQL | Oracle | 功能说明 |
| - | - | - | - | - | - |
| a.Count | - | - | jsonb_array_length(coalesce(a, '[])) | - | json数组类型的长度 |
| a.Any() | - | - | jsonb_array_length(coalesce(a, '[])) > 0 | - | json数组类型是否为空 |
| a.Contains(b) | - | - | coalesce(a, '{}') @> b::jsonb | - | json中是否包含b |
| a.ContainsKey(b) | - | - | coalesce(a, '{}') ? b | - | json中是否包含键b |
| a.Concat(b) | - | - | coalesce(a, '{}') || b::jsonb | - | 连接两个json |
| Parse(a) | - | - | a::jsonb | - | 转化字符串为json类型 |
### 字符串对象
| 表达式 | MySql | SqlServer | PostgreSQL | Oracle | 功能说明 |
| - | - | - | - | - | - |
| string.Empty | '' | '' | '' | 空字符串表示 |
| string.IsNullOrEmpty(a) | (a is null or a = '') | (a is null or a = '') | (a is null or a = '') | (a is null or a = '') | 空字符串表示 |
| a.CompareTo(b) | strcmp(a, b) | - | case when a = b then 0 when a > b then 1 else -1 end | case when a = b then 0 when a > b then 1 else -1 end | 比较a和b大小 |
| a.Contains('b') | a like '%b%' | a like '%b%' | a ilike'%b%' | a like '%b%' | a是否包含b |
| a.EndsWith('b') | a like '%b' | a like '%b' | a ilike'%b' | a like '%b' | a尾部是否包含b |
| a.IndexOf(b) | locate(a, b) - 1 | locate(a, b) - 1 | strpos(a, b) - 1 | instr(a, b, 1, 1) - 1 | 查找a中出现b的位置 |
| a.Length | char_length(a) | len(a) | char_length(a) | length(a) | 返回a的字符串长度 |
| a.PadLeft(b, c) | lpad(a, b, c) | - | lpad(a, b, c) | lpad(a, b, c) | 在a的左侧充字符c直到字符串长度大于b |
| a.PadRight(b, c) | rpad(a, b, c) | - | rpad(a, b, c) | rpad(a, b, c) | 在a的右侧充字符c直到字符串长度大于b |
| a.Replace(b, c) | replace(a, b, c) | replace(a, b, c) | replace(a, b, c) | replace(a, b, c) | 将a中字符串b替换成c |
| a.StartsWith('b') | a like 'b%' | a like 'b%' | a ilike'b%' | a like 'b%' | a头部是否包含b |
| a.Substring(b, c) | substr(a, b, c + 1) | substring(a, b, c + 1) | substr(a, b, c + 1) | substr(a, b, c + 1) | 截取a中位置b到c的内容 |
| a.ToLower | lower(a) | lower(a) | lower(a) | lower(a) | 转小写 |
| a.ToUpper | upper(a) | upper(a) | upper(a) | upper(a) | 转大写 |
| a.Trim | trim(a) | trim(a) | trim(a) | trim(a) | 移除两边字符 |
| a.TrimEnd | rtrim(a) | rtrim(a) | rtrim(a) | rtrim(a) | 移除左侧指定字符 |
| a.TrimStart | ltrim(a) | ltrim(a) | ltrim(a) | ltrim(a) | 移除右侧指定字符 |
### 日期对象
| 表达式 | MySql | SqlServer | PostgreSQL | Oracle |
| - | - | - | - | - |
| DateTime.Now | now() | getdate() | current_timestamp | systimestamp |
| DateTime.UtcNow | utc_timestamp() | getutcdate() | (current_timestamp at time zone 'UTC') | sys_extract_utc(systimestamp) |
| DateTime.Today | curdate | convert(char(10),getdate(),120) | current_date | trunc(systimestamp) |
| DateTime.MaxValue | cast('9999/12/31 23:59:59' as datetime) | '9999/12/31 23:59:59' | '9999/12/31 23:59:59'::timestamp | to_timestamp('9999-12-31 23:59:59','YYYY-MM-DD HH24:MI:SS.FF6') |
| DateTime.MinValue | cast('0001/1/1 0:00:00' as datetime) | '1753/1/1 0:00:00' | '0001/1/1 0:00:00'::timestamp | to_timestamp('0001-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS.FF6') |
| DateTime.Compare(a, b) | a - b | a - b | extract(epoch from a::timestamp-b::timestamp) | extract(day from (a-b)) |
| DateTime.DaysInMonth(a, b) | dayofmonth(last_day(concat(a, '-', b, '-1'))) | datepart(day, dateadd(day, -1, dateadd(month, 1, cast(a as varchar) + '-' + cast(b as varchar) + '-1'))) | extract(day from (a || '-' || b || '-01')::timestamp+'1 month'::interval-'1 day'::interval) | cast(to_char(last_day(a||'-'||b||'-01'),'DD') as number) |
| DateTime.Equals(a, b) | a = b | a = b | a = b | a = b |
| DateTime.IsLeapYear(a) | a%4=0 and a%100<>0 or a%400=0 | a%4=0 and a%100<>0 or a%400=0 | a%4=0 and a%100<>0 or a%400=0 | mod(a,4)=0 AND mod(a,100)<>0 OR mod(a,400)=0 |
| DateTime.Parse(a) | cast(a as datetime) | cast(a as datetime) | a::timestamp | to_timestamp(a,'YYYY-MM-DD HH24:MI:SS.FF6') |
| a.Add(b) | date_add(a, interval b microsecond) | dateadd(millisecond, b / 1000, a) | a::timestamp+(b||' microseconds')::interval | 增加TimeSpan值 | a + b |
| a.AddDays(b) | date_add(a, interval b day) | dateadd(day, b, a) | a::timestamp+(b||' day')::interval | a + b |
| a.AddHours(b) | date_add(a, interval b hour) | dateadd(hour, b, a) | a::timestamp+(b||' hour')::interval | a + b/24 |
| a.AddMilliseconds(b) | date_add(a, interval b*1000 microsecond) | dateadd(millisecond, b, a) | a::timestamp+(b||' milliseconds')::interval | a + b/86400000 |
| a.AddMinutes(b) | date_add(a, interval b minute) | dateadd(minute, b, a) | a::timestamp+(b||' minute')::interval | a + b/1440 |
| a.AddMonths(b) | date_add(a, interval b month) | dateadd(month, b, a) | a::timestamp+(b||' month')::interval | add_months(a,b) |
| a.AddSeconds(b) | date_add(a, interval b second) | dateadd(second, b, a) | a::timestamp+(b||' second')::interval | a + b/86400 |
| a.AddTicks(b) | date_add(a, interval b/10 microsecond) | dateadd(millisecond, b / 10000, a) | a::timestamp+(b||' microseconds')::interval | a + b/86400000000 |
| a.AddYears(b) | date_add(a, interval b year) | dateadd(year, b, a) | a::timestamp+(b||' year')::interval | add_months(a,b*12) |
| a.Date | cast(date_format(a, '%Y-%m-%d') as datetime) | convert(char(10),a,120) | a::date | trunc(a) |
| a.Day | dayofmonth(a) | datepart(day, a) | extract(day from a::timestamp) | cast(to_char(a,'DD') as number) |
| a.DayOfWeek | dayofweek(a) | datepart(weekday, a) - 1 | extract(dow from a::timestamp) | case when to_char(a)='7' then 0 else cast(to_char(a) as number) end |
| a.DayOfYear | dayofyear(a) | datepart(dayofyear, a) | extract(doy from a::timestamp) | cast(to_char(a,'DDD') as number) |
| a.Hour | hour(a) | datepart(hour, a) | extract(hour from a::timestamp) | cast(to_char(a,'HH24') as number) |
| a.Millisecond | floor(microsecond(a) / 1000) | datepart(millisecond, a) | extract(milliseconds from a::timestamp)-extract(second from a::timestamp)*1000 | cast(to_char(a,'FF3') as number) |
| a.Minute | minute(a) | datepart(minute, a) | extract(minute from a::timestamp) | cast(to_char(a,'MI') as number) |
| a.Month | month(a) | datepart(month, a) | extract(month from a::timestamp) | cast(to_char(a,'FF3') as number) |
| a.Second | second(a) | datepart(second, a) | extract(second from a::timestamp) | cast(to_char(a,'SS') as number) |
| a.Subtract(b) | timestampdiff(microsecond, b, a) | datediff(millisecond, b, a) * 1000 | (extract(epoch from a::timestamp-b::timestamp)*1000000) | a - b |
| a.Ticks | timestampdiff(microsecond, '0001-1-1', a) * 10 | datediff(millisecond, '1970-1-1', a) * 10000 + 621355968000000000 | extract(epoch from a::timestamp)*10000000+621355968000000000 | cast(to_char(a,'FF7') as number) |
| a.TimeOfDay | timestampdiff(microsecond, date_format(a, '%Y-%m-%d'), a) | '1970-1-1 ' + convert(varchar, a, 14) | extract(epoch from a::time)*1000000 | a - trunc(a) |
| a.Year | year(a) | datepart(year, a) | extract(year from a::timestamp) | 年 | cast(to_char(a,'YYYY') as number) |
| a.Equals(b) | a = b | a = b | a = b | a = b |
| a.CompareTo(b) | a - b | a - b | a - b | a - b |
| a.ToString() | date_format(a, '%Y-%m-%d %H:%i:%s.%f') | convert(varchar, a, 121) | to_char(a, 'YYYY-MM-DD HH24:MI:SS.US') | to_char(a,'YYYY-MM-DD HH24:MI:SS.FF6') |
### 时间对象
| 表达式 | MySql(微秒) | SqlServer(秒) | PostgreSQL(微秒) | Oracle(Interval day(9) to second(7)) |
| - | - | - | - | - |
| TimeSpan.Zero | 0 | 0 | - | 0微秒 | numtodsinterval(0,'second') |
| TimeSpan.MaxValue | 922337203685477580 | 922337203685477580 | - | numtodsinterval(233720368.5477580,'second') |
| TimeSpan.MinValue | -922337203685477580 | -922337203685477580 | - | numtodsinterval(-233720368.5477580,'second') |
| TimeSpan.Compare(a, b) | a - b | a - b | - | extract(day from (a-b)) |
| TimeSpan.Equals(a, b) | a = b | a = b | - | a = b |
| TimeSpan.FromDays(a) | a * 1000000 * 60 * 60 * 24 | a * 1000000 * 60 * 60 * 24 | - | numtodsinterval(a*86400,'second') |
| TimeSpan.FromHours(a) | a * 1000000 * 60 * 60 | a * 1000000 * 60 * 60 | - | numtodsinterval(a*3600,'second') |
| TimeSpan.FromMilliseconds(a) | a * 1000 | a * 1000 | - | numtodsinterval(a/1000,'second') |
| TimeSpan.FromMinutes(a) | a * 1000000 * 60 | a * 1000000 * 60 | - | numtodsinterval(a*60,'second') |
| TimeSpan.FromSeconds(a) | a * 1000000 | a * 1000000 | - | numtodsinterval(a,'second') |
| TimeSpan.FromTicks(a) | a / 10 | a / 10 | - | numtodsinterval(a/10000000,'second') |
| a.Add(b) | a + b | a + b | - | a + b |
| a.Subtract(b) | a - b | a - b | - | a - b |
| a.CompareTo(b) | a - b | a - b | - | extract(day from (a-b)) |
| a.Days | a div (1000000 * 60 * 60 * 24) | a div (1000000 * 60 * 60 * 24) | - | extract(day from a) |
| a.Hours | a div (1000000 * 60 * 60) mod 24 | a div (1000000 * 60 * 60) mod 24 | - | extract(hour from a) |
| a.Milliseconds | a div 1000 mod 1000 | a div 1000 mod 1000 | - | cast(substr(extract(second from a)-floor(extract(second from a)),2,3) as number) |
| a.Seconds | a div 1000000 mod 60 | a div 1000000 mod 60 | - | extract(second from a) |
| a.Ticks | a * 10 | a * 10 | - | (extract(day from a)*86400+extract(hour from a)*3600+extract(minute from a)*60+extract(second from a))*10000000 |
| a.TotalDays | a / (1000000 * 60 * 60 * 24) | a / (1000000 * 60 * 60 * 24) | - | extract(day from a) |
| a.TotalHours | a / (1000000 * 60 * 60) | a / (1000000 * 60 * 60) | - | (extract(day from a)*24+extract(hour from a)) |
| a.TotalMilliseconds | a / 1000 | a / 1000 | - | (extract(day from a)*86400+extract(hour from a)*3600+extract(minute from a)*60+extract(second from a))*1000 |
| a.TotalMinutes | a / (1000000 * 60) | a / (1000000 * 60) | - | | (extract(day from a)*1440+extract(hour from a)*60+extract(minute from a)) |
| a.TotalSeconds | a / 1000000 | a / 1000000 | - | (extract(day from a)*86400+extract(hour from a)*3600+extract(minute from a)*60+extract(second from a)) |
| a.Equals(b) | a = b | a = b | - | a = b |
| a.ToString() | cast(a as varchar) | cast(a as varchar) | - | to_char(a) |
### 数学函数
| 表达式 | MySql | SqlServer | PostgreSQL | Oracle |
| - | - | - | - | - |
| Math.Abs(a) | abs(a) | abs(a) | abs(a) |
| Math.Acos(a) | acos(a) | acos(a) | acos(a) | acos(a) |
| Math.Asin(a) | asin(a) | asin(a) | asin(a) | asin(a) |
| Math.Atan(a) | atan(a) | atan(a) | atan(a) | atan(a) |
| Math.Atan2(a, b) | atan2(a, b) | atan2(a, b) | atan2(a, b) | - |
| Math.Ceiling(a) | ceiling(a) | ceiling(a) | ceiling(a) | ceil(a) |
| Math.Cos(a) | cos(a) | cos(a) | cos(a) | cos(a) |
| Math.Exp(a) | exp(a) | exp(a) | exp(a) | exp(a) |
| Math.Floor(a) | floor(a) | floor(a) | floor(a) | floor(a) |
| Math.Log(a) | log(a) | log(a) | log(a) | log(e,a) |
| Math.Log10(a) | log10(a) | log10(a) | log10(a) | log(10,a) |
| Math.PI(a) | 3.1415926535897931 | 3.1415926535897931 | 3.1415926535897931 | 3.1415926535897931 |
| Math.Pow(a, b) | pow(a, b) | power(a, b) | pow(a, b) | power(a, b) |
| Math.Round(a, b) | round(a, b) | round(a, b) | round(a, b) | round(a, b) |
| Math.Sign(a) | sign(a) | sign(a) | sign(a) | sign(a) |
| Math.Sin(a) | sin(a) | sin(a) | sin(a) | sin(a) |
| Math.Sqrt(a) | sqrt(a) | sqrt(a) | sqrt(a) | sqrt(a) |
| Math.Tan(a) | tan(a) | tan(a) | tan(a) | tan(a) |
| Math.Truncate(a) | truncate(a, 0) | floor(a) | trunc(a, 0) | trunc(a, 0) |
### 类型转换
| 表达式 | MySql | SqlServer | PostgreSQL | Oracle |
| - | - | - | - | - |
| Convert.ToBoolean(a) | a not in ('0','false') | a not in ('0','false') | a::varchar not in ('0','false','f','no') | - |
| Convert.ToByte(a) | cast(a as unsigned) | cast(a as tinyint) | a::int2 | cast(a as number) |
| Convert.ToChar(a) | substr(cast(a as char),1,1) | substring(cast(a as nvarchar),1,1) | substr(a::char,1,1) | substr(to_char(a),1,1) |
| Convert.ToDateTime(a) | cast(a as datetime) | cast(a as datetime) | a::timestamp | to_timestamp(a,'YYYY-MM-DD HH24:MI:SS.FF6') |
| Convert.ToDecimal(a) | cast(a as decimal(36,18)) | cast(a as decimal(36,19)) | a::numeric | cast(a as number) |
| Convert.ToDouble(a) | cast(a as decimal(32,16)) | cast(a as decimal(32,16)) | a::float8 | cast(a as number) |
| Convert.ToInt16(a) | cast(a as signed) | cast(a as smallint) | a::int2 | cast(a as number) |
| Convert.ToInt32(a) | cast(a as signed) | cast(a as int) | a::int4 | cast(a as number) |
| Convert.ToInt64(a) | cast(a as signed) | cast(a as bigint) | a::int8 | cast(a as number) |
| Convert.ToSByte(a) | cast(a as signed) | cast(a as tinyint) | a::int2 | cast(a as number) |
| Convert.ToString(a) | cast(a as decimal(14,7)) | cast(a as decimal(14,7)) | a::float4 | to_char(a) |
| Convert.ToSingle(a) | cast(a as char) | cast(a as nvarchar) | a::varchar | cast(a as number) |
| Convert.ToUInt16(a) | cast(a as unsigned) | cast(a as smallint) | a::int2 | cast(a as number) |
| Convert.ToUInt32(a) | cast(a as unsigned) | cast(a as int) | a::int4 | cast(a as number) |
| Convert.ToUInt64(a) | cast(a as unsigned) | cast(a as bigint) | a::int8 | cast(a as number) |

View File

@ -1,103 +0,0 @@
# 生成器
生成器是基于 dbfirst 开发的辅助工具,适用老项目一键生成实体。生成器采用模板的方式,作者实现了三种生成模板:
| 模板名称 | 类型映射 | 外键导航属性 | 缓存管理 | 失血 | 贫血 | 充血 |
| ------------- | - | - |- | - |- | - |
| simple-entity | √ | X | X | √ | X | X |
| simple-entity-navigation-object | √ | √ | X | √ | X | X |
| rich-entity-navigation-object | √ | √ | √ | X | √ | X |
模板在项目目录:/Templates/MySql
> 更多模板逐步开发中。。。
```csharp
//定义 mysql FreeSql
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10")
.UseAutoSyncStructure(true)
.UseMonitorCommand(
cmd => {
Console.WriteLine(cmd.CommandText);
}, //监听SQL命令对象在执行前
(cmd, traceLog) => {
Console.WriteLine(traceLog);
}) //监听SQL命令对象在执行后
.Build();
//创建模板生成类实现
var gen = new FreeSql.Generator.TemplateGenerator();
gen.Build(mysql.DbFirst,
@"C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity", //模板目录(事先下载)
@"C:\Users\28810\Desktop\新建文件夹 (9)", //生成后保存的目录
"cccddd" //数据库
);
```
## 模板语法
```html
<html>
<head>
<title>{#title}</title>
</head>
<body>
<!--绑定表达式-->
{#表达式}
{##表达式} 当表达式可能发生runtime错误时使用性能没有上面的高
<!--可嵌套使用同一标签最多支持3个指令-->
{include ../header.html}
<div @for="i 1, 101">
<p @if="i === 50" @for="item,index in data">aaa</p>
<p @else="i % 3 === 0">bbb {#i}</p>
<p @else="">ccc {#i}</p>
</div>
<!--定义模块,可以将公共模块定义到一个文件中-->
{module module_name1 parms1, 2, 3...}
{/module}
{module module_name2 parms1, 2, 3...}
{/module}
<!--使用模块-->
{import ../module.html as myname}
{#myname.module_name(parms1, 2, 3...)}
<!--继承-->
{extends ../inc/layout.html}
{block body}{/block}
<!--嵌入代码块-->
{%
for (var a = 0; a < 100; a++)
print(a);
%}
<!--条件分支-->
{if i === 50}
{elseif i > 60}
{else}
{/if}
<!--三种循环-->
{for i 1,101} 可自定义名 {for index2 表达式1 in 表达式2}
{for item,index in items} 可选参数称 index
可自定义名 {for item2, index99 in 数组表达式}
{for key,item,index on json} 可选参数 item, index
可自定义名 {for key2, item2, index99 in 对象表达式}
{/for}
<!--不被解析-->
{miss}
此块内容不被bmw.js解析
{/miss}
</body>
</html>
```

View File

@ -1,79 +0,0 @@
# 插入数据
| 方法 | 返回值 | 参数 | 描述 |
| - | - | - | - |
| AppendData | \<this\> | T1 \| IEnumerable<T1> | 追加准备插入的实体 |
| InsertColumns | \<this\> | Lambda | 只插入的列 |
| IgnoreColumns | \<this\> | Lambda | 忽略的列 |
| ToSql | string | | 返回即将执行的SQL语句 |
| ExecuteAffrows | long | | 执行SQL语句返回影响的行数 |
| ExecuteIdentity | long | | 执行SQL语句返回自增值 |
| ExecuteInserted | List\<T1\> | | 执行SQL语句返回插入后的记录 |
### 列优先级
> 全部列 < 指定列(InsertColumns) < 忽略列(IgnoreColumns)
### 测试代码
```csharp
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10")
.Build();
IInsert<Topic> insert => fsql.Insert<Topic>();
[Table(Name = "tb_topic")]
class Topic {
[Column(IsIdentity = true, IsPrimary = true)]
public int Id { get; set; }
public int Clicks { get; set; }
public TestTypeInfo Type { get; set; }
public string Title { get; set; }
public DateTime CreateTime { get; set; }
}
var items = new List<Topic>();
for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 });
```
### 插入
```csharp
var t1 = insert.AppendData(items.First()).ToSql();
//INSERT INTO `tb_topic`(`Clicks`, `Title`, `CreateTime`) VALUES(?Clicks0, ?Title0, ?CreateTime0)
```
### 批量插入
```csharp
var t2 = insert.AppendData(items).ToSql();
//INSERT INTO `tb_topic`(`Clicks`, `Title`, `CreateTime`) VALUES(?Clicks0, ?Title0, ?CreateTime0), (?Clicks1, ?Title1, ?CreateTime1), (?Clicks2, ?Title2, ?CreateTime2), (?Clicks3, ?Title3, ?CreateTime3), (?Clicks4, ?Title4, ?CreateTime4), (?Clicks5, ?Title5, ?CreateTime5), (?Clicks6, ?Title6, ?CreateTime6), (?Clicks7, ?Title7, ?CreateTime7), (?Clicks8, ?Title8, ?CreateTime8), (?Clicks9, ?Title9, ?CreateTime9)
```
### 只想插入指定的列
```csharp
var t3 = insert.AppendData(items).InsertColumns(a => a.Title).ToSql();
//INSERT INTO `tb_topic`(`Title`) VALUES(?Title0), (?Title1), (?Title2), (?Title3), (?Title4), (?Title5), (?Title6), (?Title7), (?Title8), (?Title9)
var t4 = insert.AppendData(items).InsertColumns(a =>new { a.Title, a.Clicks }).ToSql();
//INSERT INTO `tb_topic`(`Clicks`, `Title`) VALUES(?Clicks0, ?Title0), (?Clicks1, ?Title1), (?Clicks2, ?Title2), (?Clicks3, ?Title3), (?Clicks4, ?Title4), (?Clicks5, ?Title5), (?Clicks6, ?Title6), (?Clicks7, ?Title7), (?Clicks8, ?Title8), (?Clicks9, ?Title9)
```
### 忽略列
```csharp
var t5 = insert.AppendData(items).IgnoreColumns(a => a.CreateTime).ToSql();
//INSERT INTO `tb_topic`(`Clicks`, `Title`) VALUES(?Clicks0, ?Title0), (?Clicks1, ?Title1), (?Clicks2, ?Title2), (?Clicks3, ?Title3), (?Clicks4, ?Title4), (?Clicks5, ?Title5), (?Clicks6, ?Title6), (?Clicks7, ?Title7), (?Clicks8, ?Title8), (?Clicks9, ?Title9)
var t6 = insert.AppendData(items).IgnoreColumns(a => new { a.Title, a.CreateTime }).ToSql();
///INSERT INTO `tb_topic`(`Clicks`) VALUES(?Clicks0), (?Clicks1), (?Clicks2), (?Clicks3), (?Clicks4), (?Clicks5), (?Clicks6), (?Clicks7), (?Clicks8), (?Clicks9)
```
### 执行命令
| 方法 | 返回值 | 描述 |
| - | - | - |
| ExecuteAffrows | long | 执行SQL语句返回影响的行数 |
| ExecuteIdentity | long | 执行SQL语句返回自增值 |
| ExecuteInserted | List\<T1\> | 执行SQL语句返回插入后的记录 |

View File

@ -1,259 +0,0 @@
# 查询数据
## 测试代码
```csharp
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10")
.Build();
ISelect<Topic> select => fsql.Select<Topic>();
[Table(Name = "tb_topic")]
class Topic {
[Column(IsIdentity = true, IsPrimary = true)]
public int Id { get; set; }
public int Clicks { get; set; }
public int TestTypeInfoGuid { get; set; }
public TestTypeInfo Type { get; set; }
public string Title { get; set; }
public DateTime CreateTime { get; set; }
}
class TestTypeInfo {
public int Guid { get; set; }
public int ParentId { get; set; }
public TestTypeParentInfo Parent { get; set; }
public string Name { get; set; }
}
class TestTypeParentInfo {
public int Id { get; set; }
public string Name { get; set; }
public List<TestTypeInfo> Types { get; set; }
}
```
# Where
### 单表
```csharp
var sql = select.Where(a => a.Id == 10).ToSql();
///SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a.`Title`, a.`CreateTime` FROM `tb_topic` a WHERE (a.`Id` = 10)
sql = select.Where(a => a.Id == 10 && a.Id > 10 || a.Clicks > 100).ToSql();
///SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a.`Title`, a.`CreateTime` FROM `tb_topic` a WHERE (a.`Id` = 10 AND a.`Id` > 10 OR a.`Clicks` > 100)
sql = select.Where(a => new []{1,2,3}.Contains(a.Id)).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a.`Title`, a.`CreateTime` FROM `tb_topic` a WHERE (a.`Id` in (1,2,3))
```
> [《Expression 表达式函数文档》](Docs/expression.md)
### 多表,使用导航属性
```csharp
sql = select.Where(a => a.Type.Name == "typeTitle" && a.Type.Guid == a.TestTypeInfoGuid).ToSql();
///SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a__Type.`Guid`, a__Type.`ParentId`, a__Type.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a, `TestTypeInfo` a__Type WHERE (a__Type.`Name` = 'typeTitle' AND a__Type.`Guid` = a.`TestTypeInfoGuid`)
sql = select.Where(a => a.Type.Parent.Name == "tparent").ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a__Type.`Guid`, a__Type.`ParentId`, a__Type.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a, `TestTypeInfo` a__Type, `TestTypeParentInfo` a__Type__Parent WHERE (a__Type__Parent.`Name` = 'tparent')
```
### 多表,没有导航属性
```csharp
sql = select.Where<TestTypeInfo>((a, b) => b.Guid == a.TestTypeInfoGuid && b.Name == "typeTitle").ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, b.`Guid`, b.`ParentId`, b.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a, `TestTypeInfo` b WHERE (b.`Guid` = a.`TestTypeInfoGuid` AND b.`Name` = 'typeTitle')
sql = select.Where<TestTypeInfo, TestTypeParentInfo>((a, b, c) => c.Name == "tparent").ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a.`Title`, a.`CreateTime` FROM `tb_topic` a, `TestTypeParentInfo` c WHERE (c.`Name` = 'tparent')
```
### 多表,任意查
```csharp
sql = select.From<TestTypeInfo, TestTypeParentInfo>((s, b, c) => s
.Where(a => a.Id == 10 && c.Name == "xxx")
.Where(a => b.ParentId == 20)).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, b.`Guid`, b.`ParentId`, b.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a, `TestTypeParentInfo` c, `TestTypeInfo` b WHERE (a.`Id` = 10 AND c.`Name` = 'xxx') AND (b.`ParentId` = 20)
```
### 子表 Exists 查询
```csharp
var sql2222 = select.Where(a => select.Where(b => b.Id == a.Id).Any()).ToList();
// SELECT a.`Id`, a.`TypeGuid`, a.`Title`, a.`CreateTime`
// FROM `xxx` a
// WHERE (exists(SELECT 1
// FROM `xxx` b
// WHERE (b.`Id` = a.`Id`)))
//两级相同的子表查询
sql2222 = select.Where(a =>
select.Where(b => b.Id == a.Id && select.Where(c => c.Id == b.Id).Where(d => d.Id == a.Id).Where(e => e.Id == b.Id)
.Offset(a.Id)
.Any()
).Any()
).ToList();
// SELECT a.`Id`, a.`TypeGuid`, a.`Title`, a.`CreateTime`
// FROM `xxx` a
// WHERE (exists(SELECT 1
// FROM `xxx` b
// WHERE (b.`Id` = a.`Id` AND exists(SELECT 1
// FROM `xxx` c
// WHERE (c.`Id` = b.`Id`) AND (c.`Id` = a.`Id`) AND (c.`Id` = b.`Id`)
// limit 0,1))
// limit 0,1))
```
### 原生SQL
```csharp
sql = select.Where("a.clicks > 100 && a.id = ?id", new { id = 10 }).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a.`Title`, a.`CreateTime` FROM `tb_topic` a WHERE (a.clicks > 100 && a.id = ?id)
```
> 以上条件查询,支持 WhereIf
# 联表
### 使用导航属性联表
```csharp
sql = select.LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a__Type.`Guid`, a__Type.`ParentId`, a__Type.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a LEFT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid`
sql = select
.LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid)
.LeftJoin(a => a.Type.Parent.Id == a.Type.ParentId).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a__Type.`Guid`, a__Type.`ParentId`, a__Type.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a LEFT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid` LEFT JOIN `TestTypeParentInfo` a__Type__Parent ON a__Type__Parent.`Id` = a__Type.`ParentId`
```
### 没有导航属性联表
```csharp
sql = select.LeftJoin<TestTypeInfo>((a, b) => b.Guid == a.TestTypeInfoGuid).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, b.`Guid`, b.`ParentId`, b.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a LEFT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid`
sql = select
.LeftJoin<TestTypeInfo>((a, b) => b.Guid == a.TestTypeInfoGuid)
.LeftJoin<TestTypeParentInfo>((a, c) => c.Id == a.Type.ParentId).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, b.`Guid`, b.`ParentId`, b.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a LEFT JOIN `TestTypeInfo` b ON b.`Guid` = a.`TestTypeInfoGuid` LEFT JOIN `TestTypeParentInfo` c ON c.`Id` = b.`ParentId`
```
### 联表任意查
```csharp
sql = select.From<TestTypeInfo, TestTypeParentInfo>((s, b, c) => s
.LeftJoin(a => a.TestTypeInfoGuid == b.Guid)
.LeftJoin(a => b.ParentId == c.Id)).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, b.`Guid`, b.`ParentId`, b.`Name`, a.`Title`, a.`CreateTime` FROM `tb_topic` a LEFT JOIN `TestTypeInfo` b ON a.`TestTypeInfoGuid` = b.`Guid` LEFT JOIN `TestTypeParentInfo` c ON b.`ParentId` = c.`Id`
```
### 原生SQL联表
```csharp
sql = select.LeftJoin("TestTypeInfo b on b.Guid = a.TestTypeInfoGuid and b.Name = ?bname", new { bname = "xxx" }).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a.`Title`, a.`CreateTime` FROM `tb_topic` a LEFT JOIN TestTypeInfo b on b.Guid = a.TestTypeInfoGuid and b.Name = ?bname
```
# 查询数据
### 返回 List
```csharp
List<Topic> t1 = select.Where(a => a.Id > 0).Skip(100).Limit(200).ToList();
```
### 返回 List + 导航属性的数据
```csharp
List<Topic> t2 = select.LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid).ToList();
//此时会返回普通字段 + 导航对象 Type 的数据
```
### 指定字段返回
```csharp
//返回一个字段
List<int> t3 = select.Where(a => a.Id > 0).Skip(100).Limit(200).ToList(a => a.Id);
//返回匿名类
List<匿名类> t4 = select.Where(a => a.Id > 0).Skip(100).Limit(200).ToList(a => new { a.Id, a.Title });
//返回元组
List<(int, string)> t5 = select.Where(a => a.Id > 0).Skip(100).Limit(200).ToList<(int, string)>("id, title");
//返回SQL字段
List<匿名类> t4 = select.Where(a => a.Id > 0).Skip(100).Limit(200)
.ToList(a => new {
a.Id,
a.Title,
cstitle = "substr(a.title, 0, 2)", //将 substr(a.title, 0, 2) 作为查询字段
csnow = Convert.ToDateTime("now()"), //将 now() 作为查询字段
//奇思妙想:怎么查询开窗函数的结果
});
```
### 执行SQL返回数据
```csharp
class xxx {
public int Id { get; set; }
public string Path { get; set; }
public string Title2 { get; set; }
}
List<xxx> t6 = fsql.Ado.Query<xxx>("select * from song");
List<(int, string ,string)> t7 = fsql.Ado.Query<(int, string, string)>("select * from song");
List<dynamic> t8 = fsql.Ado.Query<dynamic>("select * from song");
```
### 分组聚合
```csharp
var groupby = fsql.Select<Topic>()
.GroupBy(a => new { tt2 = a.Title.Substring(0, 2), mod4 = a.Id % 4 })
.Having(a => a.Count() > 0 && a.Avg(a.Key.mod4) > 0 && a.Max(a.Key.mod4) > 0)
.Having(a => a.Count() < 300 || a.Avg(a.Key.mod4) < 100)
.OrderBy(a => a.Key.tt2)
.OrderByDescending(a => a.Count())
.ToList(a => new { a.Key.tt2, cou1 = a.Count(), arg1 = a.Avg(a.Key.mod4) });
//SELECT substr(a.`Title`, 1, 2) as1, count(1) as2, avg((a.`Id` % 4)) as3
//FROM `xxx` a
//GROUP BY substr(a.`Title`, 1, 2), (a.`Id` % 4)
//HAVING (count(1) > 0 AND avg((a.`Id` % 4)) > 0 AND max((a.`Id` % 4)) > 0) AND (count(1) < 300 OR avg((a.`Id` % 4)) < 100)
//ORDER BY substr(a.`Title`, 1, 2), count(1) DESC
```
# 更多文档整理中。。。
| 方法 | 返回值 | 参数 | 描述 |
| ------------- | - | - | - |
| ToSql | string | | 返回即将执行的SQL语句 |
| ToList | List<T1> | | 执行SQL查询返回 T1 实体所有字段的记录,若存在导航属性则一起查询返回,记录不存在时返回 Count 为 0 的列表 |
| ToList\<T\> | List\<T\> | Lambda | 执行SQL查询返回指定字段的记录记录不存在时返回 Count 为 0 的列表 |
| ToList\<T\> | List\<T\> | string field | 执行SQL查询返回 field 指定字段的记录,并以元组或基础类型(int,string,long)接收,记录不存在时返回 Count 为 0 的列表 |
| ToOne | T1 | | 执行SQL查询返回 T1 实体所有字段的第一条记录,记录不存在时返回 null |
| Any | bool | | 执行SQL查询是否有记录 |
| Sum | T | Lambda | 指定一个列求和 |
| Min | T | Lambda | 指定一个列求最小值 |
| Max | T | Lambda | 指定一个列求最大值 |
| Avg | T | Lambda | 指定一个列求平均值 |
| 【分页】 |
| Count | long | | 查询的记录数量 |
| Count | \<this\> | out long | 查询的记录数量以参数out形式返回 |
| Skip | \<this\> | int offset | 查询向后偏移行数 |
| Offset | \<this\> | int offset | 查询向后偏移行数 |
| Limit | \<this\> | int limit | 查询多少条数据 |
| Take | \<this\> | int limit | 查询多少条数据 |
| Page | \<this\> | int pageIndex, int pageSize | 分页 |
| 【条件】 |
| Where | \<this\> | Lambda | 支持多表查询表达式 |
| WhereIf | \<this\> | bool, Lambda | 支持多表查询表达式 |
| Where | \<this\> | string, parms | 原生sql语法条件Where("id = ?id", new { id = 1 }) |
| WhereIf | \<this\> | bool, string, parms | 原生sql语法条件WhereIf(true, "id = ?id", new { id = 1 }) |
| 【分组】 |
| GroupBy | \<this\> | Lambda | 按选择的列分组GroupBy(a => a.Name) | GroupBy(a => new{a.Name,a.Time}) |
| GroupBy | \<this\> | string, parms | 按原生sql语法分组GroupBy("concat(name, ?cc)", new { cc = 1 }) |
| Having | \<this\> | string, parms | 按原生sql语法聚合条件过滤Having("count(name) = ?cc", new { cc = 1 }) |
| 【排序】 |
| OrderBy | \<this\> | Lambda | 按列排序OrderBy(a => a.Time) |
| OrderByDescending | \<this\> | Lambda | 按列倒向排序OrderByDescending(a => a.Time) |
| OrderBy | \<this\> | string, parms | 按原生sql语法排序OrderBy("count(name) + ?cc", new { cc = 1 }) |
| 【联表】 |
| LeftJoin | \<this\> | Lambda | 左联查询,可使用导航属性,或指定关联的实体类型 |
| InnerJoin | \<this\> | Lambda | 联接查询,可使用导航属性,或指定关联的实体类型 |
| RightJoin | \<this\> | Lambda | 右联查询,可使用导航属性,或指定关联的实体类型 |
| LeftJoin | \<this\> | string, parms | 左联查询使用原生sql语法LeftJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) |
| InnerJoin | \<this\> | string, parms | 联接查询使用原生sql语法InnerJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) |
| RightJoin | \<this\> | string, parms | 右联查询使用原生sql语法RightJoin("type b on b.id = a.id and b.clicks > ?clicks", new { clicks = 1 }) |
| From | \<this\> | Lambda | 多表查询3个表以上使用非常方便目前设计最大支持10个表 |
| 【其他】 |
| As | \<this\> | string alias = "a" | 指定别名 |
| Master | \<this\> | | 指定从主库查询(默认查询从库) |
| Caching | \<this\> | int seconds, string key = null | 缓存查询结果 |

View File

@ -1,129 +0,0 @@
# 更新数据
| 方法 | 返回值 | 参数 | 描述 |
| - | - | - | - |
| SetSource | \<this\> | T1 \| IEnumerable<T1> | 更新数据,设置更新的实体 |
| IgnoreColumns | \<this\> | Lambda | 忽略的列 |
| Set | \<this\> | Lambda, value | 设置列的新值Set(a => a.Name, "newvalue") |
| Set | \<this\> | Lambda | 设置列的的新值为基础上增加Set(a => a.Clicks + 1),相当于 clicks=clicks+1; |
| SetRaw | \<this\> | string, parms | 设置值自定义SQL语法SetRaw("title = ?title", new { title = "newtitle" }) |
| Where | \<this\> | Lambda | 表达式条件,仅支持实体基础成员(不包含导航对象) |
| Where | \<this\> | string, parms | 原生sql语法条件Where("id = ?id", new { id = 1 }) |
| Where | \<this\> | T1 \| IEnumerable<T1> | 传入实体或集合,将其主键作为条件 |
| WhereExists | \<this\> | ISelect | 子查询是否存在 |
| ToSql | string | | 返回即将执行的SQL语句 |
| ExecuteAffrows | long | | 执行SQL语句返回影响的行数 |
| ExecuteUpdated | List\<T1\> | | 执行SQL语句返回更新后的记录 |
### 列优先级
> 全部列 < 指定列(Set/SetRaw) < 忽略列(IgnoreColumns)
### 测试代码
```csharp
[Table(Name = "tb_topic")]
class Topic {
[Column(IsIdentity = true, IsPrimary = true)]
public int Id { get; set; }
public int Clicks { get; set; }
public TestTypeInfo Type { get; set; }
public string Title { get; set; }
public DateTime CreateTime { get; set; }
}
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10")
.Build();
IUpdate<Topic> update => fsql.Update<Topic>();
```
### 动态条件
```csharp
Update<Topic>(object dywhere)
```
dywhere 支持
* 主键值
* new[] { 主键值1, 主键值2 }
* Topic对象
* new[] { Topic对象1, Topic对象2 }
* new { id = 1 }
### 更新指定列
```csharp
var t1 = fsql.Update<Topic>(1).Set(a => a.CreateTime, DateTime.Now).ToSql();
//UPDATE `tb_topic` SET `CreateTime` = '2018-12-08 00:04:59' WHERE (`Id` = 1)
```
### 更新指定列,累加
```csharp
var t2 = fsql.Update<Topic>(1).Set(a => a.Clicks + 1).ToSql();
//UPDATE `tb_topic` SET `Clicks` = ifnull(`Clicks`,0) + 1 WHERE (`Id` = 1)
```
### 保存实体
```csharp
var item = new Topic { Id = 1, Title = "newtitle" };
var t3 = update.SetSource(item).ToSql();
//UPDATE `tb_topic` SET `Clicks` = ?p_0, `Title` = ?p_1, `CreateTime` = ?p_2 WHERE (`Id` = 1)
```
### 保存实体,忽略一些列
```csharp
var t4 = update.SetSource(item).IgnoreColumns(a => a.Clicks).ToSql();
//UPDATE `tb_topic` SET `Title` = ?p_0, `CreateTime` = ?p_1 WHERE (`Id` = 1)
var t5 = update.SetSource(item).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ToSql();
//UPDATE `tb_topic` SET `Title` = ?p_0 WHERE (`Id` = 1)
```
### 批量保存
```csharp
var items = new List<Topic>();
for (var a = 0; a < 10; a++) items.Add(new Topic { Id = a + 1, Title = $"newtitle{a}", Clicks = a * 100 });
var t6 = update.SetSource(items).ToSql();
//UPDATE `tb_topic` SET `Clicks` = CASE `Id` WHEN 1 THEN ?p_0 WHEN 2 THEN ?p_1 WHEN 3 THEN ?p_2 WHEN 4 THEN ?p_3 WHEN 5 THEN ?p_4 WHEN 6 THEN ?p_5 WHEN 7 THEN ?p_6 WHEN 8 THEN ?p_7 WHEN 9 THEN ?p_8 WHEN 10 THEN ?p_9 END, `Title` = CASE `Id` WHEN 1 THEN ?p_10 WHEN 2 THEN ?p_11 WHEN 3 THEN ?p_12 WHEN 4 THEN ?p_13 WHEN 5 THEN ?p_14 WHEN 6 THEN ?p_15 WHEN 7 THEN ?p_16 WHEN 8 THEN ?p_17 WHEN 9 THEN ?p_18 WHEN 10 THEN ?p_19 END, `CreateTime` = CASE `Id` WHEN 1 THEN ?p_20 WHEN 2 THEN ?p_21 WHEN 3 THEN ?p_22 WHEN 4 THEN ?p_23 WHEN 5 THEN ?p_24 WHEN 6 THEN ?p_25 WHEN 7 THEN ?p_26 WHEN 8 THEN ?p_27 WHEN 9 THEN ?p_28 WHEN 10 THEN ?p_29 END WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))
```
> 批量保存的场景先查询20条记录根据本地很复杂的规则把集合的值改完后
> 传统做法是循环20次保存用 case when 只要一次就行
### 批量保存,忽略一些列
```csharp
var t7 = update.SetSource(items).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ToSql();
//UPDATE `tb_topic` SET `Title` = CASE `Id` WHEN 1 THEN ?p_0 WHEN 2 THEN ?p_1 WHEN 3 THEN ?p_2 WHEN 4 THEN ?p_3 WHEN 5 THEN ?p_4 WHEN 6 THEN ?p_5 WHEN 7 THEN ?p_6 WHEN 8 THEN ?p_7 WHEN 9 THEN ?p_8 WHEN 10 THEN ?p_9 END WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))
```
### 批量更新指定列
```csharp
var t8 = update.SetSource(items).Set(a => a.CreateTime, DateTime.Now).ToSql();
//UPDATE `tb_topic` SET `CreateTime` = ?p_0 WHERE (`Id` IN (1,2,3,4,5,6,7,8,9,10))
```
> 指定列更新后,批量保存将失效
### 更新条件
> 除了顶上介绍的 dywhere 构造参数外,还支持 Where lambda/sql 方法
```csharp
var t9 = update.Set(a => a.Title, "新标题").Where(a => a.Id == 1).ToSql();
//UPDATE `tb_topic` SET `Title` = '新标题' WHERE (Id = 1)
```
### 自定义SQL
```csharp
var t10 = update.SetRaw("Title = {0}", "新标题").Where("Id = {0}", 1).ToSql();
//UPDATE `tb_topic` SET Title = '新标题' WHERE (Id = 1)
//sql语法条件参数使用 {0},与 string.Format 保持一致,无须加单引号,错误的用法:'{0}'
```
### 执行命令
| 方法 | 返回值 | 参数 | 描述 |
| - | - | - | - |
| ExecuteAffrows | long | | 执行SQL语句返回影响的行数 |
| ExecuteUpdated | List\<T1\> | | 执行SQL语句返回更新后的记录 |

View File

@ -1,284 +1,216 @@
这是 [FreeSql](https://github.com/2881099/FreeSql) 衍生出来的扩展包,包含 DbContext & DbSet、Repository & UnitOfWork 实现面向对象的特性QQ群4336577 FreeSql.DbContext 实现类似 EFCore 使用习惯,跟踪对象状态,最终通过 SaveChanges 方法提交事务。
## 安装
> dotnet add package FreeSql.DbContext > dotnet add package FreeSql.DbContext
## 更新日志 ## 如何使用
### v0.6.5 0、通用方法为啥是0
```
- 修复 Repository 级联保存的 bug using (var ctx = fsql.CreateDbContext()) {
- 添加工作单元开启方法; //var db1 = ctx.Set<Song>();
- 适配 .net framework 4.5、netstandard 2.0 //var db2 = ctx.Set<Tag>();
### v0.6.1
- 拆分 FreeSql 小包引用,各数据库单独包、延时加载包;
- FreeSql.Extensions.LazyLoading
- FreeSql.Provider.MySql
- FreeSql.Provider.PostgreSQL
- FreeSql.Provider.SqlServer
- FreeSql.Provider.Sqlite
- FreeSql.Provider.Oracle
- 移除 IFreeSql.Cache以及 ISelect.Caching 方法;
- 移除 IFreeSql.Log包括内部原有的日志输出改为 Trace.WriteLine
- IAdo.Query\<dynamic\> 读取返回变为 List\<Dictionary\<string, object\>\>
- 定义 IFreeSql 和以前一样,移除了 UseCache、UseLogger 方法;
## DbContext & DbSet
```csharp
using (var ctx = new SongContext()) {
var song = new Song { BigNumber = "1000000000000000000" };
ctx.Songs.Add(song);
song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString();
ctx.Songs.Update(song);
var tag = new Tag {
Name = "testaddsublist",
Tags = new[] {
new Tag { Name = "sub1" },
new Tag { Name = "sub2" },
new Tag {
Name = "sub3",
Tags = new[] {
new Tag { Name = "sub3_01" }
}
}
}
};
ctx.Tags.Add(tag);
var item = new Song { };
ctx.Add(item);
ctx.SaveChanges(); ctx.SaveChanges();
} }
``` ```
## Repository & UnitOfWork > 注意DbContext 对象多线程不安全
仓储与工作单元一起使用,工作单元具有事务特点。 1、在 OnConfiguring 方法上配置与 IFreeSql 关联
```csharp ```csharp
using (var unitOfWork = fsql.CreateUnitOfWork()) { public class SongContext : DbContext {
var songRepository = unitOfWork.GetRepository<Song, int>();
var tagRepository = unitOfWork.GetRepository<Tag, int>();
var song = new Song { BigNumber = "1000000000000000000" }; public DbSet<Song> Songs { get; set; }
songRepository.Insert(song); public DbSet<Song> Tags { get; set; }
songRepository.Update(song); protected override void OnConfiguring(DbContextOptionsBuilder builder) {
builder.UseFreeSql(dbcontext_01.Startup.Fsql);
song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); }
songRepository.Update(song);
var tag = new Tag {
Name = "testaddsublist",
Tags = new[] {
new Tag { Name = "sub1" },
new Tag { Name = "sub2" },
new Tag {
Name = "sub3",
Tags = new[] {
new Tag { Name = "sub3_01" }
}
}
}
};
tagRepository.Insert(tag);
ctx.Commit();
} }
```
## Repository
简单使用仓储,有状态跟踪,它不包含事务的特点。
```csharp
var songRepository = fsql.GetRepository<Song, int>();
var song = new Song { BigNumber = "1000000000000000000" };
songRepository.Insert(song);
```
## IFreeSql 核心定义
```csharp
var fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\dd2.db;Pooling=true;Max Pool Size=10")
.UseAutoSyncStructure(true)
.UseNoneCommandParameter(true)
.UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText))
.Build();
public class Song { public class Song {
[Column(IsIdentity = true)] [Column(IsIdentity = true)]
public int Id { get; set; } public int Id { get; set; }
public string BigNumber { get; set; } public DateTime? Create_time { get; set; }
public bool? Is_deleted { get; set; }
[Column(IsVersion = true)] //乐观锁 public string Title { get; set; }
public long versionRow { get; set; } public string Url { get; set; }
}
public class Tag {
[Column(IsIdentity = true)]
public int Id { get; set; }
public int? Parent_id { get; set; }
public virtual Tag Parent { get; set; }
public string Name { get; set; }
public virtual ICollection<Tag> Tags { get; set; } public virtual ICollection<Tag> Tags { get; set; }
} }
public class Song_tag {
public int Song_id { get; set; }
public virtual Song Song { get; set; }
public class SongContext : DbContext { public int Tag_id { get; set; }
public DbSet<Song> Songs { get; set; } public virtual Tag Tag { get; set; }
public DbSet<Tag> Tags { get; set; } }
protected override void OnConfiguring(DbContextOptionsBuilder builder) { public class Tag {
builder.UseFreeSql(fsql); [Column(IsIdentity = true)]
} public int Id { get; set; }
public int? Parent_id { get; set; }
public virtual Tag Parent { get; set; }
public decimal? Ddd { get; set; }
public string Name { get; set; }
public virtual ICollection<Song> Songs { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
} }
``` ```
# 过滤器与验证 使用的时候与 EFCore 类似:
假设我们有User(用户)、Topic(主题)两个实体,在领域类中定义了两个仓储:
```csharp ```csharp
var userRepository = fsql.GetGuidRepository<User>(); long id = 0;
var topicRepository = fsql.GetGuidRepository<Topic>();
using (var ctx = new SongContext()) {
var song = new Song { };
await ctx.Songs.AddAsync(song);
id = song.Id;
var adds = Enumerable.Range(0, 100)
.Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" })
.ToList();
await ctx.Songs.AddRangeAsync(adds);
for (var a = 0; a < adds.Count; a++)
adds[a].Title = "dkdkdkdk" + a;
ctx.Songs.UpdateRange(adds);
ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList());
//ctx.Songs.Update(adds.First());
adds.Last().Url = "skldfjlksdjglkjjcccc";
ctx.Songs.Update(adds.Last());
//throw new Exception("回滚");
await ctx.SaveChangesAsync();
}
``` ```
在开发过程中,总是担心 topicRepository 的数据安全问题即有可能查询或操作到其他用户的主题。因此我们在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。 2、注入方式使用
```csharp ```csharp
var userRepository = fsql.GetGuidRepository<User>(a => a.Id == 1); public void ConfigureServices(IServiceCollection services)
var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1); {
services.AddSingleton<IFreeSql>(Fsql);
services.AddFreeDbContext<SongContext>(options => options.UseFreeSql(Fsql));
}
``` ```
* 在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据; 在 mvc 中获取:
* 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常;
# 分表与分库
FreeSql 提供 AsTable 分表的基础方法GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。
```csharp ```csharp
var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString("YYYYMM")}"); IFreeSql _orm;
public ValuesController(SongContext songContext) {
}
``` ```
上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。 ## 优先级
合并两个仓储,实现分表下的联表查询: OnConfiguring > AddFreeDbContext
## 乐观锁
更新实体数据在并发情况下极容易造成旧数据将新的记录更新。FreeSql 核心部分已经支持乐观锁。
乐观锁的原理是利用实体某字段long version更新前先查询数据此时 version 为 1更新时产生的 SQL 会附加 where version = 1当修改失败时即 Affrows == 0抛出异常。
每个实体只支持一个乐观锁,在属性前标记特性:[Column(IsVersion = true)] 即可。
> 无论是使用 FreeSql/FreeSql.Repository/FreeSql.DbContext每次更新 version 的值都会增加 1
## 说明
- DbContext 操作的数据在最后 SaveChanges 时才批量保存;
- DbContext 内所有操作,使用同一个事务;
- 当实体存在自增时,或者 Add/AddRange 的时候主键值为空,会提前开启事务;
- 支持同步/异步方法;
## 合并机制
db.Add(new Xxx());
db.Add(new Xxx());
db.Add(new Xxx());
这三步,会合并成一个批量插入的语句执行,前提是它们没有自增属性。
适用 Guid 主键Guid 主键的值不用设置,交给 FreeSql 处理即可,空着的 Guid 主键会在插入时获取有序不重值的 Guid 值。
又比如:
db.Add(new Xxx());
db.Add(new Xxx());
db.Update(xxx);
db.Add(new Xxx());
Guid Id 的情况下执行三次命令前两次插入合并执行update 为一次,后面的 add 为一次。
## 联级保存
请移步文档[《联级保存》](https://github.com/2881099/FreeSql/wiki/%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98)
## 实体变化事件
全局设置:
```csharp ```csharp
fsql.GetGuidRepository<User>().Select.FromRepository(logRepository) fsql.SetDbContextOptions(opt => {
.LeftJoin<Log>(b => b.UserId == a.Id) opt.OnEntityChange = report => {
.ToList(); Console.WriteLine(report);
};
});
``` ```
注意事项: 单独设置 DbContext 或者 UnitOfWork
* 不能使用 CodeFirst 迁移分表,开发环境时仍然可以迁移 Log 表; ```csharp
* 不可在分表分库的实体类型中使用《延时加载》; var ctx = fsql.CreateDbContext();
ctx.Options.OnEntityChange = report => {
Console.WriteLine(report);
};
# 历史版本 var uow = fsql.CreateUnitOfWork();
uow.OnEntityChange = report => {
Console.WriteLine(report);
};
```
### v0.5.23 参数 report 是一个 List 集合,集合元素的类型定义如下:
- 增加 DbSet/Repository FlushState 手工清除状态管理数据; ```csharp
public class EntityChangeInfo
{
public object Object { get; set; }
public EntityChangeType Type { get; set; }
}
public enum EntityChangeType { Insert, Update, Delete, SqlRaw }
```
### v0.5.21 | 变化类型 | 说明 |
| -- | -- |
| Insert | 实体对象被插入 |
| Update | 实体对象被更新 |
| Delete | 实体对象被删除 |
| SqlRaw | 执行了SQL语句 |
- 修复 AddOrUpdate/InsertOrUpdate 当主键无值时,仍然查询了一次数据库; SqlRaw 目前有两处地方比较特殊:
- 增加 查询数据时 TrackToList 对导航集合的状态跟踪; - 多对多联级更新导航属性的时候,对中间表的全部删除操作;
- 完善 AddOrUpdateNavigateList 级联保存,忽略标记 IsIgnore 的集合属性; - 通用仓储类 BaseRepository 有一个 Delete 方法,参数为表达式,而并非实体;
- 完成 IFreeSql.Include、IncludeMany 功能; ```csharp
int Delete(Expression<Func<TEntity, bool>> predicate);
```
### v0.5.12 DbContext.SaveChanges或者 Repository 对实体的 Insert/Update/Delete或者 UnitOfWork.Commit 操作都会最多触发一次该事件。
- 增加 工作单元开关,可在任意 Insert/Update/Delete 之前调用,以关闭工作单元使其失效;[PR #1](https://github.com/2881099/FreeSql.DbContext/pull/1)
### v0.5.9
- 增加 linq to sql 的查询语法,以及单元测试,[wiki](https://github.com/2881099/FreeSql/wiki/LinqToSql)
- 修复 EnableAddOrUpdateNavigateList 设置对异步方法无效的 bug
### v0.5.8
- 增加 IFreeSql.SetDbContextOptions 设置 DbContext 的功能开启或禁用连级一对多导航集合属性保存的功能EnableAddOrUpdateNavigateList默认开启
- 增加 IUnitOfWork.IsolationLevel 设置事务级别;
### v0.5.7
- 修复 UnitOfWork.GetRepository() 事务 bug原因仓储的每步操作都提交了事务
### v0.5.5
- 修复 MapEntityValue 对 IsIgnore 未处理的 bug
### v0.5.4
- 修复 Repository 追加导航集合的保存 bug
- 公开 IRepository.Orm 对象;
### v0.5.3
- 修复 实体跟踪的 bug当查询到的实体自增值为 0 时重现;
- 优化 状态管理字典为 ConcurrentDictionary
### v0.5.2
- 优化 SqlServer UnitOfWork 使用bug在 FreeSql 内部解决的;
- 补充 测试与支持联合主键的自增;
### v0.5.1
- 补充 开放 DbContext.UnitOfWork 对象,方便扩展并保持在同一个事务执行;
- 补充 增加 DbSet\<object\>、Repository\<object\> 使用方法,配合 AsType(实体类型),实现弱类型操作;
- 修复 DbContext.AddOrUpdate 传入 null 时,任然会查询一次数据库的 bug
- 优化 DbContext.AddOrUpdate 未添加实体主键的错误提醒;
- 修复 DbContext.Set\<object\> 缓存的 bug使用多种弱类型时发生
- 修复 IsIgnore 过滤字段后,查询的错误;
- 修复 全局过滤器功能迁移的遗留 bug
### v0.4.14
- 优化 Add 时未设置主键的错误提醒;
### v0.4.13
- 补充 Repository 增加 Attach 方法;
- 优化 Update/AddOrUpdate 实体的时候,若状态管理不存在,尝试查询一次数据库,以便跟踪对象;
### v0.4.12
- 修复 非自增情况下Add 后再 Update 该实体时,错误(需要先 Attach 或查询)的 bug
### v0.4.10
- 补充 开放 DbContext.Orm 对象;
- 修复 OnConfiguring 未配置时注入获取失败的 bug
### v0.4.6
- 修复 DbSet AddRange/UpdateRange/RemoveRange 参数为空列表时报错,现在不用判断 data.Any() == true 再执行;
- 增加 DbContext 对 DbSet 的快速代理方法(Add/Update/Remove/Attach)
- 增加 DbContext 通用类命名为FreeContext也可以通过 IFreeSql 扩展方法 CreateDbContext 创建;
- 增加 ISelect NoTracking 扩展方法,查询数据时不追踪(从而提升查询性能);
### v0.4.5
- 增加 DbSet Attach 方法附加实体,可用于不查询就更新或删除;
### v0.4.2
- 增加 DbSet UpdateAsync/UpdateRangeAsync 方法,当一个实体被更新两次时,会先执行前面的队列;
- 增加 GetRepository 获取联合主键的适用仓储类;
- 增加 DbSet 在 Add/Update 时对导航属性(OneToMany) 的处理AddOrUpdate
### v0.4.1
- 独立 FreeSql.DbContext 项目;
- 实现 Repository + DbSet 统一的状态跟踪与工作单元;
- 增加 DbSet AddOrUpdate 方法;
- 增加 Repository InsertOrUpdate 方法;

View File

@ -1,95 +1,68 @@
这是 [FreeSql](https://github.com/2881099/FreeSql) 衍生出来的扩展包,包含 Repository & UnitOfWork 实现面向对象的特性QQ群4336577 FreeSql.Repository 作为扩展实现了通用仓储层功能。与其他规范标准一样仓储层也有相应的规范定义。FreeSql.Repository 参考 abp vnext 接口定义和实现基础的仓储层CURD应该算比较通用的方法吧。
## 安装
> dotnet add package FreeSql.Repository > dotnet add package FreeSql.Repository
## Repository & UnitOfWork ## 定义
仓储与工作单元一起使用,工作单元具有事务特点。
```csharp ```csharp
using (var unitOfWork = fsql.CreateUnitOfWork()) { static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
var songRepository = unitOfWork.GetRepository<Song, int>(); .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10")
var tagRepository = unitOfWork.GetRepository<Tag, int>(); .UseAutoSyncStructure(true) //自动迁移实体的结构到数据库
.Build(); //请务必定义成 Singleton 单例模式
var song = new Song { BigNumber = "1000000000000000000" };
songRepository.Insert(song);
songRepository.Update(song);
song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString();
songRepository.Update(song);
var tag = new Tag {
Name = "testaddsublist",
Tags = new[] {
new Tag { Name = "sub1" },
new Tag { Name = "sub2" },
new Tag {
Name = "sub3",
Tags = new[] {
new Tag { Name = "sub3_01" }
}
}
}
};
tagRepository.Insert(tag);
ctx.Commit();
}
```
## Repository
简单使用仓储,有状态跟踪,它不包含事务的特点。
```csharp
var songRepository = fsql.GetRepository<Song, int>();
var song = new Song { BigNumber = "1000000000000000000" };
songRepository.Insert(song);
```
## IFreeSql 核心定义
```csharp
var fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\dd2.db;Pooling=true;Max Pool Size=10")
.UseAutoSyncStructure(true)
.UseNoneCommandParameter(true)
.UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText))
.Build();
public class Song { public class Song {
[Column(IsIdentity = true)] [Column(IsIdentity = true)]
public int Id { get; set; } public int Id { get; set; }
public string BigNumber { get; set; } public string Title { get; set; }
[Column(IsVersion = true)] //乐观锁
public long versionRow { get; set; }
}
public class Tag {
[Column(IsIdentity = true)]
public int Id { get; set; }
public int? Parent_id { get; set; }
public virtual Tag Parent { get; set; }
public string Name { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class SongContext : DbContext {
public DbSet<Song> Songs { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder) {
builder.UseFreeSql(fsql);
}
} }
``` ```
# 过滤器与验证 ## 使用方法
1、IFreeSql 的扩展方法;
```csharp
var curd = fsql.GetRepository<Song>();
```
> 注意Repository对象多线程不安全
2、继承实现
```csharp
public class SongRepository : BaseRepository<Song, int> {
public SongRepository(IFreeSql fsql) : base(fsql, null, null) {}
//在这里增加 CURD 以外的方法
}
```
3、依赖注入
```csharp
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IFreeSql>(Fsql);
services.AddFreeRepository(filter => filter
.Apply<ISoftDelete>("SoftDelete", a => a.IsDeleted == false)
.Apply<ITenant>("Tenant", a => a.TenantId == 1)
,
this.GetType().Assembly
);
}
//在控制器使用
public SongsController(GuidRepository<Song> repos1) {
}
```
> 依赖注入的方式可实现全局【过滤与验证】的设定,方便租户功能的设计;
更多资料:[《过滤器、全局过滤器》](https://github.com/2881099/FreeSql/wiki/%e8%bf%87%e6%bb%a4%e5%99%a8)
## 过滤与验证
假设我们有User(用户)、Topic(主题)两个实体,在领域类中定义了两个仓储: 假设我们有User(用户)、Topic(主题)两个实体,在领域类中定义了两个仓储:
@ -98,7 +71,7 @@ var userRepository = fsql.GetGuidRepository<User>();
var topicRepository = fsql.GetGuidRepository<Topic>(); var topicRepository = fsql.GetGuidRepository<Topic>();
``` ```
在开发过程中,总是担心 topicRepository 的数据安全问题即有可能查询或操作到其他用户的主题。因此我们在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。 在开发过程中,总是担心 topicRepository 的数据安全问题即有可能查询或操作到其他用户的主题。因此我们在v0.0.7版本进行了改进,增加了 filter lambda 表达式参数。
```csharp ```csharp
var userRepository = fsql.GetGuidRepository<User>(a => a.Id == 1); var userRepository = fsql.GetGuidRepository<User>(a => a.Id == 1);
@ -108,7 +81,7 @@ var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);
* 在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据; * 在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据;
* 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常; * 在添加时,使用表达式验证数据的合法性,若不合法则抛出异常;
# 分表与分库 ## 分表与分库
FreeSql 提供 AsTable 分表的基础方法GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。 FreeSql 提供 AsTable 分表的基础方法GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。
@ -118,15 +91,148 @@ var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{Da
上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。 上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。
合并两个仓储,实现分表下的联表查询:
```csharp
fsql.GetGuidRepository<User>().Select.FromRepository(logRepository)
.LeftJoin<Log>(b => b.UserId == a.Id)
.ToList();
```
注意事项: 注意事项:
* 不能使用 CodeFirst 迁移分表,开发环境时仍然可以迁移 Log 表; * v0.11.12以后的版本可以使用 CodeFirst 迁移分表;
* 不可在分表分库的实体类型中使用《延时加载》; * 不可在分表分库的实体类型中使用《延时加载》;
## 兼容问题
FreeSql 支持五种数据库,分别为 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/达梦,虽然他们都为关系型数据库,但各自有着独特的技术亮点,有许多亮点值得我们使用;
比如 SqlServer 提供的 output inserted 特性,在表使用了自增或数据库定义了默认值的时候,使用它可以快速将 insert 的数据返回。PostgreSQL 也有相应的功能,如此方便却不是每个数据库都支持。
IRepository 接口定义:
```csharp
TEntity Insert(TEntity entity);
Task<TEntity> InsertAsync(TEntity entity);
```
于是我们做了两种仓库层实现:
- BaseRepository 采用 ExecuteInserted 执行;
- GuidRepository 采用 ExecuteAffrows 执行(兼容性好);
当采用了不支持的数据库时Sqlite/MySql/Oracle建议
* 使用 uuid 作为主键(即 Guid
* 避免使用数据库的默认值功能;
* 仓储层实现请使用 GuidRepository
## UnitOfWork
UnitOfWork 可将多个仓储放在一个单元管理执行,最终通用 Commit 执行所有操作,内部采用了数据库事务;
```csharp
using (var uow = fsql.CreateUnitOfWork()) {
var songRepo = uow.GetRepository<Song>();
var userRepo = uow.GetRepository<User>();
//上面两个仓储由同一UnitOfWork uow 创建
//在此执行仓储操作
//这里不受异步方便影响
uow.Commit();
}
```
参考:在 asp.net core 中注入工作单元方法
```csharp
//第一步:
public class UnitOfWorkRepository<TEntity, TKey> : BaseRepository<TEntity, TKey>
{
public UnitOfWorkRepository(IFreeSql fsql, IUnitOfWork uow) : base(fsql, null, null)
{
this.UnitOfWork = uow;
}
}
public class UnitOfWorkRepository<TEntity> : BaseRepository<TEntity, int>
{
public UnitOfWorkRepository(IFreeSql fsql, IUnitOfWork uow) : base(fsql, null, null)
{
this.UnitOfWork = uow;
}
}
//第二步:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFreeSql>(fsql);
services.AddScoped<FreeSql.IUnitOfWork>(sp => fsql.CreateUnitOfWork());
services.AddScoped(typeof(IReadOnlyRepository<>), typeof(UnitOfWorkRepository<>));
services.AddScoped(typeof(IBasicRepository<>), typeof(UnitOfWorkRepository<>));
services.AddScoped(typeof(BaseRepository<>), typeof(UnitOfWorkRepository<>));
services.AddScoped(typeof(IReadOnlyRepository<,>), typeof(UnitOfWorkRepository<,>));
services.AddScoped(typeof(IBasicRepository<,>), typeof(UnitOfWorkRepository<,>));
services.AddScoped(typeof(BaseRepository<,>), typeof(UnitOfWorkRepository<,>));
//批量注入程序集内的所有自建仓储类,可以根据自己需要来修改
Assembly[] assemblies = new [] { typeof(XxxRepository).Assembly };
if (assemblies?.Any() == true)
foreach (var asse in assemblies)
foreach (var repo in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(UnitOfWorkRepository).IsAssignableFrom(a)))
services.AddScoped(repo);
}
```
## 联级保存
请移步文档[《联级保存》](https://github.com/2881099/FreeSql/wiki/%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98)
## 实体变化事件
全局设置:
```csharp
fsql.SetDbContextOptions(opt => {
opt.OnEntityChange = report => {
Console.WriteLine(report);
};
});
```
单独设置 DbContext 或者 UnitOfWork
```csharp
var ctx = fsql.CreateDbContext();
ctx.Options.OnEntityChange = report => {
Console.WriteLine(report);
};
var uow = fsql.CreateUnitOfWork();
uow.OnEntityChange = report => {
Console.WriteLine(report);
};
```
参数 report 是一个 List 集合,集合元素的类型定义如下:
```csharp
public class EntityChangeInfo
{
public object Object { get; set; }
public EntityChangeType Type { get; set; }
}
public enum EntityChangeType { Insert, Update, Delete, SqlRaw }
```
| 变化类型 | 说明 |
| -- | -- |
| Insert | 实体对象被插入 |
| Update | 实体对象被更新 |
| Delete | 实体对象被删除 |
| SqlRaw | 执行了SQL语句 |
SqlRaw 目前有两处地方比较特殊:
- 多对多联级更新导航属性的时候,对中间表的全部删除操作;
- 通用仓储类 BaseRepository 有一个 Delete 方法,参数为表达式,而并非实体;
```csharp
int Delete(Expression<Func<TEntity, bool>> predicate);
```
DbContext.SaveChanges或者 Repository 对实体的 Insert/Update/Delete或者 UnitOfWork.Commit 操作都会最多触发一次该事件。

227
readme.md
View File

@ -1,12 +1,14 @@
<p align="center"> <p align="center">
<img height="210" src="https://github.com/2881099/FreeSql/blob/master/logo.png?raw=true"/> <img height="160" src="https://github.com/2881099/FreeSql/blob/master/logo.png?raw=true"/>
</p> </p>
FreeSql 是功能强大的对象关系映射技术(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.0+ 或 Xamarin FreeSql 是功能强大的对象关系映射技术(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.0+ 或 Xamarin
扶摇直上至强ORM只为自由编码鹏程万里至简Linq可使保留黑发横批FreeSql诗人Coder 扶摇直上至强ORM只为自由编码鹏程万里至简Linq可使保留黑发横批FreeSql诗人Coder
# Features [![nuget](https://img.shields.io/nuget/v/FreeSql.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql) [![stats](https://img.shields.io/nuget/dt/FreeSql.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql?groupby=Version) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/2881099/FreeSql/master/LICENSE.txt)
## Features
- [x] 支持 CodeFirst 迁移,哪怕使用 Access 数据库也支持; - [x] 支持 CodeFirst 迁移,哪怕使用 Access 数据库也支持;
- [x] 支持 DbFirst 从数据库导入实体类,[安装实体类生成工具](https://github.com/2881099/FreeSql/wiki/DbFirst) - [x] 支持 DbFirst 从数据库导入实体类,[安装实体类生成工具](https://github.com/2881099/FreeSql/wiki/DbFirst)
@ -16,29 +18,22 @@ FreeSql 是功能强大的对象关系映射技术(O/RM),支持 .NETCore 2.1+
- [x] 支持 读写分离、分表分库,租户设计,过滤器,乐观锁,悲观锁; - [x] 支持 读写分离、分表分库,租户设计,过滤器,乐观锁,悲观锁;
- [x] 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/达梦数据库/Access - [x] 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/达梦数据库/Access
| | | ## Documentation
| - | - |
| <img src="https://user-images.githubusercontent.com/16286519/55138232-f5e19e80-516d-11e9-9144-173cc7e52845.png" width="40" height="59"/> | [《新人学习指引》](https://www.cnblogs.com/FreeSql/p/11531300.html) \| [《Select》](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2) \| [《Update》](https://github.com/2881099/FreeSql/wiki/%e4%bf%ae%e6%94%b9) \| [《Insert》](https://github.com/2881099/FreeSql/wiki/%e6%b7%bb%e5%8a%a0) \| [《Delete》](https://github.com/2881099/FreeSql/wiki/%e5%88%a0%e9%99%a4) |
| <img src="https://user-images.githubusercontent.com/16286519/55138241-faa65280-516d-11e9-8b27-139dea46e4df.png" width="40" height="59"/> | [《表达式函数》](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0) \| [《CodeFirst》](https://github.com/2881099/FreeSql/wiki/CodeFirst) \| [《DbFirst》](https://github.com/2881099/FreeSql/wiki/DbFirst) \| [《BaseEntity》](https://github.com/2881099/FreeSql/tree/master/Examples/base_entity) |
| <img src="https://user-images.githubusercontent.com/16286519/55138263-06921480-516e-11e9-8da9-81f18a18b694.png" width="40" height="59"/> | [《Repository》](https://github.com/2881099/FreeSql/wiki/Repository) \| [《UnitOfWork》](https://github.com/2881099/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83) \| [《过滤器》](https://github.com/2881099/FreeSql/wiki/%e8%bf%87%e6%bb%a4%e5%99%a8) \| [《乐观锁》](https://github.com/2881099/FreeSql/wiki/%e4%bf%ae%e6%94%b9#%E4%B9%90%E8%A7%82%E9%94%81) \| [《DbContext》](https://github.com/2881099/FreeSql/wiki/DbContext) |
| <img src="https://user-images.githubusercontent.com/16286519/55138284-0eea4f80-516e-11e9-8764-29264807f402.png" width="40" height="59"/> | [《读写分离》](https://github.com/2881099/FreeSql/wiki/%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb) \| [《分区分表》](https://github.com/2881099/FreeSql/wiki/%e5%88%86%e5%8c%ba%e5%88%86%e8%a1%a8) \| [《租户》](https://github.com/2881099/FreeSql/wiki/%e7%a7%9f%e6%88%b7) \| [《AOP》](https://github.com/2881099/FreeSql/wiki/AOP) \| [《黑科技》](https://github.com/2881099/FreeSql/wiki/%E9%AA%9A%E6%93%8D%E4%BD%9C) \| [*更新日志*](https://github.com/2881099/FreeSql/wiki/%e6%9b%b4%e6%96%b0%e6%97%a5%e5%bf%97) |
# Packages [《新人学习指引》](https://www.cnblogs.com/FreeSql/p/11531300.html)、[《Select》](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2)、[《Update》](https://github.com/2881099/FreeSql/wiki/%e4%bf%ae%e6%94%b9)、[《Insert》](https://github.com/2881099/FreeSql/wiki/%e6%b7%bb%e5%8a%a0)、[《Delete》](https://github.com/2881099/FreeSql/wiki/%e5%88%a0%e9%99%a4)
| Package Name | NuGet | Downloads | [《表达式函数》](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0)、[《CodeFirst》](https://github.com/2881099/FreeSql/wiki/CodeFirst)、[《DbFirst》](https://github.com/2881099/FreeSql/wiki/DbFirst)、[《BaseEntity》](https://github.com/2881099/FreeSql/tree/master/Examples/base_entity)
|--------------| ------- | ---- |
| FreeSql | [![nuget](https://img.shields.io/nuget/v/FreeSql.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql) | [![stats](https://img.shields.io/nuget/dt/FreeSql.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql?groupby=Version) |
| FreeSql.Repository | [![nuget](https://img.shields.io/nuget/v/FreeSql.Repository.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql.Repository) | [![stats](https://img.shields.io/nuget/dt/FreeSql.Repository.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql.Repository?groupby=Version) |
| FreeSql.DbContext | [![nuget](https://img.shields.io/nuget/v/FreeSql.DbContext.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql.DbContext) | [![stats](https://img.shields.io/nuget/dt/FreeSql.DbContext.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql.DbContext?groupby=Version) |
| [FreeSql.AdminLTE](https://github.com/2881099/FreeSql.AdminLTE) | [![nuget](https://img.shields.io/nuget/v/FreeSql.AdminLTE.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql.AdminLTE) | [![stats](https://img.shields.io/nuget/dt/FreeSql.AdminLTE.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql.AdminLTE?groupby=Version) |
> FreeSql 提供了五种使用习惯,请根据实际情况选择团队合适的一种: [《Repository》](https://github.com/2881099/FreeSql/wiki/Repository)、[《UnitOfWork》](https://github.com/2881099/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83)、[《过滤器》](https://github.com/2881099/FreeSql/wiki/%e8%bf%87%e6%bb%a4%e5%99%a8)、[《乐观锁》](https://github.com/2881099/FreeSql/wiki/%e4%bf%ae%e6%94%b9#%E4%B9%90%E8%A7%82%E9%94%81)、[《DbContext》](https://github.com/2881099/FreeSql/wiki/DbContext)
[《读写分离》](https://github.com/2881099/FreeSql/wiki/%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb)、[《分区分表》](https://github.com/2881099/FreeSql/wiki/%e5%88%86%e5%8c%ba%e5%88%86%e8%a1%a8)、[《租户》](https://github.com/2881099/FreeSql/wiki/%e7%a7%9f%e6%88%b7)、[《AOP》](https://github.com/2881099/FreeSql/wiki/AOP)、[《黑科技》](https://github.com/2881099/FreeSql/wiki/%E9%AA%9A%E6%93%8D%E4%BD%9C)、[*更新日志*](https://github.com/2881099/FreeSql/wiki/%e6%9b%b4%e6%96%b0%e6%97%a5%e5%bf%97)
> FreeSql 提供多种使用习惯,请根据实际情况选择团队合适的一种:
- 要么FreeSql原始用法 - 要么FreeSql原始用法
- 要么[FreeSql.Repository](https://github.com/2881099/FreeSql/wiki/Repository),仓储+工作单元习惯; - 要么[FreeSql.Repository](https://github.com/2881099/FreeSql/wiki/Repository),仓储+工作单元习惯;
- 要么[FreeSql.DbContext](https://github.com/2881099/FreeSql/wiki/DbContext)有点像efcore的使用习惯 - 要么[FreeSql.DbContext](https://github.com/2881099/FreeSql/wiki/DbContext)有点像efcore的使用习惯
- 要么[FreeSql.Connection.Extensions](https://github.com/2881099/FreeSql.Connection.Extensions)有点像Dapper的使用习惯 - 要么[FreeSql.BaseEntity](https://github.com/2881099/FreeSql/tree/master/Examples/base_entity),求简单使用这个;
- 要么[FreeSql.BaseEntity](https://github.com/2881099/FreeSql/tree/master/Examples/base_entity),我求简单现在使用的这个;
> [FluentApi 与 EfCore 90% 相似的扩展包](https://github.com/2881099/FreeSql/tree/master/Extensions/FreeSql.Extensions.EfCoreFluentApi) > [FluentApi 与 EfCore 90% 相似的扩展包](https://github.com/2881099/FreeSql/tree/master/Extensions/FreeSql.Extensions.EfCoreFluentApi)
@ -49,173 +44,139 @@ FreeSql 是功能强大的对象关系映射技术(O/RM),支持 .NETCore 2.1+
欢迎更多使用 FreeSql 的开源项目加入目录 欢迎更多使用 FreeSql 的开源项目加入目录
# Providers
| Package Name | Version |
|--------------| ------- |
| FreeSql.Provider.MySql | NETStandard2.0、net45、net40 |
| FreeSql.Provider.MySqlConnector | NETStandard2.0、net45 |
| FreeSql.Provider.PostgreSQL | NETStandard2.0、net45 |
| FreeSql.Provider.SqlServer | NETStandard2.0、net45、net40 |
| FreeSql.Provider.Sqlite | NETStandard2.0、net45、net40 |
| FreeSql.Provider.Oracle | NETStandard2.0、net45、net40 |
| [FreeSql.Provider.Odbc](https://github.com/2881099/FreeSql/tree/master/Providers/FreeSql.Provider.Odbc) | NETStandard2.0、net45、net40 |
| FreeSql.Extensions.LazyLoading | NETStandard2.0、net45、net40 |
| FreeSql.Extensions.JsonMap | NETStandard2.0、net45、net40 |
| FreeSql.Extensions.BaseEntity | NETStandard2.0 |
# ConnectionStrings
| DataType | ConnectionString |
| --- | --- |
| DataType.MySql | Data Source=127.0.0.1;Port=3306;User ID=root;Password=root; Initial Catalog=cccddd;Charset=utf8; SslMode=none;Min pool size=1 |
| DataType.PostgreSQL | Host=192.168.164.10;Port=5432;Username=postgres;Password=123456; Database=tedb;Pooling=true;Minimum Pool Size=1 |
| DataType.SqlServer | Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=1 |
| DataType.Oracle | user id=user1;password=123456; data source=//127.0.0.1:1521/XE;Pooling=true;Min Pool Size=1 |
| DataType.Sqlite | Data Source=\|DataDirectory\|\document.db; Attachs=xxxtb.db; Pooling=true;Min Pool Size=1 |
| DataType.OdbcMySql | Driver={MySQL ODBC 8.0 Unicode Driver}; Server=127.0.0.1;Persist Security Info=False; Trusted_Connection=Yes;UID=root;PWD=root; DATABASE=cccddd_odbc;Charset=utf8; SslMode=none;Min Pool Size=1 |
| DataType.OdbcSqlServer | Driver={SQL Server};Server=.;Persist Security Info=False; Trusted_Connection=Yes;Integrated Security=True; DATABASE=freesqlTest_odbc; Pooling=true;Min Pool Size=1 |
| DataType.OdbcOracle | Driver={Oracle in XE};Server=//127.0.0.1:1521/XE; Persist Security Info=False; Trusted_Connection=Yes;UID=odbc1;PWD=123456; Min Pool Size=1 |
| DataType.OdbcPostgreSQL | Driver={PostgreSQL Unicode(x64)};Server=192.168.164.10; Port=5432;UID=postgres;PWD=123456; Database=tedb_odbc;Pooling=true;Min Pool Size=1 |
| DataType.OdbcDameng (达梦) | Driver={DM8 ODBC DRIVER};Server=127.0.0.1:5236; Persist Security Info=False; Trusted_Connection=Yes; UID=USER1;PWD=123456789 |
| DataType.Odbc | Driver={SQL Server};Server=.;Persist Security Info=False; Trusted_Connection=Yes;Integrated Security=True; DATABASE=freesqlTest_odbc; Pooling=true;Min pool size=1 |
<p align="center"> <p align="center">
<img src="https://images.cnblogs.com/cnblogs_com/kellynic/133561/o_functions06.png"/> <img src="https://images.cnblogs.com/cnblogs_com/kellynic/133561/o_functions06.png"/>
</p> </p>
# Quick start ## Quick start
> dotnet add package FreeSql.Provider.Sqlite > dotnet add package FreeSql.Provider.Sqlite
```csharp ```csharp
static IFreeSql fsql = new FreeSql.FreeSqlBuilder() static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=document.db")
@"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10") .UseAutoSyncStructure(true) //自动同步实体结构到数据库
.UseAutoSyncStructure(true) //自动同步实体结构到数据库 .Build(); //请务必定义成 Singleton 单例模式
.Build(); //请务必定义成 Singleton 单例模式
class Song { class Song {
[Column(IsIdentity = true)] [Column(IsIdentity = true)]
public int Id { get; set; } public int Id { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string Url { get; set; } public string Url { get; set; }
public DateTime CreateTime { get; set; } public DateTime CreateTime { get; set; }
public virtual ICollection<Tag> Tags { get; set; } public virtual ICollection<Tag> Tags { get; set; }
} }
class Song_tag { class Song_tag {
public int Song_id { get; set; } public int Song_id { get; set; }
public virtual Song Song { get; set; } public virtual Song Song { get; set; }
public int Tag_id { get; set; } public int Tag_id { get; set; }
public virtual Tag Tag { get; set; } public virtual Tag Tag { get; set; }
} }
class Tag { class Tag {
[Column(IsIdentity = true)] [Column(IsIdentity = true)]
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public int? Parent_id { get; set; } public int? Parent_id { get; set; }
public virtual Tag Parent { get; set; } public virtual Tag Parent { get; set; }
public virtual ICollection<Song> Songs { get; set; } public virtual ICollection<Song> Songs { get; set; }
public virtual ICollection<Tag> Tags { get; set; } public virtual ICollection<Tag> Tags { get; set; }
} }
``` ```
# Query ## Query
```csharp ```csharp
//OneToOne、ManyToOne //OneToOne、ManyToOne
var t0 = fsql.Select<Tag>() fsql.Select<Tag>()
.Where(a => a.Parent.Parent.Name == "粤语") .Where(a => a.Parent.Parent.Name == "粤语")
.IncludeMany(a => a.Tags, then => then.Where(sub => sub.Name == "xxx")) .IncludeMany(a => a.Tags, then => then.Where(sub => sub.Name == "xxx"))
.ToList(); .ToList();
//OneToMany //OneToMany
var t1 = fsql.Select<Tag>() fsql.Select<Tag>()
.Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)) .Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10))
.ToList(); .ToList();
//ManyToMany //ManyToMany
var t2 = fsql.Select<Song>() fsql.Select<Song>()
.Where(s => s.Tags.AsSelect().Any(t => t.Name == "国语")) .Where(s => s.Tags.AsSelect().Any(t => t.Name == "国语"))
.IncludeMany(a => a.Tags, then => then.Where(sub => sub.Name == "xxx")) .IncludeMany(a => a.Tags, then => then.Where(sub => sub.Name == "xxx"))
.ToList(); .ToList();
//Other //Other
var t3 = fsql.Select<Xxx>() fsql.Select<Xxx>()
.Where(a => a.IsDelete == 0) .Where(a => a.IsDelete == 0)
.WhereIf(keyword != null, a => a.UserName.Contains(keyword)) .WhereIf(keyword != null, a => a.UserName.Contains(keyword))
.WhereIf(role_id > 0, a => a.RoleId == role_id) .WhereIf(role_id > 0, a => a.RoleId == role_id)
.Where(a => a.Nodes.AsSelect().Any(t => t.Parent.Id == t.UserId)) .Where(a => a.Nodes.AsSelect().Any(t => t.Parent.Id == t.UserId))
.Count(out var total) .Count(out var total)
.Page(page, size) .Page(page, size)
.OrderByDescending(a => a.Id) .OrderByDescending(a => a.Id)
.ToList() .ToList()
``` ```
更多前往Wiki[《Select 查询数据文档》](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2) 更多前往Wiki[《Select 查询数据文档》](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2)
```csharp ```csharp
var t3 = fsql.Select<Song>() fsql.Select<Song>()
.Where(a => new[] { 1, 2, 3 }.Contains(a.Id)) .Where(a => new[] { 1, 2, 3 }.Contains(a.Id))
.ToList(); .ToList();
```
```csharp fsql.Select<Song>()
var t4 = fsql.Select<Song>() .Where(a => a.CreateTime.Date == DateTime.Today)
.Where(a => a.CreateTime.Date == DateTime.Now.Date) .ToList();
.ToList();
``` fsql.Select<Song>()
```csharp .OrderBy(a => Guid.NewGuid())
var t5 = fsql.Select<Song>() .Limit(1)
.OrderBy(a => Guid.NewGuid()) .ToList();
.Limit(1)
.ToList();
``` ```
更多前往Wiki[《表达式函数》](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0) 更多前往Wiki[《表达式函数》](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0)
# Repository & UnitOfWork ## Repository & UnitOfWork
> dotnet add package FreeSql.Repository > dotnet add package FreeSql.Repository
```csharp ```csharp
using (var uow = fsql.CreateUnitOfWork()) { using (var uow = fsql.CreateUnitOfWork()) {
var repo1 = uow.GetRepository<Song, int>(); var repo1 = uow.GetRepository<Song>();
var repo2 = uow.GetRepository<Tag, int>(); var repo2 = uow.GetRepository<Tag>();
await repo1.InsertAsync(new Song()); await repo1.InsertAsync(new Song());
await repo2.InsertAsync(new Tag()); await repo2.InsertAsync(new Tag());
uow.Commit(); uow.Commit();
} }
``` ```
# DbContext & DbSet ## DbContext & DbSet
> dotnet add package FreeSql.DbContext > dotnet add package FreeSql.DbContext
```csharp ```csharp
using (var ctx = new fsql.CreateDbContext()) { using (var ctx = new fsql.CreateDbContext()) {
var songs = ctx.Set<Song>(); var songs = ctx.Set<Song>();
var tags = ctx.Set<Tag>(); var tags = ctx.Set<Tag>();
var tag = new Tag { var tag = new Tag {
Name = "testaddsublist", Name = "testaddsublist",
Tags = new[] {
new Tag { Name = "sub1" },
new Tag { Name = "sub2" },
new Tag {
Name = "sub3",
Tags = new[] { Tags = new[] {
new Tag { Name = "sub1" }, new Tag { Name = "sub3_01" }
new Tag { Name = "sub2" },
new Tag {
Name = "sub3",
Tags = new[] {
new Tag { Name = "sub3_01" }
}
}
} }
}; }
//tags.Add(tag); }
ctx.Add(tag); };
await ctx.SaveChangesAsync(); //tags.Add(tag);
ctx.Add(tag);
await ctx.SaveChangesAsync();
} }
``` ```
# Performance ## Performance
FreeSql Query & Dapper Query FreeSql Query & Dapper Query
```shell ```shell
@ -243,7 +204,7 @@ Elapsed: 00:00:00.6495301; Query Entity Counts: 131072; ORM: Dapper
[Test code](FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs)、[More](https://github.com/2881099/FreeSql/wiki/%e6%80%a7%e8%83%bd) [Test code](FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs)、[More](https://github.com/2881099/FreeSql/wiki/%e6%80%a7%e8%83%bd)
# Contributors ## Contributors
[systemhejiyong](https://github.com/systemhejiyong)、 [systemhejiyong](https://github.com/systemhejiyong)、
[LambertW](https://github.com/LambertW)、 [LambertW](https://github.com/LambertW)、
@ -260,7 +221,7 @@ Elapsed: 00:00:00.6495301; Query Entity Counts: 131072; ORM: Dapper
QQ群4336577 QQ群4336577
# Donation ## Donation
L*y 58元、花花 88元、麦兜很乖 50元、网络来者 2000元、John 99.99元、alex 666元 L*y 58元、花花 88元、麦兜很乖 50元、网络来者 2000元、John 99.99元、alex 666元