ServiceStack.OrmLite: 在 SqlExpression 中为匿名类型使用别名
ServiceStack.OrmLite: using aliases in SqlExpression for anonymous types
我在这里发布这个问题,因为我仍在 ServiceStack 客户论坛上等待批准。
这是一个相当具体的问题,所以我不希望很多人能提供帮助... @mythz :)
我正在将 ServiceStack 从 v5.4 迁移到 v5.7,SqlExpression
中的别名存在问题。
我写了一个自定义 sql concat 来在一列中得到一个 "kind-of-csv-format",以便在使用联合时将数据合并到一列中。
从 SQL 方面来看,简化版本为:
SELECT CONCAT(Col1, ',', Col2) as Data FROM Table1
UNION ALL
SELECT CONCAT(Col3, ',', Col4, ',', Col5) as Data FROM Table2
在 C# 中,使用 OrmLite api,我这样做:
var q1 = db.From<Table1>();
q1.Select(x => new
{
Data = Sql.Custom(q1.ConcatWithSeparator("@delimiter", y => new { y.Col1, y.Col2 }))
});
ConcatWithSeparator
是我的自定义方法,它在后台调用底层 IOrmLiteDialectProvider.SqlConcat()
,在匿名类型成员之间的 @delimiter
之前插入。
这给了我:
SELECT CONCAT("Table1"."Col1", @delimiter, "Table1"."Col2") AS DATA FROM "Table1"
这适用于 v5.4,但正如我所注意到的,在 v5.7 中,方法中引入了一个更改SqlExpression.SetAnonTypePropertyNamesForSelectExpression()
(https://github.com/ServiceStack/ServiceStack.OrmLite/blob/v5.7/src/ServiceStack.OrmLite/Expressions/SqlExpression.cs)
if (arg is ConditionalExpression ce || // new { Alias = x.Value > 1 ? 1 : x.Value }
arg is BinaryExpression be || // new { Alias = x.First + " " + x.Last }
arg is MemberExpression me || // new { Alias = DateTime.UtcNow }
arg is ConstantExpression ct) // new { Alias = 1 }
{
IOrmLiteConverter converter;
var strExpr = !(expr is PartialSqlString) && (converter = DialectProvider.GetConverterBestMatch(expr.GetType())) != null
? converter.ToQuotedString(expr.GetType(), expr)
: expr.ToString();
return new PartialSqlString(strExpr + " AS " + member.Name);
}
特别是关于这张支票:
if (
...
arg is MemberExpression me || // new { Alias = DateTime.UtcNow }
...
)
这对 new { x.Col1 }
的计算结果也为真,因为它是 new { Col1 = x.Col1 }
的缩写,这进一步使 sql 语法 Col1 as Col1
在我的 concat 中,结果是错误的 SQL
SELECT CONCAT("Table1"."Col1" AS Col1, @delimiter, "Table1"."Col2" AS Col2) AS DATA FROM "Table1"
所以真正的问题是,是否应该进行额外检查,如果它是 MemberExpression me
,那么是否还 me.Member.Name != member.Name
?或者可能有一些额外的配置选项来绕过别名生成?
或者...有没有其他更容易实现的方法,我想做什么? (我必须支持 MySQL、MSSQL、Sqlite 和 PostgreSQL)。
更新,关于@mythz 下面的回答
不幸的是,它仍然没有按预期工作。但我认为,我们越来越近了。
转换器在这里:
IOrmLiteConverter converter;
var strExpr = !(expr is PartialSqlString) && (converter = DialectProvider.GetConverterBestMatch(expr.GetType())) != null
? converter.ToQuotedString(expr.GetType(), expr)
: expr.ToString();
return new PartialSqlString(strExpr != member.Name
? strExpr + " AS " + member.Name
: strExpr);
将表达式计算为带引号的字符串,所以最后有一个检查 "\"Col1\"" != "Col1"
.
此外,我还写了一个ConcatWithSeparator<Table1, Table2>
的版本,可以将lambda作为(t1, t2) => new { t1.Col1, t2.Col1 }
。
在那种情况下,它会将成员表达式评估为 "Table1"."Col1"
之类的东西(取决于 DBMS)——我希望 multi-table 版本不是一蹴而就的,它仍然可以实现没有原始 sql...
我已将其更改为当别名与 this commit 中的名称相同时不使用别名。
此更改适用于现在 available on MyGet 的最新 v5.7.1。
我在这里发布这个问题,因为我仍在 ServiceStack 客户论坛上等待批准。 这是一个相当具体的问题,所以我不希望很多人能提供帮助... @mythz :)
我正在将 ServiceStack 从 v5.4 迁移到 v5.7,SqlExpression
中的别名存在问题。
我写了一个自定义 sql concat 来在一列中得到一个 "kind-of-csv-format",以便在使用联合时将数据合并到一列中。 从 SQL 方面来看,简化版本为:
SELECT CONCAT(Col1, ',', Col2) as Data FROM Table1
UNION ALL
SELECT CONCAT(Col3, ',', Col4, ',', Col5) as Data FROM Table2
在 C# 中,使用 OrmLite api,我这样做:
var q1 = db.From<Table1>();
q1.Select(x => new
{
Data = Sql.Custom(q1.ConcatWithSeparator("@delimiter", y => new { y.Col1, y.Col2 }))
});
ConcatWithSeparator
是我的自定义方法,它在后台调用底层 IOrmLiteDialectProvider.SqlConcat()
,在匿名类型成员之间的 @delimiter
之前插入。
这给了我:
SELECT CONCAT("Table1"."Col1", @delimiter, "Table1"."Col2") AS DATA FROM "Table1"
这适用于 v5.4,但正如我所注意到的,在 v5.7 中,方法中引入了一个更改SqlExpression.SetAnonTypePropertyNamesForSelectExpression()
(https://github.com/ServiceStack/ServiceStack.OrmLite/blob/v5.7/src/ServiceStack.OrmLite/Expressions/SqlExpression.cs)
if (arg is ConditionalExpression ce || // new { Alias = x.Value > 1 ? 1 : x.Value }
arg is BinaryExpression be || // new { Alias = x.First + " " + x.Last }
arg is MemberExpression me || // new { Alias = DateTime.UtcNow }
arg is ConstantExpression ct) // new { Alias = 1 }
{
IOrmLiteConverter converter;
var strExpr = !(expr is PartialSqlString) && (converter = DialectProvider.GetConverterBestMatch(expr.GetType())) != null
? converter.ToQuotedString(expr.GetType(), expr)
: expr.ToString();
return new PartialSqlString(strExpr + " AS " + member.Name);
}
特别是关于这张支票:
if (
...
arg is MemberExpression me || // new { Alias = DateTime.UtcNow }
...
)
这对 new { x.Col1 }
的计算结果也为真,因为它是 new { Col1 = x.Col1 }
的缩写,这进一步使 sql 语法 Col1 as Col1
在我的 concat 中,结果是错误的 SQL
SELECT CONCAT("Table1"."Col1" AS Col1, @delimiter, "Table1"."Col2" AS Col2) AS DATA FROM "Table1"
所以真正的问题是,是否应该进行额外检查,如果它是 MemberExpression me
,那么是否还 me.Member.Name != member.Name
?或者可能有一些额外的配置选项来绕过别名生成?
或者...有没有其他更容易实现的方法,我想做什么? (我必须支持 MySQL、MSSQL、Sqlite 和 PostgreSQL)。
更新,关于@mythz 下面的回答
不幸的是,它仍然没有按预期工作。但我认为,我们越来越近了。 转换器在这里:
IOrmLiteConverter converter;
var strExpr = !(expr is PartialSqlString) && (converter = DialectProvider.GetConverterBestMatch(expr.GetType())) != null
? converter.ToQuotedString(expr.GetType(), expr)
: expr.ToString();
return new PartialSqlString(strExpr != member.Name
? strExpr + " AS " + member.Name
: strExpr);
将表达式计算为带引号的字符串,所以最后有一个检查 "\"Col1\"" != "Col1"
.
此外,我还写了一个ConcatWithSeparator<Table1, Table2>
的版本,可以将lambda作为(t1, t2) => new { t1.Col1, t2.Col1 }
。
在那种情况下,它会将成员表达式评估为 "Table1"."Col1"
之类的东西(取决于 DBMS)——我希望 multi-table 版本不是一蹴而就的,它仍然可以实现没有原始 sql...
我已将其更改为当别名与 this commit 中的名称相同时不使用别名。
此更改适用于现在 available on MyGet 的最新 v5.7.1。