Reputation: 73
I am posting this question here, as I'm still waiting for approval on the ServiceStack customer forum. It's a rather specific question, so I don't expect many can help... @mythz :)
I'm migrating ServiceStack from v5.4 to v5.7 and there is an issue with aliases in SqlExpression
.
I wrote a custom sql concat to get a "kind-of-csv-format" in one column, to merge data into one column, when using unions. From the SQL side, simplified version would be:
SELECT CONCAT(Col1, ',', Col2) as Data FROM Table1
UNION ALL
SELECT CONCAT(Col3, ',', Col4, ',', Col5) as Data FROM Table2
In C#, using OrmLite api, I do:
var q1 = db.From<Table1>();
q1.Select(x => new
{
Data = Sql.Custom(q1.ConcatWithSeparator("@delimiter", y => new { y.Col1, y.Col2 }))
});
ConcatWithSeparator
is my custom method, that calls the underlying IOrmLiteDialectProvider.SqlConcat()
under the hood, inserting before the @delimiter
between the members of anonymous types.
That gave me:
SELECT CONCAT("Table1"."Col1", @delimiter, "Table1"."Col2") AS DATA FROM "Table1"
This worked well for the v5.4, but as I noticed, in v5.7, there was a change introduced in methodSqlExpression.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);
}
In particular, it's about this check:
if (
...
arg is MemberExpression me || // new { Alias = DateTime.UtcNow }
...
)
This evaluates to true also for new { x.Col1 }
, as it's a short for new { Col1 = x.Col1 }
which further makes the sql syntax Col1 as Col1
In my concat, it results in a wrong SQL
SELECT CONCAT("Table1"."Col1" AS Col1, @delimiter, "Table1"."Col2" AS Col2) AS DATA FROM "Table1"
So the real question is, shouldn't there be an extra check, that if it's a MemberExpression me
, also me.Member.Name != member.Name
? Or maybe some extra configuration option to bypass the alias generation?
Or... is there any other, easier approach to achieve, what am I trying to do? (I have to support MySQL, MSSQL, Sqlite and PostgreSQL).
Update, regarding @mythz answer below
Unfortunately, it's still not working as expected. But I think, we are getting closer. The converter here:
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);
evaluates the expression to quoted string, so in the end there is a check "\"Col1\"" != "Col1"
.
Additionally, I also wrote a version of ConcatWithSeparator<Table1, Table2>
that can take a lambda as (t1, t2) => new { t1.Col1, t2.Col1 }
.
In that case, it will evaluate member expression into something like "Table1"."Col1"
(depending on DBMS) - I hope, multi-table version is not a hit-in-the-wall and it still can be implemented without raw sql...
Upvotes: 3
Views: 418
Reputation: 143359
I've changed it to not use the alias when the alias is the same name in this commit.
This change is available from the latest v5.7.1 that's now available on MyGet.
Upvotes: 1