AScript是一个开源的C#动态脚本解析执行引擎,支持扩展自定义语法。
在之前的文章《AScript如何实现LINQ语法》中实现了C#标准LINQ查询语法,但并不支持left join/right join语句,不过.NET10在Queryable/Enumerable中新增了LeftJoin/RightJoin扩展方法(LINQ查询语法中仍然不支持left join/right join写法)。
本篇将在LINQ语法的基础上增加left join和right join语法,以提高脚本灵活性。
一、left join
标准LINQ查询的左连接写法如下:
1 from p in context.Persons
2 join a in context.AddressInfos on p.Id equals a.UserId into aa
3 from a in aa.DefaultIfEmpty()
4 select new { p.Id, p.Name, p.Age, MyAddress = a.Address };
简化后的left join语法如下:
1 from p in context.Persons
2 left join a in context.AddressInfos on p.Id equals a.UserId
3 select new { p.Id, p.Name, p.Age, MyAddress = a.Address };
如何实现呢?
1、在QueryNode中添加AddLeftJoin方法
1 public void AddLeftJoin(string varName, ITreeNode source, ITreeNode key1, ITreeNode key2)
2 {
3 string name1 = $"___{varName}___";
4 AddJoin(varName, source, key1, key2, name1);
5 AddFrom(varName, new CallFuncNode { Name = "DefaultIfEmpty", Args = new ITreeNode[] { new VariableNode(name1) } });
6 }
可以看到,AddLeftJoin方法中我们手动增加了join ... into xxx和from ... xxx.DefaultIfEmpty()语句。
2、在FromTokenHandler中添加left join解析
1 public void Build(DefaultSyntaxAnalyzer analyzer, TokenAnalyzingArgs e)
2 {
3 e.IsHandled = true;
4 var queryNode = e.Ignore ? null : new QueryNode();
5 var createFullOptions = (e.Options.CreateFullTreeNode ?? false) ? e.Options : new BuildOptions(e.Options) { CreateFullTreeNode = true };
6 // 解析from语句
7 BuildFrom(analyzer, e, createFullOptions, queryNode);
8 // 解析后续linq语句
9 while (true)
10 {
11 var token = e.TokenReader.Read();
12 ...
13 else if (token.Value.IsSymbol("join"))
14 {
15 BuildJoin(analyzer, e, createFullOptions, queryNode);
16 }
17 else if (token.Value.IsSymbol("left"))
18 {
19 BuildLeftJoin(analyzer, e, createFullOptions, queryNode);
20 }
21 else if (token.Value.IsSymbol("right"))
22 {
23 BuildRightJoin(analyzer, e, createFullOptions, queryNode);
24 }
25 ...
26 }
27 // 将LINQ语句添加到语法树中
28 if (!e.Ignore)
29 {
30 e.TreeBuilder.AddData(e.BuildContext, e.ScriptContext, e.Options, e.Control, queryNode);
31 }
32 }
33
34 private void BuildLeftJoin(DefaultSyntaxAnalyzer analyzer, TokenAnalyzingArgs e, BuildOptions createFullOptions, QueryNode queryNode)
35 {
36 analyzer.ValidateNextToken(e.TokenReader, "join");
37
38 var varToken = analyzer.ValidateNextToken(e.TokenReader, ETokenType.Word);
39 analyzer.ValidateNextToken(e.TokenReader, "in");
40
41 var source = analyzer.BuildOneStatement(e.BuildContext, e.ScriptContext, e.Options, e.TokenReader, e.Control, e.Ignore, _OnTokens);
42
43 analyzer.ValidateNextToken(e.TokenReader, "on");
44
45 var key1 = analyzer.BuildOneStatement(e.BuildContext, e.ScriptContext, createFullOptions, e.TokenReader, e.Control, e.Ignore, _EqualsTokens);
46 analyzer.ValidateNextToken(e.TokenReader, "equals");
47 var key2 = analyzer.BuildOneStatement(e.BuildContext, e.ScriptContext, createFullOptions, e.TokenReader, e.Control, e.Ignore, _Join2EndTokens);
48
49 queryNode?.AddLeftJoin(varToken.Value.Value, source, key1, key2);
50 }
这样我们就实现了left join语法,以后在脚本中写左连接语句就方便多了。
sqlite查询完整示例
1 using (var context = new TestSqliteContext())
2 {
3 string s = @"
4 var q = from p in context.Persons
5 left join a in context.AddressInfos on p.Id equals a.UserId
6 select new { p.Id, p.Name, p.Age, MyAddress = a.Address };
7 q.ToList();
8 ";
9 var script = new Script();
10 script.Context.SetVar("context", context);
11 var list = script.Eval(s);
12 Console.WriteLine(JsonConvert.SerializeObject(list, Formatting.Indented));
13 }
生成的sqlite查询语句为:
1 SELECT "p"."Id", "p"."Name", "p"."Age", "a"."Address" AS "MyAddress"
2 FROM "Persons" AS "p"
3 LEFT JOIN "AddressInfos" AS "a" ON "p"."Id" = "a"."UserId"
二、right join
标准LINQ查询要实现右连接,只能用左连接语句并交换2个表的位置来实现右连接功能。
比如上面的示例中把左连接AddressInfos表改为右连接,则交换2个表位置:
1 from a in context.AddressInfos
2 left join p in context.Persons on a.UserId equals p.Id
3 select new { p.Id, p.Name, p?.Age, MyAddress = a.Address };
虽然能实现右连接效果,但是写法不太符合我们的逻辑,使用right join语法如下:
1 from p in context.Persons
2 right join a in context.AddressInfos on p.Id equals a.UserId
3 select new { p.Id, p.Name, p?.Age, MyAddress = a.Address };
如何实现right join语法呢?
1、在QueryNode中添加AddRightJoin方法
1 public void AddRightJoin(string varName, ITreeNode source, ITreeNode key1, ITreeNode key2)
2 {
3 var right = _Source;
4 var rightName = _CurrentVarName;
5 _Source = source;
6 _CurrentVarName = varName;
7 AddLeftJoin(rightName, right, key2, key1);
8 }
没错,就是交换2个表的位置使用左连接来实现。
2、在FromTokenHandler中添加right join解析
1 private void BuildRightJoin(DefaultSyntaxAnalyzer analyzer, TokenAnalyzingArgs e, BuildOptions createFullOptions, QueryNode queryNode)
2 {
3 analyzer.ValidateNextToken(e.TokenReader, "join");
4
5 var varToken = analyzer.ValidateNextToken(e.TokenReader, ETokenType.Word);
6 analyzer.ValidateNextToken(e.TokenReader, "in");
7
8 var source = analyzer.BuildOneStatement(e.BuildContext, e.ScriptContext, e.Options, e.TokenReader, e.Control, e.Ignore, _OnTokens);
9
10 analyzer.ValidateNextToken(e.TokenReader, "on");
11
12 var key1 = analyzer.BuildOneStatement(e.BuildContext, e.ScriptContext, createFullOptions, e.TokenReader, e.Control, e.Ignore, _EqualsTokens);
13 analyzer.ValidateNextToken(e.TokenReader, "equals");
14 var key2 = analyzer.BuildOneStatement(e.BuildContext, e.ScriptContext, createFullOptions, e.TokenReader, e.Control, e.Ignore, _Join2EndTokens);
15
16 queryNode?.AddRightJoin(varToken.Value.Value, source, key1, key2);
17 }
sqlite查询完整示例
1 using (var context = new TestSqliteContext())
2 {
3 string s = @"
4 var q = from p in context.Persons
5 right join a in context.AddressInfos on p.Id equals a.UserId
6 select new { p.Id, p.Name, p?.Age, MyAddress = a.Address };
7 q.ToList();
8 ";
9 var script = new Script();
10 script.Context.SetVar("context", context);
11 var list = script.Eval(s);
12 Console.WriteLine(JsonConvert.SerializeObject(list, Formatting.Indented));
13 }
生成的sqlit查询语句为(左连接交换2个表位置):
1 SELECT "p"."Id", "p"."Name", "p"."Age", "a"."Address" AS "MyAddress"
2 FROM "AddressInfos" AS "a"
3 LEFT JOIN "Persons" AS "p" ON "a"."UserId" = "p"."Id"
三、结束语
虽然是2个小小的语法,却大大提高了我们的脚本编写效率。
AScript开源地址:https://gitee.com/rockey627/AScript