Dapper.Common基于Dapper的开源LINQ超轻量扩展 - 腾讯云开发者社区-腾讯云

mikel阅读(561)

来源: Dapper.Common基于Dapper的开源LINQ超轻量扩展 – 腾讯云开发者社区-腾讯云

Dapper.Common是基于Dapper的LINQ实现,支持.net core,遵循Linq语法规则、链式调用、配置简单、上手快,支持MySQL,SQLServer(目前只实现了这两个数据库,实现其他数据库也很轻松),支持单表,多表,自定义函数等功能。源码及其简单,直白,解析Lambda只有300行左右代码。严格区分C#函数和数据库函数,你可以在表达式中调用C#函数(不推荐,推荐将计算结果保存到变量,在写入lambda表达式),性能损失在表达式编译:常量>变量>函数。损失多少可以通过ExpressionUtil.BuildExpression()来测试,几万次耗时百毫秒级别。

开源地址:https://github.com/1448376744/Dapper.Common

Nuget:Install-Package Dapper.Common -Version 1.5.0

测试

映射

public class User
  {
       /// <summary>
       /// 如果表名与字段名一致,可以不用Column进行注解,主键采用类型的第一个属性【不推荐】
       /// name:用于映射字段名和数据库字段不一致【完全可以用T4一键生成我GitHub有现成的】
       /// key:
       ///     目前实现了Primary的定义,设置为Primary的字段update实体时,默认采用该字段为更新条件
       /// isIdentity:
       ///     设置未true时在Insert时不会向该字段设置任何值
       /// isColumn:
       ///     标识该字段是否在数据库存在,用于扩展User而不在sql中生成该字段
       /// </summary>
       [Column(name: "id", key: ColumnKey.Primary, isIdentity: true, isColumn: true)]
       public int? Id { get; set; }
       [Column(name:"nick_name")]
       public string NickName { get; set; }
       [Column(name: "create_time")]
       public DateTime? CreateTime { get; set; }
  }

配置

//在App启动时执行一次即可
SessionFactory.AddDataSource(new DataSource()
{
    Name = "mysql",
    Source = () => new SqlConnection("connectionString"),
    SourceType = DataSourceType.SQLSERVER,
    UseProxy = true//使用Session的静态代理实现,记录日志,执行耗时,线上环境建议关闭代理
});

//获取数据库上下文
using (var session = SessionFactory.GetSession("msql"))
{
    //linq to sql
}

使用

1.Insert

var entity = new User()
{
    CreateTime=DateTime.Now,
    NickName="dapper",
};
//绝大部分接口可以设置condition已决定是否执行,支持批量更新
session.From<User>().Insert(entity,condition:1>2);
//查看日志,如果出现异常,应该在catch里,查看session.Loggers
var loggers = session.Loggers;

2.Update

var entity = new User()
{
  Id=2,
  NickName="李四"
};
//更新所有字段(where id=2),支持批量,显然除NickName之外将被更新成null
session.From<User>().Update(entity);

//更新部分字段
session.From<User>()
   .Set(a => a.NickName, "李四", condition: true)//condition为true时更新该字段
   .Set(a => a.Balance, a => a.Balance + 100)//余额在原来基础增加100
   .Where(a => a.Id.In(1,2,3))//将id为1,2,3的记录进行更新
   .Update();

3.Delete

//删除id>5||nick_name like '%da%'
 session.From<User>()
    .Where(a=>a.Id>5||a.NickName.Like("da"))
    .Delete();

4.Single

//查询全部字段
  var user1 = session.From<User>()
      .Where(a=>a.Id==2)
      .Single();

  //查询部分字段
  var user2 = session.From<User>()
     .Where(a => a.Id == 2)
     .Single(s=>new
     {
         s.Id,
         s.NickName
     });

5.Select

//查询:where id in(1,2,3)
 var list = session.From<User>()
        .Where(a => a.Id.In("1,2,3".Split(',')))
        .Select();

6.Where

//构建动态查询,condition: true执行,通过condition选择分支,多个where之间用 and 连接
 var list = session.From<User>()
        .Where(a => a.Id.In(1, 2, 3), condition: true)
        .Where(a => a.NickName.Like("da"), condition: false)
        .Where(a => a.Id > 2 || (a.NickName.Like("da") && a.Balance > 50))
        .Where("select * from user_bill where user_bill.user_id=user.id")//同样可以当作字符串拼接工具
        .Select();

7.Function

/// <summary>
 /// 自定义函数
 /// </summary>
 public static class MySqlFun
 {
     //这里使用泛型并不是必须的,只用函数名在数据库存在即可,泛型为了指定返回数据类型
     [Function]//Dapper.Common严格区分C#函数和数据库函数,一定要用该特性标识数据库函数
     public static T COUNT<T>(T column)
     {
         return default(T);
     }
     [Function]
     public static T MAX<T>(T column)
     {
         return default(T);
     }
     [Function]
     public static T DISTINCT<T>(T column)
     {
         return default(T);
     }
   [Function]
   public static T DATE<T>(T column)
     {
     return default(T);
   }
  }

8.GroupBy

var list = session.From<Order>()
     .GroupBy(a => a.UserId)//多个条件可以new一个匿名对象,也可以并联多个group
     .Having(a => MySqlFun.COUNT(MySqlFun.DISTINCT(a.UserId)) > 10)//count(distinct(user_id))>10
     .Select(s => new
     {
         s.UserId,
         OrderCount = MySqlFun.COUNT(1L),//这里应该返回long int,
         MaxFee = MySqlFun.MAX(s.TotalFee)
     });

9.Join

var list = session.From<Order, User>()
     .Join((a, b) => a.UserId == b.Id, JoinType.Inner)
     .GroupBy((a, b) => a.UserId)
     .Having((a, b) => MySqlFun.COUNT(MySqlFun.DISTINCT(a.UserId)) > 10)//count(distinct(user_id))>10
     .Select((a, b) => new
     {
         a.UserId,
         b.NickName,
         OrderCount = MySqlFun.COUNT(1L),//这里应该返回long int,
         MaxFee = MySqlFun.MAX(a.TotalFee)
     });

10.SubQuery

var list = session.From<Order>()
    .GroupBy(a  => a.UserId)
    .Having(a => MySqlFun.COUNT(MySqlFun.DISTINCT(a.UserId)) > 10)
    .Select(a => new
    {
        a.UserId,
        UserName=Convert.ToString("select nick_name from user where user.id=order.user_id"),//如果这个子查询返回的是int:Convert.ToInt32(sql)
        OrderCount = MySqlFun.COUNT(1L),//这里应该返回long int【这就是为什么定义成泛型函数】,
        MaxFee = MySqlFun.MAX(a.TotalFee)
    });

11.Page

//分页应该写在Where,Having,Group之后(如果有)
var list = session.From<User>()
        .Where(a=>a.NickName != null)
        .Page(1,10,out long total)
        .Select();

12.Take

var list = session.From<User>()
        .Take(5)
        .Select();

13.Skip

//从数据库索引为1的位置(跳过1之前的记录),获取10
var list = session.From<User>()
    .Skip(1,10)
    .Select();

14.Sum

var list= session.From<User>()
     .Sum(s=>s.Balance*s.Id);

15.Exists

//内部采用exist子查询判断满足where条件的记录是否存在
var flag = seesion.From<User>()
    .Where(a=>a.Id > 10)
    .Exists();

16.OrderBy

var list1 = session.From<User>()
    .Order(a=>a.Id)
    .Select();

var list2 = session.From<User>()
    .GroupBy(a => MysqlFun.DATE(a.CreateTime))
    .OrderByDescending(a => MysqlFun.DATE(a.CreateTime))
    .Select(s=>new 
    {
         Date=MysqlFun.DATE(s.CreateTime),
         Count = MysqlFun.Count(1L)
    });

17.Filter

var user =new User ()
{
  Id = 12
  Balance = 50,
  NickName = "张三",
  CreateTime = Datetime.Now
};
//Filter会在Insert,Update,Select,过滤掉不想要的字段
//这将不会更新余额及创建时间
var row = session.From<User>()
    .Filter(f=>new 
    {
          f.CreateTime,
          f.Balance,
    })
    .Update(user);

18.Transaction

//获取数据库上下文
  ISession session = null;
  try
  {
    session=SessionFactory.GetSession();
      //开启事务
      session.Open(true);
      //sql
      //提交事务
      session.Commit();
  }
  catch (Exception)
  {
      session?.Rollback();
      throw;
  }
  finally
  {
      session?.Close();
  }

原文地址:https://cnblogs.com/chaeyeon/p/11028480.html

c# Dapper,添加,修改,删除,查询,事务,存储过程,含数据库(从入门到高级)_橙-极纪元的博客-CSDN博客

mikel阅读(842)

来源: c# Dapper,添加,修改,删除,查询,事务,存储过程,含数据库(从入门到高级)_橙-极纪元的博客-CSDN博客

目录

前言

介绍

重点提示

安装

使用NuGet安装

准备工作

数据库SQL (含存储过程)

模型类

Type.cs

Product.cs

数据库访问层

引入关键库

创建数据库连接

添加 3个方法

1.新增一条数据,不带事务

2.新增一条数据,且,返回自增ID;不带事务

3.新增多条数据,不带事务

修改 2个方法

1.修改一条数据,不带事务

2.修改多条数据,不带事务

删除 2个方法

1.删除一条数据,不带事务

2.删除多条数据,不带事务

查询/Join查询/函数查询/查询In操作/多语句查询/验证类型名是否存在 7个方法

1.查询一条数据

2.普通查询-列表

3.Join查询-列表

4.函数查询 和,平均值,最大值,最小值

5.查询In操作

6.多语句查询

7.验证类型名是否存在

事务 3个方法

1.事务第一种用法;添加一条分类的同时新增一条产品数据

2.事务第二种用法;添加一条分类的同时新增一条产品数据

3.新增(添加)多条数据,带事务

执行存储过程 3个方法

1.查询产品表,根据类型ID 查询出所有产品,且返回产品数量

SQL

2.插入新产品的存储过程,产品名存在就不插入

sql

3.获取产品表和产品类型表中的所有数据

sql

 

前言
介绍
C# Dapper高级编程;
添加方法3个,

修改方法2个,

删除方法2个,

查询方法7个(含Join),

事务方法3个,

存储过程方法3个

重点提示
一、ORM框架执行效率排序EF 7 8 Dapper执行效率 1 2

二、传参

传参有三种方式
第一种:model模型传参 如:User user=new User(); user.name=”小明”
第二种:匿名类型传参 如:var user = new {name=”小明”};
第三种:DynamicParameters 传参 如下:
DynamicParameters paras = new DynamicParameters();
paras.Add(“@name”, “小明”);//输入参数

三、

只有在BeginTransaction(事务)时要求连接是打开的conn.Open()。
而在不使用事务的时候,简单的增删改查可以不用这一句dbConnection.Open()(即conn.Open());
因为Dapper的大部分方法中有dbConnection.Open(),所以不需要每次都打开连接,程序会自己处理。

安装
使用NuGet安装
项目》引用》右键》管理NuGet程序包》左侧出现界面》浏览》输入“Dapper”》选中》左侧有个“安装”》点击安装

在解决方案管理器中点击项目,查看引用,如果有Dapper,说明安装成功。

 

准备工作
数据库sql (含存储过程)
USE [master]
GO
/****** Object: Database [DBTase] Script Date: 2020/11/20 10:22:57 ******/
CREATE DATABASE [DBTase]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N’DBTase’, FILENAME = N’C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLServer\MSSQL\DATA\DBTase.mdf’ , SIZE = 5120KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
LOG ON
( NAME = N’DBTase_log’, FILENAME = N’C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLServer\MSSQL\DATA\DBTase_log.ldf’ , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO
ALTER DATABASE [DBTase] SET COMPATIBILITY_LEVEL = 110
GO
IF (1 = FULLTEXTSERVICEPROPERTY(‘IsFullTextInstalled’))
begin
EXEC [DBTase].[dbo].[sp_fulltext_database] @action = ‘enable’
end
GO
ALTER DATABASE [DBTase] SET ANSI_NULL_DEFAULT OFF
GO
ALTER DATABASE [DBTase] SET ANSI_NULLS OFF
GO
ALTER DATABASE [DBTase] SET ANSI_PADDING OFF
GO
ALTER DATABASE [DBTase] SET ANSI_WARNINGS OFF
GO
ALTER DATABASE [DBTase] SET ARITHABORT OFF
GO
ALTER DATABASE [DBTase] SET AUTO_CLOSE OFF
GO
ALTER DATABASE [DBTase] SET AUTO_CREATE_STATISTICS ON
GO
ALTER DATABASE [DBTase] SET AUTO_SHRINK OFF
GO
ALTER DATABASE [DBTase] SET AUTO_UPDATE_STATISTICS ON
GO
ALTER DATABASE [DBTase] SET CURSOR_CLOSE_ON_COMMIT OFF
GO
ALTER DATABASE [DBTase] SET CURSOR_DEFAULT GLOBAL
GO
ALTER DATABASE [DBTase] SET CONCAT_NULL_YIELDS_NULL OFF
GO
ALTER DATABASE [DBTase] SET NUMERIC_ROUNDABORT OFF
GO
ALTER DATABASE [DBTase] SET QUOTED_IDENTIFIER OFF
GO
ALTER DATABASE [DBTase] SET RECURSIVE_TRIGGERS OFF
GO
ALTER DATABASE [DBTase] SET DISABLE_BROKER
GO
ALTER DATABASE [DBTase] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
GO
ALTER DATABASE [DBTase] SET DATE_CORRELATION_OPTIMIZATION OFF
GO
ALTER DATABASE [DBTase] SET TRUSTWORTHY OFF
GO
ALTER DATABASE [DBTase] SET ALLOW_SNAPSHOT_ISOLATION OFF
GO
ALTER DATABASE [DBTase] SET PARAMETERIZATION SIMPLE
GO
ALTER DATABASE [DBTase] SET READ_COMMITTED_SNAPSHOT OFF
GO
ALTER DATABASE [DBTase] SET HONOR_BROKER_PRIORITY OFF
GO
ALTER DATABASE [DBTase] SET RECOVERY FULL
GO
ALTER DATABASE [DBTase] SET MULTI_USER
GO
ALTER DATABASE [DBTase] SET PAGE_VERIFY CHECKSUM
GO
ALTER DATABASE [DBTase] SET DB_CHAINING OFF
GO
ALTER DATABASE [DBTase] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF )
GO
ALTER DATABASE [DBTase] SET TARGET_RECOVERY_TIME = 0 SECONDS
GO
EXEC sys.sp_db_vardecimal_storage_format N’DBTase’, N’ON’
GO
USE [DBTase]
GO
/****** Object: StoredProcedure [dbo].[cp_petowner] Script Date: 2020/11/20 10:22:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[cp_petowner]
AS
select * from product

GO
/****** Object: StoredProcedure [dbo].[cp_petowner_VarCharValue] Script Date: 2020/11/20 10:22:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
–2.创建一个带参数的存储过程
CREATE PROCEDURE [dbo].[cp_petowner_VarCharValue]
@ownername varchar(50)
AS
DECLARE @ownername2 varchar(50)
set @ownername2=’小米手机’
select *,@ownername as ownername from product where name=@ownername2

GO
/****** Object: StoredProcedure [dbo].[PROC_Product] Script Date: 2020/11/20 10:22:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
–【存储过程】查询产品表,根据类型ID 查询出所有产品,且返回产品数量
create proc [dbo].[PROC_Product]
@typeID int,
@count int out
as
begin

select @count=COUNT(ID) from Product where TypeID=@typeID;

select * from Product where TypeID=@typeID;
end

GO
/****** Object: StoredProcedure [dbo].[PROC_Product_insert] Script Date: 2020/11/20 10:22:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
–【存储过程】插入新产品的存储过程,产品名存在就不插入
create proc [dbo].[PROC_Product_insert]
@name nvarchar(50),
@typeID int ,
@count int out
as
begin
declare @c int;
select @c=COUNT(ID) from Product where Name=@name;
if(@c!=0)
set @count =0;
else
begin
insert into Product(name, typeID) values(@name,@typeID);
set @count=1;
end
end

GO
/****** Object: StoredProcedure [dbo].[PROC_TypeAndProduct] Script Date: 2020/11/20 10:22:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
–【存储过程】获取产品表和产品类型表中的所有数据
create procedure [dbo].[PROC_TypeAndProduct]
as
begin
select TypeID,TypeName from [Type];
select ID,TypeID,Name from Product;
end
GO
/****** Object: Table [dbo].[Product] Script Date: 2020/11/20 10:22:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Product](
[ID] [int] IDENTITY(1,1) NOT NULL,
[TypeID] [int] NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
/****** Object: Table [dbo].[Type] Script Date: 2020/11/20 10:22:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Type](
[TypeID] [int] IDENTITY(1,1) NOT NULL,
[TypeName] [nvarchar](50) NULL,
CONSTRAINT [PK_Type] PRIMARY KEY CLUSTERED
(
[TypeID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET IDENTITY_INSERT [dbo].[Product] ON

INSERT [dbo].[Product] ([ID], [TypeID], [Name]) VALUES (1, 1, N’苹果手机’)
INSERT [dbo].[Product] ([ID], [TypeID], [Name]) VALUES (3, 1, N’三星手机’)
INSERT [dbo].[Product] ([ID], [TypeID], [Name]) VALUES (4, 1, N’华为手机’)
INSERT [dbo].[Product] ([ID], [TypeID], [Name]) VALUES (5, 1, N’小米手机’)
INSERT [dbo].[Product] ([ID], [TypeID], [Name]) VALUES (7, 2, N’苹果电脑’)
INSERT [dbo].[Product] ([ID], [TypeID], [Name]) VALUES (8, 2, N’戴尔电脑’)
INSERT [dbo].[Product] ([ID], [TypeID], [Name]) VALUES (9, 3, N’小米手环’)
INSERT [dbo].[Product] ([ID], [TypeID], [Name]) VALUES (10, 3, N’华为手环’)
SET IDENTITY_INSERT [dbo].[Product] OFF
SET IDENTITY_INSERT [dbo].[Type] ON

INSERT [dbo].[Type] ([TypeID], [TypeName]) VALUES (1, N’手机’)
INSERT [dbo].[Type] ([TypeID], [TypeName]) VALUES (2, N’电脑’)
INSERT [dbo].[Type] ([TypeID], [TypeName]) VALUES (3, N’手表’)
SET IDENTITY_INSERT [dbo].[Type] OFF
ALTER TABLE [dbo].[Product] WITH CHECK ADD CONSTRAINT [FK_product_Type] FOREIGN KEY([TypeID])
REFERENCES [dbo].[Type] ([TypeID])
GO
ALTER TABLE [dbo].[Product] CHECK CONSTRAINT [FK_product_Type]
GO
USE [master]
GO
ALTER DATABASE [DBTase] SET READ_WRITE
GO
模型类
Type.cs
/// <summary>
/// 产品类型
/// </summary>
public class Type
{

/// <summary>
/// TypeID
/// </summary>
private int _typeid;
public int TypeID
{
get { return _typeid; }
set { _typeid = value; }
}
/// <summary>
/// TypeName
/// </summary>
private string _typename;
public string TypeName
{
get { return _typename; }
set { _typename = value; }
}

}
Product.cs
/// <summary>
/// 产品
/// </summary>
public class Product
{

/// <summary>
/// ID
/// </summary>
private int _id;
public int ID
{
get { return _id; }
set { _id = value; }
}
/// <summary>
/// TypeID
/// </summary>
private int _typeid;
public int TypeID
{
get { return _typeid; }
set { _typeid = value; }
}
/// <summary>
/// Name
/// </summary>
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}

public Type ProductType { get; set; }//做联表查询

}
数据库访问层
引入关键库
using System;
using System.Collections.Generic;
using System.Linq;
using Dapper;
using System.Data;
using System.Data.SqlClient;
创建数据库连接
private static string connStr = “server=.;uid=sa;pwd=sa;database=DBTase”;

添加 3个方法

1.新增一条数据,不带事务
/// <summary>
/// 新增一条数据,不带事务
/// </summary>
public static void Add()
{
string sql = @”insert into Type(TypeName) values(@TypeName)”;

Type productType = new Type()
{
TypeName = “测试类型2020-11-19”
};

using (SqlConnection conn = new SqlConnection(connStr))
{
int result = conn.Execute(sql, productType);
if (result > 0)
{
Console.WriteLine(“新增一条数据成功!”);
}
}
}
2.新增一条数据,且,返回自增ID;不带事务
/// <summary>
/// 新增一条数据,且,返回自增ID;不带事务
/// </summary>
public static void AddGetID()
{
/*
在高并发的环境中 可能得到的@@identity会不是你想要的结果
你在新增,别人也可能也在新增 。你获取的时候就是 获取到最后插入的那条记录的ID.
所以 我们要修改一下
SELECT @@identity
修改为
SELECT SCOPE_IDENTITY();
*/
string sql = @”insert into Type(TypeName) values(@TypeName);SELECT SCOPE_IDENTITY();”;

Type productType = new Type()
{
TypeName = “新增一条数据,且,返回自增ID;不带事务”
};

using (SqlConnection conn = new SqlConnection(connStr))
{
//获取新加的ID
int id = Convert.ToInt32(conn.ExecuteScalar(sql, productType));
if (id > 0)
{
Console.WriteLine(“新增一条数据,且,返回自增ID;不带事务”);
Console.WriteLine($”新增ID为:{id}”);
}
}
}
3.新增多条数据,不带事务
/// <summary>
/// 新增多条数据,不带事务
/// </summary>
public static void AddList()
{
string sql = @”insert into Type(TypeName) values(@TypeName)”;

List<Type> productTypeList = new List<Type>
{
new Type(){ TypeName = “1测试类型2020-11-19”},
new Type(){ TypeName = “2测试类型2020-11-19”},
new Type(){ TypeName = “3测试类型2020-11-19”},
new Type(){ TypeName = “4测试类型2020-11-19”},
new Type(){ TypeName = “5测试类型2020-11-19”},
new Type(){ TypeName = “6测试类型2020-11-19”}
};

using (SqlConnection conn = new SqlConnection(connStr))
{
int result = conn.Execute(sql, productTypeList);
if (result > 0)
{
Console.WriteLine(“新增多条数据,不带事务–添加成功!”);
}
}
}

 

修改 2个方法

1.修改一条数据,不带事务
/// <summary>
/// 修改一条数据,不带事务
/// </summary>
public static void Update()
{
string sql = @”update Type set TypeName=@TypeName where TypeID=@TypeID”;
Type productType = new Type()
{
TypeID = 5,
TypeName = “Update测试类型2020-11-19”
};
using (SqlConnection conn = new SqlConnection(connStr))
{
int result = conn.Execute(sql, productType);
if (result > 0)
{
Console.WriteLine(“修改一条数据成功!”);
}
}
}
2.修改多条数据,不带事务
/// <summary>
/// 修改多条数据,不带事务
/// </summary>
public static void UpdateList()
{
string sql = @”update Type set TypeName=@TypeName where TypeID=@TypeID”;
List<Type> productTypeList = new List<Type>
{
new Type(){ TypeID=6, TypeName = “1Update测试类型2020-11-19”},
new Type(){ TypeID=7, TypeName = “2Update测试类型2020-11-19”},
new Type(){ TypeID=8, TypeName = “3Update测试类型2020-11-19”},
new Type(){ TypeID=9, TypeName = “4Update测试类型2020-11-19”},
new Type(){ TypeID=10,TypeName = “5Update测试类型2020-11-19”},
new Type(){ TypeID=11,TypeName = “6Update测试类型2020-11-19”}
};
using (SqlConnection conn = new SqlConnection(connStr))
{
int result = conn.Execute(sql, productTypeList);
if (result > 0)
{
Console.WriteLine(“修改多条数据成功!”);
}
}
}

 

删除 2个方法
1.删除一条数据,不带事务
/// <summary>
/// 删除一条数据,不带事务
/// </summary>
public static void Delete()
{
string sql = @”delete Type where TypeID=@TypeID”;
Type productType = new Type();
productType.TypeID = 11;

using (SqlConnection conn = new SqlConnection(connStr))
{
//添加、删除、修改 Execute 存储过程
//update
int result = conn.Execute(sql, productType);
if (result > 0)
{
Console.WriteLine(“删除一条数据成功!”);
}
}
}

2.删除多条数据,不带事务
/// <summary>
/// 删除多条数据,不带事务
/// </summary>
public static void DeleteList()
{
string sql = @”delete Type where TypeID=@TypeID”;

List<Type> productTypeList = new List<Type>
{
new Type(){ TypeID=9},
new Type(){ TypeID=10}
};

using (SqlConnection conn = new SqlConnection(connStr))
{
//添加、删除、修改 Execute 存储过程
//update
int result = conn.Execute(sql, productTypeList);
if (result > 0)
{
Console.WriteLine(“删除多条数据成功!”);
}
}
}

 

查询/Join查询/函数查询/查询In操作/多语句查询/验证类型名是否存在 7个方法
1.查询一条数据
/// <summary>
/// 查询一条数据
/// </summary>
public static void GetModel()
{
Console.WriteLine(“******查询一条数据******”);

string sql = @”select * from Type where TypeID=@TypeID”;
Type productType = new Type();
productType.TypeID = 6;

using (SqlConnection conn = new SqlConnection(connStr))
{
Type _type = conn.Query<Type>(sql, productType).Single();
Console.WriteLine($”[ID:{_type.TypeID}]Name:{_type.TypeName}”);
}
}
2.普通查询-列表
/// <summary>
/// 普通查询-列表
/// ProductSelect产品查询
/// </summary>
public static void ProductSelect()
{
Console.WriteLine(“******普通查询-列表******”);
string sql = “select * from product”;

using (SqlConnection conn = new SqlConnection(connStr))
{
List<Product> productsList = conn.Query<Product>(sql).ToList();

Console.WriteLine(“//ProductSelect查询//”);
foreach (var item in productsList)
{
Console.WriteLine($”ID:{item.ID}\nName:{item.Name}”);
}
}

}
3.Join查询-列表
/// <summary>
/// Join查询-列表
/// ProductJoinTypeSelect产品带产品类型名称查询
/// </summary>
public static void ProductJoinTypeSelect()
{
Console.WriteLine(“******Join查询-列表******”);

string sql = @”select * from product p
left join type t
on (p.TypeID=t.TypeID)”;

using (SqlConnection conn = new SqlConnection(connStr))
{
var productsList = conn.Query<Product, Type, Product>
(sql, (product, type) =>
{
product.ProductType = type;//关联表
return product;
}, splitOn: “typeid”//建立关系的字段
).ToList();

Console.WriteLine(“//ProductJoinTypeSelect查询//”);
foreach (var item in productsList)
{
Console.WriteLine($”ID:{item.ID}\nName:{item.Name}\nTypeName:{item.ProductType.TypeName}”);
}
}

}

4.函数查询 和,平均值,最大值,最小值
/// <summary>
/// 函数查询 和,平均值,最大值,最小值
/// </summary>
public static void GetFunction()
{
Console.WriteLine(“******函数查询,和,平均值,最大值,最小值******”);

string sql = @”select * from Type”;//ORM框架执行效率排序EF 7 8 Dapper执行效率 1 2
using (SqlConnection conn = new SqlConnection(connStr))
{
List<Type> productTypeList = conn.Query<Type>(sql).ToList();
int count = productTypeList.Count();//获取记录条数
Console.WriteLine($”记录条数:{count}”);
var sum = productTypeList.Sum(p => p.TypeID);
var avg = productTypeList.Average(p => p.TypeID);
var max = productTypeList.Max(p => p.TypeID);
var min = productTypeList.Min(p => p.TypeID);
Console.WriteLine($”和 Sum:{sum}”);
Console.WriteLine($”平均值 Average:{avg}”);
Console.WriteLine($”最大值 Max:{max}”);
Console.WriteLine($”最小值 Min:{min}”);

foreach (Type t in productTypeList)
{
Console.WriteLine($”{t.TypeID}-{t.TypeName}”);
}
}
}
5.查询In操作
/// <summary>
/// 查询In操作
/// </summary>
public static void GetIn()
{
Console.WriteLine(“******查询In操作******”);

string sql = “select * from product where ID in @ids”;

int[] ids = {1,2,3,4,5};

using (SqlConnection conn = new SqlConnection(connStr))
{
List<Product> productsList = conn.Query<Product>(sql,new { ids= ids }).ToList();

foreach (var item in productsList)
{
Console.WriteLine($”ID:{item.ID}\nName:{item.Name}”);
}
}
}
6.多语句查询
/// <summary>
/// 多语句查询
/// </summary>
public static void GetMultiple()
{
Console.WriteLine(“******多语句查询******”);

string sql = “SELECT TypeID, TypeName FROM [Type];SELECT ID, TypeID, Name FROM Product”;

using (SqlConnection conn = new SqlConnection(connStr))
{
Dapper.SqlMapper.GridReader multiReader = conn.QueryMultiple(sql);

//注意:由于首先查出是Type,其次是Product,那么你在执行下面代码的时候顺序必须和sql语句顺序一致
IEnumerable<Type> typeList = multiReader.Read<Type>();
IEnumerable<Product> productList = multiReader.Read<Product>();

Console.WriteLine(“//多语句查询-类型信息”);
foreach (var item in typeList)
{
Console.WriteLine($”ID:{item.TypeID}\nName:{item.TypeName}”);
}

Console.WriteLine(“//多语句查询-产品信息”);
foreach (var item in productList)
{
Console.WriteLine($”ID:{item.ID}\nName:{item.Name}”);
}
}
}
7.验证类型名是否存在
/// <summary>
/// 验证类型名是否存在
/// </summary>
/// <param name=”name”>类型名</param>
/// <returns></returns>
public static bool Check(string name)
{
string sql = “select count(TypeName) from Type where TypeName=@TypeName”;
using (SqlConnection conn = new SqlConnection(connStr))
{
var number = conn.QueryFirst<int>(sql, new { TypeName = name });

if (number > 0)
{
return true;
}
return false;
}
}
事务 3个方法
1.事务第一种用法;添加一条分类的同时新增一条产品数据
/// <summary>
/// 事务第一种用法;添加一条分类的同时新增一条产品数据
/// </summary>
public static void ExecuteTranscationOne()
{

/*
在高并发的环境中 可能得到的@@identity会不是你想要的结果
你在新增,别人也可能也在新增 。你获取的时候就是 获取到最后插入的那条记录的ID.
所以 我们要修改一下
SELECT @@identity
修改为
SELECT SCOPE_IDENTITY();
*/
string TypeSql = @”insert into Type(TypeName) values(@TypeName);
SELECT SCOPE_IDENTITY();”;

string ProduxtSql = @”insert into Product(TypeID,Name)
values(@TypeID,@Name)”;

Type type = new Type()
{
TypeName = “新的分类1”
};
Product product = new Product()
{
TypeID = 0,
Name = “新的产品1”
};
using (SqlConnection conn = new SqlConnection(connStr))//打开数据库方式交给框架执行
{
SqlTransaction trans = null;
try
{
conn.Open();//手动打开数据库链接//在dapper中使用事务,需要手动打开连接
//程序事务,建立在某个连接对象上
//开始事务
trans = conn.BeginTransaction();//连接对象开启事务
int typeID = Convert.ToInt32(conn.ExecuteScalar(TypeSql, type, trans));
product.TypeID = typeID;
int result2 = conn.Execute(ProduxtSql, product, trans);
//提交事务(持久化数据)
trans.Commit();
if (result2 > 0)
Console.WriteLine(“事务1:添加一条数据成功”);
else
Console.WriteLine(“事务1:添加一条数据【失败】”);
}
catch (Exception ex)
{
//事务回滚
trans.Rollback();
Console.WriteLine(ex.Message);
}
}
}
2.事务第二种用法;添加一条分类的同时新增一条产品数据
/// <summary>
/// 事务第二种用法;添加一条分类的同时新增一条产品数据
/// </summary>
public static void ExecuteTranscationTwo()
{

/*
在高并发的环境中 可能得到的@@identity会不是你想要的结果
你在新增,别人也可能也在新增 。你获取的时候就是 获取到最后插入的那条记录的ID.
所以 我们要修改一下
SELECT @@identity
修改为
SELECT SCOPE_IDENTITY();
*/
string TypeSql = @”insert into Type(TypeName) values(@TypeName);
SELECT SCOPE_IDENTITY();”;

string ProduxtSql = @”insert into Product(TypeID,Name)
values(@TypeID,@Name)”;

Type type = new Type()
{
TypeName = “新的分类:事务2”
};
Product product = new Product()
{
TypeID = 0,
Name = “新的产品:事务2”
};
using (SqlConnection conn = new SqlConnection(connStr))//打开数据库方式交给框架执行
{
conn.Open();//在dapper中使用事务,需要手动打开连接

// 连接对象开启事务
//开始事务
using (SqlTransaction trans = conn.BeginTransaction())
{
try
{
//获取新加类型ID
int typeID = Convert.ToInt32(conn.ExecuteScalar(TypeSql, type, trans));
//把新的类型ID 给产品
product.TypeID = typeID;
int result2 = conn.Execute(ProduxtSql, product, trans);
//提交事务(持久化数据)
trans.Commit();

if (result2 > 0)
Console.WriteLine(“事务2:添加一条数据成功”);
else
Console.WriteLine(“事务2:添加一条数据【失败】”);
}
catch (Exception ex)
{
//事务回滚
trans.Rollback();
Console.WriteLine(ex.Message);
}
}
}
}
3.新增(添加)多条数据,带事务
/// <summary>
/// 新增多条数据,带事务
/// </summary>
public static void ExecAddList()
{
string sql = @”insert into Type(TypeID,TypeName) values(@TypeID,@TypeName)”;

List<Type> productTypeList = new List<Type>
{
new Type(){ TypeID=1, TypeName = “Exec1测试类型2020-11-20”},
new Type(){ TypeName = “Exec2测试类型2020-11-20”},
new Type(){ TypeName = “Exec3测试类型2020-11-20”},
new Type(){ TypeName = “Exec4测试类型2020-11-20”},
new Type(){ TypeName = “Exec5测试类型2020-11-20”},
new Type(){ TypeName = “Exec6测试类型2020-11-20″}
};

using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();//在dapper中使用事务,需要手动打开连接
// 连接对象开启事务
//开始事务
using (SqlTransaction trans = conn.BeginTransaction())
{
try
{
int result = conn.Execute(sql, productTypeList, trans);
//提交事务(持久化数据)
trans.Commit();
if (result > 0)
{
Console.WriteLine($”新增多条数据,带事务–添加{result}条成功!”);
}
}
catch (Exception ex)
{
//事务回滚
trans.Rollback();
Console.WriteLine(ex.Message);
}
}
}
}

 

执行存储过程 3个方法
1.查询产品表,根据类型ID 查询出所有产品,且返回产品数量
/// <summary>
/// 查询产品表,根据类型ID 查询出所有产品,且返回产品数量
/// </summary>
public static void PROC_Product_CountAndList()
{

using (SqlConnection conn = new SqlConnection(connStr))//打开数据库方式交给框架执行
{
//参数
DynamicParameters paras = new DynamicParameters();
paras.Add(“@typeID”, 1);//输入参数
paras.Add(“@count”, 0, DbType.Int32, ParameterDirection.Output);//输出参数

List<Product> productList = conn.Query<Product>(“PROC_Product”, paras, commandType: CommandType.StoredProcedure).ToList();//sql 存储过程

int count = paras.Get<int>(“@count”);//获取输出参数

Console.WriteLine($”共计:{count}”);
foreach (Product product in productList)
{
Console.WriteLine($”ID:{product.ID} – Name:{product.Name} – TypeID:{product.TypeID}”);
}
}
}
sql
–【存储过程】查询产品表,根据类型ID 查询出所有产品,且返回产品数量
create proc PROC_Product
@typeID int,
@count int out
as
begin

select @count=COUNT(ID) from Product where TypeID=@typeID;

select * from Product where TypeID=@typeID;
end
GO

–查询
DECLARE @count int
EXEC PROC_Product 1,@count OUTPUT
select @count
2.插入新产品的存储过程,产品名存在就不插入
/// <summary>
/// 插入新产品的存储过程,产品名存在就不插入
/// 参考:https://www.cnblogs.com/wyy1234/p/9078859.html
/// </summary>
public static void PROC_Product_insert()
{
using (SqlConnection conn = new SqlConnection(connStr))//打开数据库方式交给框架执行
{
//参数
DynamicParameters paras = new DynamicParameters();
paras.Add(“@name”, “苹果3”);//输入参数
paras.Add(“@typeID”, 1);//输入参数
paras.Add(“@count”, 0, DbType.Int32, ParameterDirection.Output);//输出参数

var result = conn.Query<Product>(“PROC_Product_insert”, paras, commandType: CommandType.StoredProcedure).ToList();//sql 存储过程

int count = paras.Get<int>(“@count”);//获取输出参数

Console.WriteLine($”插入:{count}条”);

}
}
sql
GO
–【存储过程】插入新产品的存储过程,产品名存在就不插入
create proc PROC_Product_insert
@name nvarchar(50),
@typeID int ,
@count int out
as
begin
declare @c int;
select @c=COUNT(ID) from Product where Name=@name;
if(@c!=0)
set @count =0;
else
begin
insert into Product(name, typeID) values(@name,@typeID);
set @count=1;
end
end
GO

–查询
DECLARE @count int
EXEC PROC_Product_insert “苹果手机2”,1,@count OUTPUT
select @count
3.获取产品表和产品类型表中的所有数据
/// <summary>
/// 获取产品表和产品类型表中的所有数据
/// 参考:https://www.cnblogs.com/wyy1234/p/9078859.html
/// </summary>
public static void PROC_TypeAndProduct()
{
using (SqlConnection conn = new SqlConnection(connStr))//打开数据库方式交给框架执行
{

//获取多个结果集
Dapper.SqlMapper.GridReader res = conn.QueryMultiple(“PROC_TypeAndProduct”, commandType: CommandType.StoredProcedure);

//注意:如果存储过程首先查出是Type,其次是Product,那么你在执行下面代码的时候顺序必须和存储过程查询顺序一致
//read方法获取Type和Product
IEnumerable<Type> typeList = res.Read<Type>();
IEnumerable<Product> productList = res.Read<Product>();
}
}
sql
GO
–【存储过程】获取产品表和产品类型表中的所有数据
create procedure PROC_TypeAndProduct
as
begin
select TypeID,TypeName from [Type];
select ID,TypeID,Name from Product;
end

–查询
EXEC PROC_TypeAndProduct

————————————————
版权声明:本文为CSDN博主「橙-极纪元」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cplvfx/article/details/109849118

Dapper 分页,万能公共分页,使用sql高效率分页_橙-极纪元的博客-CSDN博客

mikel阅读(578)

来源: Dapper 分页,万能公共分页,使用sql高效率分页_橙-极纪元的博客-CSDN博客

用户表实体类 UserInfoModel
public class UserInfoModel
{
public int Id{set;get;}
public int ClassId{set;get;}
public string Name{set;get;}
public int sex{set;get;}

}
分页模型

/*分页计算
当前显示数据=每页行数x(当前页数-1)
skip()跳过多少条,take()查询多少条
list.Skip(page.pageSize * (page.pageIndex – 1)).Take(page.pageSize).AsQueryable().ToList();
*/

/// <summary>
/// 分页信息
/// </summary>
public class PageInfo
{

/// <summary>
/// 每页行数(每页数据量):默认每页10条
/// </summary>
public int pageSize { get; set; } = 10;

/// <summary>
/// 当前页:默认第1页
/// </summary>
public int pageIndex { get; set; } = 1;

/// <summary>
/// 总记录数:默认0条
/// </summary>
public int count { get; set; } = 0;

/// <summary>
/// 总页数
/// </summary>
public int pageCount
{
get
{
if (count > 0)
{
return count % this.pageSize == 0 ? count / this.pageSize : count / this.pageSize + 1;
}
else
{
return 0;
}
}
}
}
数据库基类
using Dapper;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace User.Dal
{
public class BaseDapper
{
/// <summary>
/// 数据库连接字符串
/// </summary>
protected static string connStr = System.Configuration.ConfigurationManager.ConnectionStrings[“DbContext”].ConnectionString;

/// <summary>
/// 公共分页
/// </summary>
/// <typeparam name=”T”>返回类型</typeparam>
/// <param name=”model”>SQL条件</param>
/// <param name=”total”>结果集总数</param>
/// <param name=”param”>参数</param>
/// <returns></returns>
public static IEnumerable<T> GetPageList<T>(SQLSelectPageModel model,out int total,object param = null)
{
#region 分页算法
int skip = 1;//从第几条开始
int take = model.pageIndex * model.pageSize;//到第几条结束
if (model.pageIndex > 0)
{
skip = ((model.pageIndex – 1) * model.pageSize)+1;
}
#endregion

StringBuilder sqlStr = new StringBuilder();
sqlStr.Append($”SELECT COUNT(1) FROM {model.tableName} where {model.where};”);
sqlStr.Append($@”SELECT {model.files}FROM
(SELECT ROW_NUMBER() OVER(ORDER BY {model.orderby}) AS RowNum,{model.files} FROM {model.tableName} WHERE {model.where}) AS result
WHERE RowNum >= {skip} AND RowNum <= {take} ORDER BY {model.orderby}”);

using (SqlConnection conn = new SqlConnection(connStr))
{
//获取多个结果集
Dapper.SqlMapper.GridReader res = conn.QueryMultiple(sqlStr.ToString(), param: param, commandType: CommandType.Text);

//注意:如果存储过程首先查出是Type,其次是Product,那么你在执行下面代码的时候顺序必须和存储过程查询顺序一致
//read方法获取Type和Product
total = res.ReadFirst<int>();
IEnumerable<T> list = res.Read<T>();
return list;

//total = reader.ReadFirst<int>();
//return reader.Read<T>();
}
}
}
/// <summary>
/// sql 分页模型
/// </summary>
public class SQLSelectPageModel
{
/// <summary>
/// 查询的“列”
/// </summary>
public string files { set; get; }
/// <summary>
/// 表名 (可以跟join)
/// </summary>
public string tableName { set; get; }
/// <summary>
/// 条件
/// </summary>
public string where { set; get; }
/// <summary>
/// 排序 条件
/// </summary>
public string orderby { set; get; }
/// <summary>
/// 当前页
/// </summary>
public int pageIndex { set; get; }
/// <summary>
/// 当前页显示条数
/// </summary>
public int pageSize { set; get; }
}
}
子类调用
方式一 【推荐】
public class UserInfoDAL
{
/// <summary>
/// 根据分类ID获取信息列表
/// </summary>
/// <param name=”ClassId”>分类ID</param>
/// <param name=”sex”>性别 0 全部,1男,2女</param>
/// <param name=”pageInfo”>分页信息</param>
/// <returns></returns>
public static List<UserInfoModel> GetBaseInfoList(UserInfoModel UModel, PageInfo pageInfo)
{
List<UserInfoModel> result = new List<UserInfoModel>();
try
{
//1.SQL参数
SQLSelectPageModel sQLSelectPage = new SQLSelectPageModel() {
files=”*”,
tableName= “UserInfo”,
where= “ClassId=@ClassId”,
orderby= “Id desc”,
pageIndex= pageInfo.pageIndex,
pageSize= pageInfo.pageSize
};

//2.拼装条件 和参数
StringBuilder whereSB = new StringBuilder();

if (UModel.sex == 1 || UModel.sex == 2)
{
whereSB.Append(” and sex = @sex “);
}

sQLSelectPage.where = sQLSelectPage.where + whereSB.ToString();

//3.调用 基类 公共分页
var res = GetPageList<UserInfoModel>(sQLSelectPage, out int totalCount, UModel).ToList();

pageInfo.count = totalCount;

return res;
}
catch (Exception e)
{
return result;
}

}
}
方式二
public class UserInfoDAL
{
/// <summary>
/// 根据分类ID获取信息列表
/// </summary>
/// <param name=”ClassId”>分类ID</param>
/// <param name=”sex”>性别 0 全部,1男,2女</param>
/// <param name=”pageInfo”>分页信息</param>
/// <returns></returns>
public static List<UserInfoModel> GetBaseInfoList(int ClassId, int sex, PageInfo pageInfo)
{
List<UserInfoModel> result = new List<UserInfoModel>();
try
{
//1.SQL参数
SQLSelectPageModel sQLSelectPage = new SQLSelectPageModel() {
files=”*”,
tableName= “UserInfo”,
where= “ClassId=@ClassId”,
orderby= “Id desc”,
pageIndex= pageInfo.pageIndex,
pageSize= pageInfo.pageSize
};

//2.拼装条件 和参数
var param = new object();
if (sex == 1 || sex == 2)
{
sQLSelectPage.where = sQLSelectPage.where + ” and sex = @sex”;
param = new { ClassId = ClassId, sex = sex };
}
else
{
param = new {ClassId = ClassId};
}

//3.调用 基类 公共分页
var res = GetPageList<UserInfoModel>(sQLSelectPage, out int totalCount, param).ToList();

pageInfo.count = totalCount;

return res;
}
catch (Exception e)
{

}

return result;
}

}

————————————————
版权声明:本文为CSDN博主「橙-极纪元」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cplvfx/article/details/120204281

基于Dapper的分页实现,支持筛选,排序,结果集总数,多表查询,非存储过程 - JIN Weijie - 博客园

mikel阅读(572)

来源: 基于Dapper的分页实现,支持筛选,排序,结果集总数,多表查询,非存储过程 – JIN Weijie – 博客园

简介

之前事先搜索了下博客园上关于Dapper分页的实现,有是有,但要么是基于存储过程,要么支持分页,而不支持排序,或者搜索条件不是那么容易维护。

代码

首先先上代码: https://github.com/jinweijie/Dapper.PagingSample

方法定义

以下是我的一个分页的实现,虽然不是泛型(因为考虑到where条件以及SQL语句的搭配),但是应该可以算是比较通用的了,方法定义如下:

public Tuple<IEnumerable<Log>, int> Find(LogSearchCriteria criteria
            , int pageIndex
            , int pageSize
            , string[] asc
            , string[] desc);

以上函数定义是一个查询Log的示例,返回结果中,Tuple的第一个值是结果集,第二个值是总行数(例如,总共有100条记录,每页10条,当前第一页,那么第一个值是10条记录,第二个值是100)

在示例项目中,我用两种方法实现了分页:

1. 第一种是基于2此查询,第一次得到总数,第二次查询得到结果集。

2. 第二种是基于1此查询,用了SQLServer 的Offest/Fetch,所以只支持SQL Server 2012+,所以大家根据自己用的SQL Server版本选择不同的实现,这里当然是第二种实现效率更高一点。

运行示例

1. 将Github的Repo下载或者Clone到本地以后,到Database目录下,解压缩Database.7z

2. Attach到Sql Server上。默认我使用Sql Server LocalDB,连接字符串是 Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DapperPagingSample;integrated security=True;   如果你用的不是LocalDB,请酌情修改App.Config的连接字符串。

3. Ctrl+F5运行程序,示例项目里,我用了一个简单的WinForm程序,但应该可以比较好的演示分页效果。

多表支持

增加了示例,支持多表查询,例如有两个Log表,Level表,Log的LevelId字段引用Level的Id字段,通过以下的查询,可以实现多表查询的分页,排序,过滤:

首先是通过两次查询的示例(基本支持所有版本Sql Server):

复制代码
 1 public Tuple<IEnumerable<Log>, int> Find(LogSearchCriteria criteria
 2             , int pageIndex
 3             , int pageSize
 4             , string[] asc
 5             , string[] desc)
 6         {
 7             using (IDbConnection connection = base.OpenConnection())
 8             {
 9                 const string countQuery = @"SELECT COUNT(1)
10                                             FROM      [Log] l
11                                             INNER JOIN [Level] lv ON l.LevelId = lv.Id
12                                             /**where**/";
13 
14                 const string selectQuery = @"  SELECT  *
15                             FROM    ( SELECT    ROW_NUMBER() OVER ( /**orderby**/ ) AS RowNum, l.*, lv.Name as [Level]
16                                       FROM      [Log] l
17                                       INNER JOIN [Level] lv ON l.LevelId = lv.Id
18                                       /**where**/
19                                     ) AS RowConstrainedResult
20                             WHERE   RowNum >= (@PageIndex * @PageSize + 1 )
21                                 AND RowNum <= (@PageIndex + 1) * @PageSize
22                             ORDER BY RowNum";
23 
24                 SqlBuilder builder = new SqlBuilder();
25 
26                 var count = builder.AddTemplate(countQuery);
27                 var selector = builder.AddTemplate(selectQuery, new { PageIndex = pageIndex, PageSize = pageSize });
28 
29                 if (!string.IsNullOrEmpty(criteria.Level))
30                     builder.Where("lv.Name= @Level", new { Level = criteria.Level });
31 
32                 if (!string.IsNullOrEmpty(criteria.Message))
33                 {
34                     var msg = "%" + criteria.Message + "%";
35                     builder.Where("l.Message Like @Message", new { Message = msg });
36                 }
37 
38                 foreach (var a in asc)
39                 {
40                     if(!string.IsNullOrWhiteSpace(a))
41                         builder.OrderBy(a);
42                 }
43 
44                 foreach (var d in desc)
45                 {
46                     if (!string.IsNullOrWhiteSpace(d))
47                         builder.OrderBy(d + " desc");
48                 }
49 
50                 var totalCount = connection.Query<int>(count.RawSql, count.Parameters).Single();
51                 var rows = connection.Query<Log>(selector.RawSql, selector.Parameters);
52 
53                 return new Tuple<IEnumerable<Log>, int>(rows, totalCount);
54             }
55         }
复制代码

第二个示例是通过Offset/Fetch查询(支持Sql Server 2012+)

复制代码
 1 public Tuple<IEnumerable<Log>, int> FindWithOffsetFetch(LogSearchCriteria criteria
 2                                                 , int pageIndex
 3                                                 , int pageSize
 4                                                 , string[] asc
 5                                                 , string[] desc)
 6         {
 7             using (IDbConnection connection = base.OpenConnection())
 8             {
 9                
10                 const string selectQuery = @" ;WITH _data AS (
11                                             SELECT l.*, lv.Name AS [Level]
12                                             FROM      [Log] l
13                                             INNER JOIN [Level] lv ON l.LevelId = lv.Id
14                                             /**where**/
15                                         ),
16                                             _count AS (
17                                                 SELECT COUNT(1) AS TotalCount FROM _data
18                                         )
19                                         SELECT * FROM _data CROSS APPLY _count /**orderby**/ OFFSET @PageIndex * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY";
20 
21                 SqlBuilder builder = new SqlBuilder();
22                 
23                 var selector = builder.AddTemplate(selectQuery, new { PageIndex = pageIndex, PageSize = pageSize });
24 
25                 if (!string.IsNullOrEmpty(criteria.Level))
26                     builder.Where("lv.Name = @Level", new { Level = criteria.Level });
27 
28                 if (!string.IsNullOrEmpty(criteria.Message))
29                 {
30                     var msg = "%" + criteria.Message + "%";
31                     builder.Where("l.Message Like @Message", new { Message = msg });
32                 }
33                 
34                 foreach (var a in asc)
35                 {
36                     if (!string.IsNullOrWhiteSpace(a))
37                         builder.OrderBy(a);
38                 }
39 
40                 foreach (var d in desc)
41                 {
42                     if (!string.IsNullOrWhiteSpace(d))
43                         builder.OrderBy(d + " desc");
44                 }
45                 
46                 var rows = connection.Query<Log>(selector.RawSql, selector.Parameters).ToList();
47 
48                 if(rows.Count == 0)
49                     return new Tuple<IEnumerable<Log>, int>(rows, 0);
50                 
51 
52                 return new Tuple<IEnumerable<Log>, int>(rows, rows[0].TotalCount);
53                 
54             }
55         }
复制代码

 

谢谢

希望对大家有帮助:)

最后,我更新了本篇随便,增加了内容,希望不要再被撤下了(上次撤下说是因为篇幅太短。。。),因为个人觉得这个对大家应该还是会有用的。

最大的 AI 工具目录库 - 知乎

mikel阅读(631)

来源: 最大的 AI 工具目录库 – 知乎

1.Futurepedia

几乎包括现有所有 AI 工具 ,都在这个目录中!每天还在不断更新……目前总共收录了482个工具(截止时间:20221222),总共分为:文本工具(172个),图片工具(135个),营销类工具(94个),助手工具(69个),代码工具(60个),设计工具(54个),写作工具(49个),音频工具(40个),视频工具(40个),搜索工具(34个),头像工具(30个),演讲工具(22个)…..;可以根据相关的标签类别进行搜索。

主页

2.Best AI Tools

1400 多种 Ai 工具按 semrush 排名排序。

3.Creaitives

定期抓取所有其他 AI 数据库并汇总数据,目前,已收录了800+AI工具,配合浏览器翻译插件使用会更好。

主页

4.Futuretools

FutureTool是一个AI工具导航网站,已收集了 173个各类 AI 工具,可根据聊天、金融、娱乐、视频、代码等多个筛选找到自己心仪的 AI 工具。

主页

5.Airadar.getinference

找到你需要的一切,主要用于创意和营销的AI工具 500+产品

主页

6.ai-lib.club

主页

7.wikiaitools

汇集1000+的Ai工具

php商城伪静态,友价商城nginx伪静态源码规则_weixin_39882394的博客-CSDN博客

mikel阅读(368)

来源: (2条消息) php商城伪静态,友价商城nginx伪静态源码规则_weixin_39882394的博客-CSDN博客

不少朋友搭建完友价 T5 源码商城后发现很多页面无法正常打开,其实是伪静态没做好。

在这里我推荐使用宝塔搭建,将下面伪静态规则复制到:网站-管理-伪静态-保存即可

一般网站有自带的apache规则,但是经过转换后无法使用,用下面的规则后便可正常了!如需源码可以联系客服!手上有更好用的源代码!

nginx 伪静态规则:rewrite ^/(.*)/search(.*).html /$1/index.php?str=$2 last;

rewrite ^/(.*)/view([0-9]*).html /$1/view.php?id=$2 last;

rewrite ^/(.*)/(.*)list(.*).html /$1/$2list.php?str=$3 last;

rewrite ^/(.*)/(.*)view(.*).html /$1/$2view.php?id=$3 last;

location ~* ^/((.*)upload|ad|gg|img|ckeditor\/attached|(.*)upload1|(.*)upload2|(.*)upload3)/.*\.(php|php5|asp)$

{

deny all;

}

rewrite ^(?i)/config/ueditor/php/upload/(.*).PHP /css/ last;

rewrite ^(?i)/config/ueditor/php/upload/(.*).asp /css/ last;

rewrite ^(?i)/config/ueditor/php/upload1/(.*).php /css/ last;

rewrite ^(?i)/config/ueditor/php/upload1/(.*).asp /css/ last;

rewrite ^(?i)/config/ueditor/php/upload2/(.*).php /css/ last;

rewrite ^(?i)/config/ueditor/php/upload2/(.*).asp /css/ last;

rewrite ^(?i)/config/ueditor/php/upload3/(.*).php /css/ last;

rewrite ^(?i)/config/ueditor/php/upload3/(.*).asp /css/ last;

rewrite ^(?i)/config/ueditor_mini/php/upload/(.*).php /css/ last;

rewrite ^(?i)/config/ueditor_mini/php/upload/(.*).asp /css/ last;

rewrite ^(?i)/config/ueditor_mini/php/upload1/(.*).php /css/ last;

rewrite ^(?i)/config/ueditor_mini/php/upload1/(.*).asp /css/ last;

rewrite ^(?i)/config/ueditor_mini/php/upload2/(.*).php /css/ last;

rewrite ^(?i)/config/ueditor_mini/php/upload2/(.*).asp /css/ last;

rewrite ^(?i)/config/ueditor_mini/php/upload3/(.*).php /css/ last;

rewrite ^(?i)/config/ueditor_mini/php/upload3/(.*).asp /css/ last;

rewrite ^(?i)/ad/(.*).php /css/ last;

rewrite ^(?i)/ad/(.*).asp /css/ last;

rewrite ^(?i)/gg/(.*).php /css/ last;

rewrite ^(?i)/gg/(.*).asp /css/ last;

rewrite ^(?i)/img/(.*).php /css/ last;

rewrite ^(?i)/img/(.*).asp /css/ last;

rewrite ^(?i)/upload/(.*).php /css/ last;

rewrite ^(?i)/upload/(.*).asp /css/ last;

rewrite ^(?i)/ckeditor/attached/(.*).php /css/ last;

rewrite ^(?i)/ckeditor/attached/(.*).asp /css/ last;

LoadRunner快速入门!看一遍就会了。(内含实例) - 知乎

mikel阅读(733)

来源: LoadRunner快速入门!看一遍就会了。(内含实例) – 知乎

LoadRunner是软件测试利器。

本教程可以帮你完成一次软件测试实验。

题目:

使用LoadRunner自带的测试项目–航班订票管理系统WebTours,网站地址为:127.0.0.1:1080/WebTours(用户名为jojo,密码为bean),完成性能测试,要求:

1、 虚拟用户为10;

2、 每隔15s启动2个Vusers;

3、 运行时间为5min;

4、 每隔15s停止2个Vusers;

5、在分析工具中自定义一个图形。


下载

链接你不需要找了,我已经给你找好了。

《LoadRunner 12 链接》,可复制链接后用石墨文档 App 或小程序打开


安装

下载解压后应该是这个样子的

选好安装位置点击Install

正在安装

安装好之后还提示安装运行依赖库 直接点击确定

运行依赖库
提示

同样,直接点确定

这里需要选择第一个 从网上下载

运行依赖库下载中
运行依赖库安装中

运行依赖库安装好后就会出现安装向导

选择好路径 点击安装

我的安装目录

安装目录

等待安装

正在安装

取消勾选指定LoadRunner代理将要使用的证书 后点下一步

身份验证
安装完成

在桌面已经生成了快捷方式

快捷方式

安装完成!!!


必知必会

LoadRunner的组件

  • Virtual User Generator—虚拟用户生成器
  • LoadRunner Controller—创建、运行和监控场景
  • LoadRunner Analysis—分析测试结果

官方使用指南

我们可以结合官方的使用指南进行学习

使用指南

LoadRunner相关的术语:

LoadRunner Terminology
  • Scenario(场景) 定义测试阶段出现的事件,基于性能需求
  • Virtual Users or Vusers Vusers 模拟用户在你的系统上操作的动作。一个场景可以包含几十,上百,甚至上千的虚拟用户。
  • Vuser Script(脚本) 记录你的应用程序中执行业务流程的动作
  • Protocal 协议是客户端和服务器之间交流的方式
  • Transaction(事务) 你所定义的事务,用于测试你的系统性能。一个事务代表一个或多个终端用户的业务流程。一个事务允许你测试这些业务流程花费了多长时间。

LoadRunner负载测试的流程是什么?

  1. Plan the load test 计划负载测试
  2. Create Vuser scripts 创建脚本
  3. Define the scenario 定义场景
  4. Analyze the results 分析结果

第一个例子

启动HP WebTours服务器

  1. 快捷键Win+S 搜索 Start HP
搜索 Satrt HP
打开文件位置

你也可以根据路径找到该文件

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\HP Software\HP LoadRunner\Samples\Web 

2.双击启动服务器

不出意外你会看到这样的命令行窗口

造成该问题的原因是服务器未配置ip地址,解决办法就是给它配置咯

所以接下来我要做一件事情

在LoadRunner的安装位置找到WebTours文件夹 并双击进入

找到WebTours
进入文件夹
打开配置文件
搜搜ServerName

再次重复 双击Start HP Web Tours Server

这样就表示已经成功启动啦

注意:需要保持这个命令行窗口状态,如果关闭就代表关闭服务器。

然后我们再双击 HP Web Tours Application

Web Tours

能够成功访问网页了。

登录测试

Username:jojo

Password:bean


录制脚本

1.启动Virtual User Generator

Virtual User Generator

2.新建脚本和解决方案

新建

3.选择Web-HTTP/HTML

选择协议并创建

4.创建好之后是这个样子的

创建完成

此时,解决方案是一个空的脚本。

*如果你的左侧没有Solution Explorer(解决方案资源管理器)。

不要慌,很简单,一下就能解决。

解决方案资源管理器

开始录制

1.点击 Record>Record

2.填入URL地址

http://localhost:1080/WebTours

其他选项保持默认,但需要与我保持一致。

填写URL
高级设置

开始录制

此时已经开始录制了。

你需要进行以下动作:

用户名登录

进入航班详情

选择航班

进入订单详情

查看日程

停止录制

查看脚本

步骤导航

Step Navigator

重播脚本

Runtime Settings

设置迭代次数

设置步调

选择无代理

运行

运行截图

Nice!

脚本重播完成

怎么办?不用怕。

解决常见的重播问题

打开Web Tours主页

回到主页

回到主页

使用Controller打开解决方案

启动Controller

新建场景

开启场景

出现错误

警告信息

不用害怕

接下来我们要做一些事情

查询本机IP

再次开启场景

开始运行

运行截图

分析总结


Nice!Nice!Nice!

你已经入门啦!

AI建模工具速查表 (2) | TensorFlow 使用指南

mikel阅读(461)

来源: AI建模工具速查表 (2) | TensorFlow 使用指南

TensorFlow速查手册

TensorFlow是一个由谷歌开发和维护的开源机器学习平台,用于科学计算、神经网络、图像分类、聚类、回归、强化学习、自然语言处理等。

TensorFlow2建模速查

TensorFlow2应用速查

下载

🔔 GitHub提供了对应代码支持一键查看与运行!能科学上网的小伙伴可以点击 Notebook 头部按钮一键直达 Google Colab 运行,也可以使用下方在线编程环境运行代码与学习。

公众号 GitHub 博客
ShowMeAI研究中心 ShowMeAI-Hub blog.showmeai.tech
下载高清PDF 一键运行代码 在线编程环境

ShowMeAI 速查表一览

速查表是『ShowMeAI硬核资料库』的重要组成部分!Awesome AI Cheatsheets 系列包含『编程语言』『AI技能知识』『数据科学工具库』『AI垂直领域工具库』四个板块。ShowMeAI会陆续把最新、最优质的工具/知识整理成速查表!系列持续更新中,想第1时间获取更新通知,请关注我们吧!

▣ 数据科学工具库速查表

Numpy SciPy Pandas Matplotlib Seaborn Bokeh Spark RDD SparkSQL More…
More…
速查表详情 速查表详情 速查表详情 速查表详情 速查表详情 速查表详情 速查表详情 速查表详情 More…

▣ AI垂直领域工具库速查表

SKLearn Keras TensorFlow2 PyTorch OpenCV 4.x More…
More…
速查表详情 速查表详情 速查表详情 速查表详情 速查表详情 More…

▣ AI知识技能速查表

Jupyter 正则表达式 CS229 CS230 线代与微积分 概率与统计 More…
More…
速查表详情 速查表详情 速查表详情 速查表详情 速查表详情 速查表详情 More…

▣ 编程语言速查表

SQL Python 3 More…
More…
速查表详情 速查表详情 More…

ShowMeAI 系列教程推荐

从0搭建基于神经网络的手语识别系统

mikel阅读(801)

来源: 从0搭建基于神经网络的手语识别系统

据北京听力协会预估数据,我国听障人群数量已过千万。而在全球范围内有4.66亿人患有残疾性听力损失,约占全世界人口的5%。聋哑人士很特殊,他们需要使用手语进行交流,其他与常人无异,我国存在特殊教育水平在各城市中发展力度具有较大差异,国家通用手语推广程度浅,但不懂手语,与听力障碍者交流会非常困难。

在本篇内容中,ShowMeAI 借助深度学习与神经网络技术,针对这个问题从 0 构建 1 个应用程序,检测手语并将其翻译给其他人进而打破手语隔阂。

搭建和部署完成后,你可以通过摄像头,轻松测试模型,如下图所示,快来一起试试吧。这个动图中的手势代表的单词,见文末哦!

💡 手语介绍

我们先来简单了解一下手语,它由 3 个主要部分组成:

  • 手指拼写:这是一种手动的交流方式,用双手和手指拼写单词。每个字母都用指定的手位置表示。
  • 单词级符号词汇:这是一个大型视频数据集,用于识别单词或字母的整个手势。
  • 非手部特征:包括任何面部表情、嘴巴、舌头或身体姿势。

在本文中,我们先解决第①个部分的问题。我们准备使用的解决方案是基于视觉数据的神经网络

💡 深度学习与计算机视觉

人工智能和计算机视觉的最典型的模型是卷积神经网络(CNN),它在典型的计算机视觉应用中(如图像识别、目标检测等)应用广泛。我们在本次应用的核心技术也将采用 CNN。

CNN 网络有着如上图所示的网络结构,典型的结构包括卷积层、池化层、激活层、全连接层等,对于输入图像,可以有效抽取图像内容表征,并进行分类或其他处理。卷积层等特殊结构,可以在控制参数量的前提下,保证良好的图像特征提取能力。

关于卷积神经网络的详细知识可以参考ShowMeAI下述教程:
深度学习教程 | 吴恩达专项课程 · 全套笔记解读 中的文章 卷积神经网络解读
深度学习与计算机视觉教程 中的文章 卷积神经网络详解

💡 小试牛刀,打通流程

我们来构建一个 CNN 识别的流程,会分成以下基础步骤:

  • 数据读取与切分
  • 数据可视化及预处理
  • CNN网络构建与训练

① 导入相关库

我们在这里主要使用 TensorFlow 构建网络与训练,会使用 Numpy 做数据计算与处理,以及使用 Matplotlib 进行简单可视化。

对于这些工具库,ShowMeAI都制作了快捷即查即用的速查表手册,大家可以在下述位置获得:
Tensorflow 速查手册
Numpy 速查手册
Matplotlib 速查手册

我们先把这些工具库导入。

  1. # 导入工具库
  2. import string
  3. import pandas as pd
  4. import numpy as np
  5. import tensorflow as tf
  6. import matplotlib.pyplot as plt
  7. from tensorflow import keras
  8. from functools import partial
  9. from tensorflow.keras.preprocessing.image import ImageDataGenerator, array_to_img

② 读取数据集

本数据集为手语字母对应的数据集,图片 size 不大,所以也叫做 sign_mnist 数据集(类比手写数字数据集 mnist),部分示例图片如下

数据集大家可以在 Kaggle平台对应数据集页面 下载,也可以通过ShowMeAI的百度网盘地址下载。

🏆 实战数据集下载(百度网盘):公众号『ShowMeAI研究中心』回复『实战』,或者点击 这里 获取本文 [5] 从0搭建基于神经网络的手语识别系统 『sign_mnist 数据集

⭐ ShowMeAI官方GitHubhttps://github.com/ShowMeAI-Hub

下面我们加载训练集与测试集并切分特征与标签:

  1. # 读取数据
  2. test = pd.read_csv("sign_mnist_test.csv")
  3. train = pd.read_csv("sign_mnist_train.csv")
  4. # 输出基本信息
  5. print("训练集维度", train.shape)
  6. print("测试集维度", train.shape)
  1. # 输出标签信息
  2. labels = train["label"].value_counts().sort_index(ascending=True)
  3. labels
  1. # 切分特征与标签
  2. train_x = train.drop(labels = "label", axis = 1)
  3. train_y = train["label"]
  4. test_x = test.drop(labels = "label", axis = 1)
  5. test_y = test["label"]
  6. train_x.head()
  1. # 数据预处理与可视化
  2. # 存储标签数据
  3. test_classes= test_y
  4. train_clasees = train_y
  5. # 特征转为numpy格式
  6. train_x = train_x.to_numpy()
  7. test_x = test_x.to_numpy()
  8. # 把数据转为3维图像数据(图片数量*宽*高,这里如果是灰度图,颜色通道为1,省略)
  9. train_x = train_x.reshape(-1,28,28)
  10. test_x = test_x.reshape(-1,28,28)
  1. # 在训练集中取样30张图片,做可视化查看
  2. def plot_categories(training_images, training_labels):
  3. fig, axes = plt.subplots(3, 10, figsize=(16, 15))
  4. axes = axes.flatten()
  5. letters = list(string.ascii_lowercase)
  6. for k in range(30):
  7. img = training_images[k]
  8. img = np.expand_dims(img, axis=-1)
  9. img = array_to_img(img)
  10. ax = axes[k]
  11. ax.imshow(img, cmap="Greys_r")
  12. ax.set_title(f"{letters[int(training_labels[k])]}")
  13. ax.set_axis_off()
  14. plt.tight_layout()
  15. plt.show()
  16. plot_categories(train_x, train_y)

③ 卷积神经网络CNN搭建

我们使用 TensorFlow 的 high level API(即keras)搭建一个简易CNN神经网络,并拟合一下数据

  1. def create_model():
  2. model = tf.keras.models.Sequential([
  3. # 卷积层
  4. tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28, 28, 1)),
  5. # 池化层
  6. tf.keras.layers.MaxPooling2D(2,2),
  7. # 卷积层
  8. tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
  9. # 池化层
  10. tf.keras.layers.MaxPooling2D(2,2),
  11. # 展平
  12. tf.keras.layers.Flatten(),
  13. # 全连接层
  14. tf.keras.layers.Dense(512, activation='relu'),
  15. # softmax分类
  16. tf.keras.layers.Dense(26, activation='softmax')])
  17. model.compile(
  18. optimizer='adam', #优化器
  19. loss='sparse_categorical_crossentropy', #损失函数
  20. metrics=['accuracy']) #评估准则
  21. return model
  1. # 初始化模型
  2. model = create_model()
  3. # 拟合数据
  4. history = model.fit(train_x, train_y, epochs=20, validation_data=(test_x, test_y))

我们这里在全量数据集上迭代20个轮次,结果如下:

我们可以看到,这里的数据并不特别复杂,在自己从头搭建的 CNN 模型上,经过训练可以达到训练集 100% 验证集 92% 的准确率。

我们再对训练过程中的「准确率」及「损失函数」变化值进行绘制,以了解模型状态。

  1. # 获取准确率与损失函数情况
  2. acc = history.history['accuracy']
  3. val_acc = history.history['val_accuracy']
  4. loss = history.history['loss']
  5. val_loss = history.history['val_loss']
  6. # matplotlib绘制训练过程中指标的变化状况
  7. epochs = range(len(acc))
  8. plt.plot(epochs, acc, 'r', label='Training accuracy')
  9. plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
  10. plt.title('Training and validation accuracy')
  11. plt.legend()
  12. plt.figure()
  13. plt.plot(epochs, loss, 'r', label='Training Loss')
  14. plt.plot(epochs, val_loss, 'b', label='Validation Loss')
  15. plt.title('Training and validation loss')
  16. plt.legend()
  17. plt.show()

💡 问题与优化

① 深度网络与梯度消失

一般来说,随着 CNN 网络层数变深,模型的学习能力会变强,也能学到更多的信息。但训练深度CNN存在梯度消失的问题。

梯度消失和梯度爆炸部分内容也可以参考ShowMeAI的对吴恩达老师课程的总结文章 深度学习教程 | 深度学习的实用层面

非常深的神经网络的梯度会很快变为零(反向传播的梯度连乘带来的问题),这最终会使整个梯度下降变慢。有一些特殊结构的神经网络,可以大程度缓解这个问题,比如最著名的 ResNet,当然,大家可以借助 ResNet 预训练模型快速迁移学习应用在我们当前的手语识别问题上,为了让大家对ResNet 细节更清晰,我们在这里手动搭建 ResNet-50(即50层的ResNet网络)来训练和做效果对比。

ResNet的详细讲解也可以参考ShowMeAI的 深度学习教程 | 吴恩达专项课程 · 全套笔记解读中的文章 深度学习教程 | 经典CNN网络实例详解

② ResNet 模型简介

ResNet 是 Residual Networks 的简称,是迄今为止我们看到的最流行和最成功的深度学习模型之一。ResNets 由残差块组成,残差块的核心组件是『跳跃连接/skip-connection』。跳跃连接,也称为快捷连接,让神经网络跳过某些层并将一层的输出馈送到神经网络中另一层的输入。它能帮助模型避免乘以中间跳过的那些层的权重,从而有助于解决梯度消失的问题。

然而,使用 ResNet 和跳跃连接,由于中间有卷积层和池化层,一层输出的维度可能与另一层的输出维度不同。为了解决这个问题,可以使用两种方法:

  • 快捷连接填充多个零实体以增加其维度
  • 添加 1X1 卷积层来匹配维度。

但是,对于第二种方法,我们需要在输出中添加一个额外的参数,而第一种方法不需要。

③ ResNet为何有效

ResNet的效果核心有2点:

  • ① 它使用我们上面提到的跳跃连接,它跳过层来解决梯度消失的问题。
  • ② 它通过让模型学习恒等函数来确保最高层的性能至少与最低层一样好。

④ 构建ResNet-50

下面我们参考 keras 官方 ResNet 构建方式,构建一个 ResNet-50,如下所示,我们先构建基本模块,再组装成最终的网络。

  1. # Defining the identity block of the Resnet-50 Model.
  2. def identity_block(X, f, filters, training=True):
  3. # filter of the three convs
  4. f1,f2,f3 = filters
  5. X_shortcut = X
  6. # First Component
  7. X = tf.keras.layers.Conv2D(filters = f1, kernel_size = 1, strides = (1,1), padding = 'valid')(X)
  8. X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
  9. X = tf.keras.layers.Activation('relu')(X)
  10. # Second Component
  11. X = tf.keras.layers.Conv2D(filters = f2, kernel_size = f, strides = (1,1), padding = 'same')(X)
  12. X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
  13. X = tf.keras.layers.Activation('relu')(X)
  14. # Third Component
  15. X = tf.keras.layers.Conv2D(filters = f3, kernel_size = 1, strides = (1,1), padding = 'valid')(X)
  16. X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
  17. # Adding the two tensors
  18. X = tf.keras.layers.Add()([X_shortcut,X])
  19. X = tf.keras.layers.Activation('relu')(X)
  20. # Returning the last output
  21. return X
  1. # Defining the Convolution Block of the Resnet-50 Model.
  2. def convolutional_block(X, f, filters, s=2,training=True):
  3. # filter of the three convs
  4. f1,f2,f3 = filters
  5. X_shortcut = X
  6. # First Component
  7. X = tf.keras.layers.Conv2D(filters = f1, kernel_size = 1, strides = (1,1), padding = 'valid')(X)
  8. X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
  9. X = tf.keras.layers.Activation('relu')(X)
  10. # Second Component
  11. X = tf.keras.layers.Conv2D(filters = f2, kernel_size = f, strides = (s,s), padding = 'same')(X)
  12. X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
  13. X = tf.keras.layers.Activation('relu')(X)
  14. # Third Component
  15. X = tf.keras.layers.Conv2D(filters = f3, kernel_size = 1, strides = (1,1), padding = 'valid')(X)
  16. X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
  17. # Converting the Input Volume to the match the last output for addition.
  18. X_shortcut =tf.keras.layers.Conv2D(filters = f3, kernel_size = 1, strides = (s,s), padding = 'valid')(X_shortcut)
  19. X_shortcut = tf.keras.layers.BatchNormalization(axis = 3)(X_shortcut, training = training)
  20. X = tf.keras.layers.Add()([X_shortcut,X])
  21. X = tf.keras.layers.Activation('relu')(X)
  22. # Adding the last two tensors
  23. X = tf.keras.layers.Add()([X, X_shortcut])
  24. X = tf.keras.layers.Activation('relu')(X)
  25. # Returning the output tensor
  26. return X
  1. # Defining a modified Resnet-50 Model using the Identity and Convolution Blocks.
  2. def ResNet50(input_shape = (28, 28, 1), classes = 26):
  3. # Defining the input as a tensor with shape input_shape
  4. X_input = tf.keras.Input(input_shape)
  5. # Zero-Padding
  6. X = tf.keras.layers.ZeroPadding2D((3, 3))(X_input)
  7. # Stage 1
  8. X = tf.keras.layers.Conv2D(64, (5, 5), strides = (1, 1))(X)
  9. X = tf.keras.layers.BatchNormalization(axis = 3)(X)
  10. X = tf.keras.layers.Activation('relu')(X)
  11. X = tf.keras.layers.MaxPooling2D((3, 3), strides=(2, 2))(X)
  12. # Stage 2
  13. X = convolutional_block(X, f = 3, filters = [64, 64, 256], s = 1)
  14. X = identity_block(X, 3, [64, 64, 256])
  15. X = identity_block(X, 3, [64, 64, 256])
  16. # Add an Average Pool Layer
  17. X = tf.keras.layers.AveragePooling2D((2,2))(X)
  18. # Output Layer
  19. X = tf.keras.layers.Flatten()(X)
  20. X = tf.keras.layers.Dense(classes, activation='softmax')(X)
  21. # Create Model
  22. model = tf.keras.Model(inputs = X_input, outputs = X)
  23. return model

⑤ 训练ResNet-50

下面我们在数据集上,使用 ResNet-50 网络进行训练

  1. # 初始化模型
  2. model = ResNet50()
  3. # 编译
  4. model.compile(optimizer="adam",metrics=["accuracy"],loss = "sparse_categorical_crossentropy")
  5. # 训练
  6. history = model.fit(train_x, train_y, validation_data = (test_x, test_y), epochs =10)

得到如下结果

💡 优化效果对比

我们对ResNet-50也绘制训练过程中准确率和损失函数的变化,如下

  1. # 获取准确率与损失函数情况
  2. acc = history.history['accuracy']
  3. val_acc = history.history['val_accuracy']
  4. loss = history.history['loss']
  5. val_loss = history.history['val_loss']
  6. # matplotlib绘制训练过程中指标的变化状况
  7. epochs = range(len(acc))
  8. plt.plot(epochs, acc, 'r', label='Training accuracy')
  9. plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
  10. plt.title('Training and validation accuracy')
  11. plt.legend()
  12. plt.figure()
  13. plt.plot(epochs, loss, 'r', label='Training Loss')
  14. plt.plot(epochs, val_loss, 'b', label='Validation Loss')
  15. plt.title('Training and validation loss')
  16. plt.legend()
  17. plt.show()

对比图如下:

我们观察到,从简单的 CNN 模型换到 ResNet 模型时,测试集的准确率从92% 到 97% 。也说明了,ResNet 的结构确实能够带来效果上的提升。

💡 部署与实时测试

在这里我们做一个简单的测试,使用 OpenCV 的视频录制功能,通过 python 收集我们的摄像头的镜头采集的图像并进行实时预测。

ShowMeAI给OpenCV工具库制作了快捷即查即用的 OpenCV 速查表手册,大家可以点击查看和下载。

具体的过程是,我们解析捕获的每一帧图像,将其处理为灰度图(类似于我们模型的训练集),在图像中心抓取一个 400×400 像素的正方形区域(参见 x0,x1,y0,y1),将正方形调整为我们最初的 28×28 大小并使用我们的模型进行测试(之前保存到 .h5 文件)。

  1. # 导入工具库
  2. import keras
  3. import numpy as np
  4. from PIL import Image
  5. import string
  6. import pandas as pd
  7. import tensorflow as tf
  1. # 导入OpenCV
  2. import cv2
  3. from matplotlib import pyplot
  4. ## 设定维度
  5. dim = (28, 28) # 图像维度
  6. letters = list(string.ascii_lowercase) # 识别的字母
  7. x0 = 1920 // 2 - 400 # 400px left of center
  8. x1 = 1920 // 2 + 400 # 400px right of center
  9. y0 = 1080 // 2 - 400 # 400px right of center
  10. y1 = 1080 // 2 + 400 # 400px right of center
  11. # 初始化视频捕获
  12. video=cv2.VideoCapture(0)
  13. cv2.namedWindow('Webcam') # 构建1个窗口
  14. cv2.moveWindow('Webcam',40,30) # 放置窗口
  15. while video.isOpened(): # 只要没有关掉实时摄像头
  16. ret,capture = video.read() # 抓取每个视频帧
  17. cropped = capture[y0:y1, x0:x1] # 截取
  18. img = cv2.cvtColor(cropped, cv2.COLOR_BGR2GRAY) # 转成灰度图
  19. img = cv2.GaussianBlur(img, (5, 5), 0) # 图像平滑
  20. img = cv2.resize(img, dim) # 图像大小缩放
  21. pyplot.imshow(img, cmap='gray') # 可视化展示图片
  22. pyplot.show() # 展示
  23. img = np.reshape(img, (1,img.shape[0],img.shape[1],1))
  24. img = tf.cast(img, tf.float32)
  25. pred=model.predict(img)
  26. # 可视化实时效果
  27. cv2.rectangle(capture, (x0,y0),(x1,y1),(255,0,0),2) # 为图片添加矩形框
  28. cv2.putText(capture,'{} res50'.format(letters[np.argmax(pred[0])]),(x0+25,y0+50),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,255,0),1) # 预测字母
  29. cv2.imshow('Webcam', capture) # 展示视频
  30. # 结果输出
  31. print(pred)
  32. print(letters[np.argmax(pred[0])])
  33. # 退出视频输入
  34. key = cv2.waitKey(1)
  35. if key == ord('q'):
  36. break
  37. video.release()
  38. cv2.destroyAllWindows()

为了更轻松地对预估结果查看,我们把将预测的字母显示在实时画面上(请参阅下面的 gif 以测试单词 hello)。

💡 参考资料

ChatGPT指令大全(建议收藏) - 知乎

mikel阅读(1053)

来源: ChatGPT指令大全(建议收藏) – 知乎

在使用 ChatGPT 时,当你给的指令越,它的回答会越到位,举例来说,假如你要请它帮忙写文案,如果没给予指定情境与对象,它会不知道该如何回答的更加准确。

一、写报告

1、我现在正在 [报告的情境与目的]。我的简报主题是 [主题],请提供 [数字] 种开头方式,要简单到 [目标族群] 能听懂,同时要足够能吸引人,让他们愿意专心听下去

我现在正在修台大的简报课,其中一项作业是要做一份让小学生能听懂的简报。我的简报主题是机会成本,请提供三种开头方式,要简单到小学生能听懂,同时要足够能吸引人,让他们愿意专心听下去

2、写出一篇有关 [知识] 的 [数字] 字研究报告,报告中需引述刷新的研究,并引用专家观点

写出一篇有关自动驾驶的 300 字研究报告,报告中需引述刷新的研究,并引用专家观点

3、你是 [某个主题] 的专家,请针对以下论述 [附上论述],提出 [数字] 个反驳的论点,每个论点都要有佐证

你是大数据分析的专家,请针对以下论述「在数据分析中,越多数据越好」,提出 3 个反驳的论点,每个论点都要有佐证

4、你是 [某个主题] 的专家,请总结以下内容,并针对以下内容提出未来能进一步研究的方向 [附上内容]

你是金融科技专家,请总结以下内容,并针对以下内容提出未来能进一步研究的方向 [附上内容]

二、资料整理

1、给我 [数字] 篇,有关 [领域] 的文章。

给我 5 篇,有关 SEO 的文章。

2、用列点的方式总结出这篇文章的 [数字] 个重点:[附上文章内容/附上文章网址]。

用列点的方式总结出这篇文章的 5 个重点:[附上文章内容/附上文章网址]。

3、用列点的方式总结出 [数字] 个 [领域] 知识重点

用列点的方式总结出 10 个量子力学知识重点。

三、简历与自传

1、这份 [职位] 的简历,有哪边可以写更好? 请以专业面试官的角度,提出具体改进建议。接着以你提出的建议来改写这段经历,改写时请维持列点的形式。[附上简历]

这份 UIUX 设计师的简历,有哪边可以写更好? 请以专业面试官的角度,提出具体改进建议。接着以你提出的建议来改写这段经历,改写时请维持列点的形式。

2、改写以下简历,为每一点加上量化的数据,改写时请维持列点的形式。[附上简历]

3、把这段经历写得更精简一点,让别人可以马上看到重点,同时维持生动的描述。[附上经历]

4、为不同公司定制化撰写简历

我今天要申请[公司]的[职位],改写以下经历,让我能更符合[公司]的企业文化。[附上经历]

我今天要申请 Google 的前端工程师,改写以下经历,让我能更符合 Google 的企业文化。[附上经历]

四、准备面试

1、你现在是[公司]的[职位]面试官,请分享在[职位]面试时常会问的[数字]个问题。

你现在是 Google 的产品经理面试官,请分享在 Google 产品经理面试时常会问的 5 个问题。

2、我针对 [问题] 的回答,有哪些可以改进的地方? [附上回答]

我针对「你会如何排定不同产品功能优先顺序?」的回答,有哪些可以改进的地方? [附上回答]

3、针对 [问题] 这个面试问题,请提供一些常见的追问面试题。

针对「你会如何排定不同产品功能优先顺序?」这个面试问题,请提供一些常见的追问面试题。

4、我在准备 [问题] 这个面试问题,请用 STAR 原则帮我回答这个问题。针对这个问题,我有的经历如下 [附上经历]

我在准备「请分享一个你在急迫的期限中完成专案的经验」这个面试问题,请用 STAR 原则帮我回答这个问题。针对这个问题,我有的经历如下 [附上经历]。

五、程式

1、你现在是一个 [程式语言] 专家,请帮我用 [程式语言] 写一个函式,它需要做到 [某个功能]

你现在是一个 JavaScript 专家,请帮我用 JavaScript 写一个函式,它需要做到 输入一个一维阵列,把这个一维阵列转换成二维阵列。同时我要能够自由地决定二维阵列中的子阵列长度是多少

2、你现在是一个 [程式语言] 专家,请告诉我以下的程式码在做什么。[附上程式码]

3、你现在是一个 Clean Code 专家,我有以下的程式码,请用更干净简洁的方式改写,让我的同事们可以更容易维护程式码。另外,也解释为什么你要这样重构,让我能把重构的方式的说明加到 Pull Request 当中。[附上程式码]

4、你现在是一个 [程式语言] 专家,我有一段程式码,我预期这段程式码可以 [做到某个功能],只是它通过不了 [测试案例] 这个测试案例。请帮我找出我哪里写错了,以及用正确的方式改写。[附上程式码]

你现在是一个 python 专家,我有一段程式码,我预期这段程式码可以判断一个字串是不是镜像回文,只是它通过不了 aacdeedcc 这个测试案例。请帮我找出我哪里写错了,以及用正确的方式改写。[附上程式码]

5、你现在是一个 [程式语言] 专家,我有一段程式码 [附上程式码],请帮我写一个测试,请至少提供五个测试案例,同时要包含到极端的状况,让我能够确定这段程式码的输出是正确的。

6、你现在是一个 Regex 专家,请帮我写一个 Regex ,它能够把 [需求]

你现在是一个 Regex 专家,请帮我写一个 Regex ,它能够把输入一个字串,把这个字串中的所有数字都取出来

六、知识学习

1、详细的说明 [填入想了解的知识]

详细的说明如何制造一台电脑

2、你扮演 [科目老师] 的角色, 我需要理解 [理论]。请用 [方式] 方式描述。

你扮演数学老师的角色, 我需要理解一元二次方程式。请用浅显易懂方式描述。

3、你是一个 [SEO] 专家,你要教我深度的 [SEO] 知识

你是一个 SEO 专家,你要教我深度的 SEO 知识。

4、教我 [二次方程式],给我一个测验

教我 一元二次方程式,给我一个测验

七、英语学习

1、用 [中文/英文] 解释以下英文单字:[填入一个或多个单字]。请用表格的方式呈现,并且表格内须包含单字、词性、解释与例句。

用中文解释以下英文单字:apple, orange, doctor, car, run。请用表格的方式呈现,并且表格内须包含单字、词性、解释与例句。

2、解释英文单字 [英文单字],并且给我 [数字] 个常用句子

解释英文单字 divest,并且给我 5 个常用句子。

3、英语对话

Can we have a conversation about 话题?

Can we have a conversation about machine learning?

4、校阅英文文法

Can you check the spelling and grammar in the following text? [附上英文文字]

5、英文作文修改与解释

校阅以下英文文章,并用表格的方式呈现,要有三个栏位,分别是原文、修正后的版本,以及用中文详解为什么要这样修改:附上英文文章

6、纠正文法和拼字错误

Please correct my grammar and spelling mistakes in the text above: 附上英文文字

Please correct my grammar and spelling mistakes in the text above: I love eat fooded

八、工作生产力

1、回覆 Email

你是一名 [职业],我会给你一封电子邮件,你要回覆这封电子邮件。电子邮件:[附上内容]

你是一名产品经理,我会给你一封电子邮件,你要回覆这封电子邮件。电子邮件:[附上内容]

九、写作帮手

1、撰写标题

写出 数字 个有关 主题 的 社群平台 风格标题,要遵守以下规则:规则 1、规则 2、其他规则。

写出 5 个有关日本迪士尼旅游心得的 Instagram 风格标题,要遵守以下规则:标题不超过 20 字、标题要加上适当表情符号。

2、撰写文章大纲

提供 [某主题] 主题的文章大纲

提供美国留学主题的文章大纲

3、文章撰写

针对 [主题] 这个主题生成一篇文章

针对使用 ChatGPT 小诀窍这个主题生成一篇文章

4、产品文案

将以下产品关键字生成 数字 句的产品文案。产品关键字:附上关键字…

将以下产品关键字生成 10 句的产品文案。产品关键字:球鞋、春季款、多种颜色、适合慢跑

十、日常生活

1、食谱生成

提供给我一个食谱,食材包含 食材 1、食材 2、食材…。

提供给我一个食谱,食材包含鸡腿肉、鸡蛋、起司。

2、提供食谱

请列出这份食谱的采买清单和步骤:数字 人份的 食谱。

请列出这份食谱的采买清单和步骤:1 人份的蕃茄炒蛋。

3、活动计划清单

你扮演一位专业的活动企划,请生成 活动 活动计划清单,包括重要任务和截止日期。

你扮演一位专业的活动企划,请生成运动会活动计划清单,包括重要任务和截止日期。

4、提供点子

提供 [数字] 个 [想法] 的点子

提供 5 个情人节的点子

5、旅游计划

生成一份 数字 天的 地点 旅游计划,交通工具是 交通工具…。要遵守以下规则:填入规则

生成一份 5 天的东京旅游计画,交通工具是地铁和火车。要遵守以下规则:1. 地点要包含东京铁塔、富士山、迪士尼乐园 2. 需要包含交通如何乘坐 3. 一天不超过 3 个地点。

十一、有趣好玩

1、写歌词

大家都说我写的歌词像 人名,但我有点没灵感,请帮我用 人名 的风格写一首歌。歌中包含的元素要 关键字…。

大家都说我写的歌词像方文山,但我有点没灵感,请帮我用方文山的风格写一首歌。歌中包含的元素要有别离、思念、峰回路转。

2、写故事

写出一篇有关 故事想法,拥有 风格 风格的短篇故事

写出一篇有关工程师拯救这个世界的短篇故事

3、写 rap

你是现在红的饶舌歌手,请创作一首 Rap,主题是 附上主题。

你是现在红的饶舌歌手,请创作一首 Rap,主题是孤勇者。

十二、角色扮演

1、综合情境

你现在是一名 角色,你要针对我提出的问题提供建议。我的问题是:附上问题。

你现在是一名生涯教练,你要针对我提出的问题提供建议。我的问题是:我是否要出国念书?

2、面试官

你现在是一个 职位 面试官,而我是要应征 职位 的面试者。你需要遵守以下规则:1. 你只能问我有关 职位 的面试问题。2. 不需要写解释。3. 你需要向面试官一样等我回答问题,再提问下一个问题。你好。

你现在是一个产品经理面试官,而我是要应征产品经理的面试者。你需要遵守以下规则:

1. 你只能问我有关产品经理的面试问题。

2. 不需要写解释。

3. 你需要向面试官一样等我回答问题,再提问下一个问题。,你好。

3、担任导游

你是一位导游,我会把我旅游的位置给你,你要找一个靠近我位置的地方。在某些情况下,我还会告诉您我想旅游地点的类型。你还会向我找靠近我位置的类似类型的地方。我个需求是填入需求

你是一位导游,我会把我旅游的位置给你,你要找一个靠近我位置的地方。在某些情况下,我还会告诉您我想旅游地点的类型。你还会向我找靠近我位置的类似类型的地方。我的首要需求是我在阿里山,我想参观博物馆。

以上指令请大家灵活应用,后面有更好的,我会继续更新在后面