说到执行动态脚本,大多使用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动态脚本也内置了一些函数,比如:
- 获取当前时间:NOW()或者GETDATE()
- 字符串拼接:CONCAT
- 计算长度:LEN(字符串或列表)或者LENGTH(字符串或列表)
- 数学函数:ABS/POW/ROUND/FLOOR/SIN/COS等
SQL脚本可以操作内存列表,操作IQuerable<T>数据源,也可以创建表(DataTable),大家觉得用SQL作为动态脚本如何呢?欢迎留言提供宝贵意见。
AScript开源地址:https://gitee.com/rockey627/AScript
文章摘自:https://www.cnblogs.com/rockey627/p/20385305
