基于AScript的SQL脚本语言发布啦!

说到执行动态脚本,大多使用Lua、Python、JS等语言,为什么没有SQL动态脚本语言呢?现在它来了!

AScript是一个开源的C#动态脚本解析执行引擎,支持扩展多种脚本语言,2026年6月8日正式发布了SQL脚本语言AScript.Lang.Sql,快来试试吧!

一、介绍

支持SqlServer/MySql基础语法和数据类型:

  • 支持SELECT查询语法:FROM/WHERE/LEFT JOIN/RIGHT JOIN/INNER JOIN/GROUP BY/ORDER BY/LIMIT
  • 支持INSERT插入语法
  • 支持UPDATE修改语法
  • 支持DELETE删除语法
  • 支持创建存储过程:Sqlserver/MySql语法都支持
  • 支持创建表
  • 支持定义变量
  • 字段名、关键字不区分大小写
  • 支持调用外部方法和变量
  • 已内置常用数据类型:tinyint/smallint/int/bigint/decimal/float/real/double/bit/char/nchar/varchar/nvarchar/text/datetime

不支持:

  • 存储过程暂不支持OUT参数
  • SELECT查询不支持*号
  • SELECT查询不支持聚合函数

二、安装

复制代码
1 install-package AScript
2 install-package AScript.Lang.Sql

三、使用说明

1、注册语言

复制代码
1 Script.Langs.Set("sql", SqlLang.Instance);
2 // 可全局设置为默认语言
3 // Script.Langs.Set("sql", SqlLang.Instance, setDefault: true); 

2、单表查询

  • select多个字段,生成匿名类型
复制代码
 1 var s = @"select Name, Age from list where age=10";
 2 var list = new[] { new Person("tom", 15), new Person("jim", 10), new Person("san", 20), new Person("qin", 10) };
 3 var script = new Script();
 4 script.Context.Langs = new[] { "sql" };
 5 script.Context.SetVar("list", list);
 6 var result = script.Eval<IEnumerable<dynamic>>(s).ToList();
 7 Assert.AreEqual(2, result.Count);
 8 Assert.AreEqual("jim", result[0].Name);
 9 Assert.AreEqual(10, result[0].Age);
10 Assert.AreEqual("qin", result[1].Name);
11 Assert.AreEqual(10, result[1].Age);
  • select一个字段
复制代码
1 var s = @"select Name from list where age=10";
2 var list = new[] { new Person("tom", 15), new Person("jim", 10), new Person("san", 20), new Person("qin", 10) };
3 var script = new Script();
4 script.Context.Langs = new[] { "sql" };
5 script.Context.SetVar("list", list);
6 var result = script.Eval<IEnumerable<string>>(s).ToList();
7 Assert.AreEqual(2, result.Count);
8 Assert.AreEqual("jim", result[0]);
9 Assert.AreEqual("qin", result[1]);

3、单独from语句

复制代码
 1 var s = @"from list where age=10";
 2 var list = new[] { new Person("tom", 15), new Person("jim", 10), new Person("san", 20), new Person("qin", 10) };
 3 var script = new Script();
 4 script.Context.Langs = new[] { "sql" };
 5 script.Context.SetVar("list", list);
 6 var result = script.Eval<IEnumerable<Person>>(s).ToList();
 7 Assert.AreEqual(2, result.Count);
 8 Assert.AreEqual("jim", result[0].Name);
 9 Assert.AreEqual(10, result[0].Age);
10 Assert.AreEqual("qin", result[1].Name);
11 Assert.AreEqual(10, result[1].Age);

4、多表查询

复制代码
 1 string s = @"
 2 var q1 = new[] { new Person('tom', 20), new Person('jim', 25), new Person('san', 18), new Person('kit', 30) }.AsQueryable();
 3 var q2 = new[] { new AddressInfo('jim', 'a'), new AddressInfo('cc', 'b'), new AddressInfo('tom', 'c'), new AddressInfo('ee', 'd') }.AsQueryable();
 4 @lang sql
 5 select a.Name, a.Age, b?.Address, case a.Age when 25 then 1 else 2 end as Level
 6 from q1 as a
 7 left join q2 as b on a.Name = b.UserName
 8 where a.age > 22
 9 order by a.age desc
10 ";
11 var script = new Script();
12 script.Context.AddType<Person>();
13 script.Context.AddType<AddressInfo>();
14 var list = script.Eval<IEnumerable<dynamic>>(s).ToList();
15 Assert.AreEqual(2, list.Count);
16 Assert.AreEqual("kit", list[0].Name);
17 Assert.AreEqual(30, list[0].Age);
18 Assert.AreEqual(2, list[0].Level);
19 Assert.IsNull(list[0].Address);
20 Assert.AreEqual("jim", list[1].Name);
21 Assert.AreEqual(25, list[1].Age);
22 Assert.AreEqual("a", list[1].Address);
23 Assert.AreEqual(1, list[1].Level);

5、INSERT

执行insert语句返回插入记录的数量。

复制代码
 1 var s = @"insert into list (Name, Age) values ('tom', 20), ('jim', 25)";
 2 var list = new List<Person>();
 3 var script = new Script();
 4 script.Context.Langs = new[] { "sql" };
 5 script.Context.SetVar("list", list);
 6 Assert.AreEqual(2, script.Eval(s));
 7 Assert.AreEqual(2, list.Count);
 8 Assert.AreEqual("tom", list[0].Name);
 9 Assert.AreEqual(20, list[0].Age);
10 Assert.AreEqual("jim", list[1].Name);
11 Assert.AreEqual(25, list[1].Age);

6、UPDATE

执行update语句返回修改记录的数量。

复制代码
 1 var s = @"update list set Age=28 where Age<25";
 2 var list = new List<Person>
 3 {
 4     new Person("jim", 18),
 5     new Person("tom", 20),
 6     new Person("lily", 30)
 7 };
 8 var script = new Script();
 9 script.Context.Langs = new[] { "sql" };
10 script.Context.SetVar("list", list);
11 var count = (int)script.Eval(s);
12 Assert.AreEqual(2, count);
13 Assert.AreEqual(28, list[0].Age);
14 Assert.AreEqual(28, list[1].Age);
15 Assert.AreEqual(30, list[2].Age);

7、DELETE

执行delete语句返回删除记录的数量。

复制代码
 1 var s = @"delete from list where Age<25";
 2 var list = new List<Person>
 3 {
 4     new Person("jim", 18),
 5     new Person("tom", 20),
 6     new Person("lily", 30)
 7 };
 8 var script = new Script();
 9 script.Context.Langs = new[] { "sql" };
10 script.Context.SetVar("list", list);
11 var count = (int)script.Eval(s);
12 Assert.AreEqual(2, count);
13 Assert.AreEqual(1, list.Count);
14 Assert.AreEqual("lily", list[0].Name);

8、SqlServer存储过程

如果有多条语句,则使用begin..end,只有一条语句可以不写begin..end。

复制代码
 1 var s = @"
 2 CREATE PROCEDURE AddPerson
 3     @name VARCHAR,
 4     @age INT
 5 AS
 6 BEGIN
 7     INSERT INTO list (name, age) VALUES(@name, @age)
 8 END
 9 EXEC AddPerson 'tom', 20
10 ";
11 var list = new List<Person>();
12 var script = new Script();
13 script.Context.Langs = new[] { "sql" };
14 script.Context.SetVar("list", list);
15 Assert.AreEqual(1, script.Eval(s));
16 Assert.AreEqual(1, list.Count);
17 Assert.AreEqual("tom", list[0].Name);
18 Assert.AreEqual(20, list[0].Age);

9、MySql存储过程

如果有多条语句,则使用begin..end,只有一条语句可以不写begin..end。

复制代码
 1 var s = @"
 2 CREATE PROCEDURE AddPerson(@name VARCHAR, @age INT)
 3 BEGIN
 4     DECLARE @name2 VARCHAR
 5     DECLARE @age2 INT
 6     SET @name2 = @name + '2'
 7     SET @age2 = @age + 10
 8     INSERT INTO list (name, age) VALUES(@name, @age),(@name2, @age2)
 9 END
10 CALL AddPerson('tom', 20)";
11 var list = new List<Person>();
12 var script = new Script();
13 script.Context.Langs = new[] { "sql" };
14 script.Context.SetVar("list", list);
15 Assert.AreEqual(2, script.Eval(s));
16 Assert.AreEqual(2, list.Count);
17 Assert.AreEqual("tom", list[0].Name);
18 Assert.AreEqual(20, list[0].Age);
19 Assert.AreEqual("tom2", list[1].Name);
20 Assert.AreEqual(30, list[1].Age);

10、创建函数

复制代码
 1 var s = @"
 2 CREATE FUNCTION Calc(@a INT, @b INT)
 3 RETURNS INT
 4 BEGIN
 5     DECLARE @sum INT
 6     SET @sum = @a * @b + @a + @b
 7     RETURN @sum
 8 END
 9 SELECT Calc(2, 3)
10 ";
11 var script = new Script();
12 script.Context.Langs = new[] { "sql" };
13 var result = script.Eval(s);
14 Assert.AreEqual(11, result);

11、创建表

表字段支持主键(PRIMARY KEY)、自增(IDENTITY(1,1)或者AUTO_INCREMENT)、默认值(DEFAULT)、可空(NULL)、非空(NOT NULL)、最大长度定义。

  • 简单示例:
复制代码
 1 var s = @"
 2 CREATE TABLE person (name varchar, age int)
 3 INSERT INTO person (name, age) VALUES ('tom', 20),('jim', 25),('san', 18)
 4 UPDATE person SET age = 30 WHERE name = 'tom'
 5 DELETE FROM person WHERE Name = 'jim'
 6 SELECT Name,age FROM person WHERE age > 22
 7 ";
 8 var script = new Script();
 9 script.Context.Langs = new[] { "sql" };
10 var list = script.Eval<IEnumerable<dynamic>>(s).ToList();
11 Assert.AreEqual(1, list.Count);
12 Assert.AreEqual("tom", list[0].Name);
13 Assert.AreEqual(30, list[0].age);
  • 复杂示例:
复制代码
 1 var s = @"
 2 CREATE TABLE student_scores (
 3     id INT PRIMARY KEY AUTO_INCREMENT,
 4     name VARCHAR(50),
 5     subject VARCHAR(20),
 6     score INT NOT NULL DEFAULT 0,
 7     class_level VARCHAR(10)
 8 );
 9 
10 INSERT INTO student_scores (name,subject,score,class_level) VALUES
11 ('张三','数学',85,'A'),
12 ('李四','数学',92,'B'),
13 ('王五','数学',78,'A'),
14 ('赵六','数学',45,'C');
15 
16 SELECT 
17     id,
18     name,
19     score,
20     CASE 
21         WHEN score >= 90 THEN '优秀'
22         WHEN score >= 80 THEN '良好'
23         WHEN score >= 60 THEN '及格'
24         ELSE '不及格'
25     END AS basic_grade,
26     CASE 
27         WHEN score >= 90 THEN 
28             CASE WHEN class_level='A' THEN '顶尖' ELSE '优秀' END
29         WHEN score >= 80 THEN '潜力'
30         ELSE '需加强'
31     END AS advanced_grade
32 FROM student_scores;
33 ";
34 var script = new Script();
35 script.Context.Langs = new[] { "sql" };
36 var list = script.Eval<IEnumerable<dynamic>>(s).ToList();
37 Assert.AreEqual(4, list.Count);
38 Assert.AreEqual(1, list[0].id);
39 Assert.AreEqual("张三", list[0].name);
40 Assert.AreEqual(85, list[0].score);
41 Assert.AreEqual("良好", list[0].basic_grade);
42 Assert.AreEqual("潜力", list[0].advanced_grade);
43 Assert.AreEqual(2, list[1].id);
44 Assert.AreEqual("李四", list[1].name);
45 Assert.AreEqual(92, list[1].score);
46 Assert.AreEqual("优秀", list[1].basic_grade);
47 Assert.AreEqual("优秀", list[1].advanced_grade);
48 Assert.AreEqual(3, list[2].id);
49 Assert.AreEqual("王五", list[2].name);
50 Assert.AreEqual(78, list[2].score);
51 Assert.AreEqual("及格", list[2].basic_grade);
52 Assert.AreEqual("需加强", list[2].advanced_grade);
53 Assert.AreEqual(4, list[3].id);
54 Assert.AreEqual("赵六", list[3].name);
55 Assert.AreEqual(45, list[3].score);
56 Assert.AreEqual("不及格", list[3].basic_grade);
57 Assert.AreEqual("需加强", list[3].advanced_grade);

12、SQL to LINQ

项目中我们经常使用LINQ to SQL,如果反过来SQL to LINQ是啥样呢?

复制代码
 1 using (var context = new TestSqliteContext())
 2 {
 3     var s = @"
 4 select 
 5     p.Id, 
 6     p.Name, 
 7     p.Age, 
 8     a.Address as MyAddress, 
 9     case p.Age when 20 then 1 when 22 then 2 else 3 end as Level
10 from context.Persons as p
11 left join context.AddressInfos as a on p.Id = a.UserId
12 ";
13     var script = new Script();
14     script.Context.Langs = new[] { "sql" };
15     script.Context.SetVar("context", context);
16     var list = script.Eval<IEnumerable<dynamic>>(s).ToList();
17     Console.WriteLine(JsonConvert.SerializeObject(list, Formatting.Indented));
18 }

我们通过SQL脚本来操作DbContext,实现SQL to LINQ,再由LINQ转SQL(EF Core/FreeSql/SqlSugar等ORM)执行数据库查询操作。

生成的SQL语句如下:

复制代码
1 SELECT "p"."Id", "p"."Name", "p"."Age", "a"."Address" AS "MyAddress", CASE
2     WHEN "p"."Age" = 20 THEN 1
3     WHEN "p"."Age" = 22 THEN 2
4     ELSE 3
5 END AS "Level"
6 FROM "Persons" AS "p"
7 LEFT JOIN "AddressInfos" AS "a" ON "p"."Id" = "a"."UserId"
8 ORDER BY "p"."Age" DESC

当然,也可以使用SQL脚本对DbContext进行新增、修改、删除操作,同上面的INSERT/UPDATE/DELETE示例。

四、应用场景

1)前端动态查询条件和排序方式:

将前端传过来的条件和排序拼接为SQL字符串,然后生成Lambda表达式,如下:

复制代码
 1 // 前端传name、op(=,>,<等比较符)、age
 2 using (var context = new TestSqliteContext())
 3 {
 4     // 拼接条件字符串
 5     var conditionString = "p.name like %"+name+"% and p.age" + op + age;
 6     var script = new Script();
 7     script.Context.Langs = new[] { "sql" };
 8     // 编译条件
 9     Expression<Func<Person, bool>> condition = script.Lambda<Person, bool>(conditionString, "p");
10     // 条件过滤
11     var result = context.Persons.Where(condition).ToList();
12 }

2)数据层只提供了IQuerable<T>可是应用层我手痒想写SQL怎么办?

那就用SQL to LINQ吧!

结束语

SQL动态脚本也内置了一些函数,比如:

  1. 获取当前时间:NOW()或者GETDATE()
  2. 字符串拼接:CONCAT
  3. 计算长度:LEN(字符串或列表)或者LENGTH(字符串或列表)
  4. 数学函数:ABS/POW/ROUND/FLOOR/SIN/COS等

SQL脚本可以操作内存列表,操作IQuerable<T>数据源,也可以创建表(DataTable),大家觉得用SQL作为动态脚本如何呢?欢迎留言提供宝贵意见。

AScript开源地址:https://gitee.com/rockey627/AScript

相关推荐
z落落2 小时前
C# 四种特殊类:抽象类、密封类、静态类、部分类
开发语言·c#
王cb4 小时前
WinRT Server and Client c#
开发语言·c#
咸鱼翻身小阿橙4 小时前
在VScode使用C#并且调用opencv库
vscode·opencv·c#
持敬chijing6 小时前
Web渗透之SQL注入-堆叠注入(Stacked Queries Injection)
sql·安全·web安全·网络安全·网络攻击模型·安全威胁分析
Full Stack Developme6 小时前
SQL 执行顺序 及 全部关键字
数据库·sql
渣渣盟7 小时前
MySQL DQL全面解析:从入门到精通
数据库·sql·mysql·dql
xiaoshuaishuai87 小时前
C# 多线程之间对比
java·开发语言·c#
CodeStats11 小时前
从JDBC时代到MyBatis封神:SQL全流程手写ORM实战
sql·mysql·mybatis
程序猿乐锅12 小时前
【MySQL | 第七篇】 索引使用规则
数据库·sql·mysql