Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 4b18d7d

Browse files
committed
Allow anon type property name in a Select() to be used as the prefix for all returned column names when selecting an entire table, e.g. SqlExpression.Select(a => new { TableA_ = a }) prefixes every column of the table referenced by parameter "a" with "TableA_". This is useful for disambiguating columns belonging to different tables in client code.
1 parent c2e88e8 commit 4b18d7d

File tree

7 files changed

+205
-26
lines changed

7 files changed

+205
-26
lines changed

src/ServiceStack.OrmLite.Firebird/FirebirdOrmLiteDialectProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,10 +657,10 @@ public override string EscapeWildcards(string value)
657657
.Replace("%", @"^%");
658658
}
659659

660-
public override string GetColumnNames(ModelDefinition modelDef, bool tableQualified = false)
660+
public override string GetColumnNames(ModelDefinition modelDef)
661661
{
662662
if (QuoteNames)
663-
return modelDef.GetColumnNames(this); // Calls this.GetColumnNames(modelDef) - infinite loop?
663+
return modelDef.GetColumnNames(this);
664664

665665
var sqlColumns = StringBuilderCache.Allocate();
666666
foreach (var field in modelDef.FieldDefinitions)

src/ServiceStack.OrmLite.PostgreSQL/PostgreSQLDialectProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ public override string GetColumnDefinition(
128128
//Convert xmin into an integer so it can be used in comparisons
129129
public const string RowVersionFieldComparer = "int8in(xidout(xmin))";
130130

131-
public override string GetRowVersionColumnName(FieldDefinition field)
131+
public override SelectListItem GetRowVersionColumnName(FieldDefinition field)
132132
{
133-
return "xmin as " + GetQuotedColumnName(field.FieldName);
133+
return new SelectListExpression(this, "xmin", field.FieldName);
134134
}
135135

136136
public override void AppendFieldCondition(StringBuilder sqlFilter, FieldDefinition fieldDef, IDbCommand cmd)

src/ServiceStack.OrmLite/Expressions/SqlExpression.cs

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,23 +1453,52 @@ protected virtual object VisitNew(NewExpression nex)
14531453
var r = StringBuilderCache.Allocate();
14541454
for (var i = 0; i < exprs.Count; ++i)
14551455
{
1456+
exprs[i] = SetAnonTypePropertyNamesForSelectExpression(exprs[i], nex.Arguments[i], nex.Members[i]);
1457+
14561458
if (i > 0)
14571459
r.Append(",");
14581460

14591461
r.Append(exprs[i]);
1462+
}
1463+
return StringBuilderCache.ReturnAndFree(r);
1464+
}
14601465

1461-
// Use the anon type property name, rather than the table property name, as the returned column name
1466+
return CachedExpressionCompiler.Evaluate(nex);
1467+
}
14621468

1463-
var propertyExpr = nex.Arguments[i] as MemberExpression;
1464-
if (propertyExpr != null && propertyExpr.Member.Name != nex.Members[i].Name)
1469+
private object SetAnonTypePropertyNamesForSelectExpression(object expr, Expression arg, MemberInfo member)
1470+
{
1471+
// When selecting a column use the anon type property name, rather than the table property name, as the returned column name
1472+
1473+
MemberExpression propertyExpr;
1474+
if ((propertyExpr = arg as MemberExpression) != null && propertyExpr.Member.Name != member.Name)
1475+
return new SelectListExpression(DialectProvider, expr.ToString(), member.Name);
1476+
1477+
// When selecting an entire table use the anon type property name as a prefix for the returned column name
1478+
// to allow the caller to distinguish properties with the same names from different tables
1479+
1480+
ParameterExpression paramExpr;
1481+
SelectList selectList;
1482+
if ((paramExpr = arg as ParameterExpression) != null && paramExpr.Name != member.Name && (selectList = expr as SelectList) != null)
1483+
{
1484+
foreach (var item in selectList.Items)
1485+
{
1486+
if (!string.IsNullOrEmpty(item.Alias))
14651487
{
1466-
r.Append(" AS " + DialectProvider.GetQuotedName(nex.Members[i].Name));
1488+
item.Alias = member.Name + item.Alias;
1489+
}
1490+
else
1491+
{
1492+
var columnItem = item as SelectListColumn;
1493+
if (columnItem != null)
1494+
{
1495+
columnItem.Alias = member.Name + columnItem.ColumnName;
1496+
}
14671497
}
14681498
}
1469-
return StringBuilderCache.ReturnAndFree(r);
14701499
}
14711500

1472-
return CachedExpressionCompiler.Evaluate(nex);
1501+
return expr;
14731502
}
14741503

14751504
protected virtual object VisitParameter(ParameterExpression p)
@@ -2074,6 +2103,118 @@ public EnumMemberAccess(string text, Type enumType)
20742103
public Type EnumType { get; private set; }
20752104
}
20762105

2106+
public abstract class SelectListItem
2107+
{
2108+
protected SelectListItem(IOrmLiteDialectProvider dialectProvider, string alias)
2109+
{
2110+
if (dialectProvider == null)
2111+
throw new ArgumentNullException("dialectProvider");
2112+
2113+
DialectProvider = dialectProvider;
2114+
Alias = alias;
2115+
}
2116+
2117+
/// <summary>
2118+
/// Unquoted alias for the column or expression being selected.
2119+
/// </summary>
2120+
public string Alias { get; set; }
2121+
2122+
protected IOrmLiteDialectProvider DialectProvider { get; }
2123+
2124+
public abstract override string ToString();
2125+
}
2126+
2127+
public class SelectListExpression : SelectListItem
2128+
{
2129+
public SelectListExpression(IOrmLiteDialectProvider dialectProvider, string selectExpression, string alias)
2130+
: base(dialectProvider, alias)
2131+
{
2132+
if (string.IsNullOrEmpty(selectExpression))
2133+
throw new ArgumentNullException("selectExpression");
2134+
if (string.IsNullOrEmpty(alias))
2135+
throw new ArgumentNullException("alias");
2136+
2137+
SelectExpression = selectExpression;
2138+
Alias = alias;
2139+
}
2140+
2141+
/// <summary>
2142+
/// The SQL expression being selected, including any necessary quoting.
2143+
/// </summary>
2144+
public string SelectExpression { get; }
2145+
2146+
public override string ToString()
2147+
{
2148+
return SelectExpression + " AS " + DialectProvider.GetQuotedName(Alias); // Alias is required for a non-column expression
2149+
}
2150+
}
2151+
2152+
public class SelectListColumn : SelectListItem
2153+
{
2154+
public SelectListColumn(IOrmLiteDialectProvider dialectProvider, string columnName, string columnAlias = null, string quotedTableAlias = null)
2155+
: base(dialectProvider, columnAlias)
2156+
{
2157+
if (string.IsNullOrEmpty(columnName))
2158+
throw new ArgumentNullException("columnName");
2159+
2160+
ColumnName = columnName;
2161+
QuotedTableAlias = quotedTableAlias;
2162+
}
2163+
2164+
/// <summary>
2165+
/// Unquoted column name being selected.
2166+
/// </summary>
2167+
public string ColumnName { get; }
2168+
/// <summary>
2169+
/// Table name or alias used to prefix the column name, if any. Already quoted.
2170+
/// </summary>
2171+
public string QuotedTableAlias { get; }
2172+
2173+
public override string ToString()
2174+
{
2175+
var text = DialectProvider.GetQuotedColumnName(ColumnName);
2176+
2177+
if (!string.IsNullOrEmpty(QuotedTableAlias))
2178+
text = QuotedTableAlias + "." + text;
2179+
if (!string.IsNullOrEmpty(Alias))
2180+
text += " AS " + DialectProvider.GetQuotedName(Alias);
2181+
2182+
return text;
2183+
}
2184+
}
2185+
2186+
public class SelectList
2187+
{
2188+
public SelectList()
2189+
{
2190+
Items = new List<SelectListItem>();
2191+
}
2192+
2193+
public SelectList(ICollection<SelectListItem> items)
2194+
{
2195+
if (items == null)
2196+
throw new ArgumentNullException("items");
2197+
2198+
Items = new List<SelectListItem>(items);
2199+
}
2200+
2201+
public List<SelectListItem> Items { get; }
2202+
2203+
public override string ToString()
2204+
{
2205+
var sb = StringBuilderCache.Allocate();
2206+
2207+
foreach (var item in Items)
2208+
{
2209+
if (sb.Length > 0)
2210+
sb.Append(", ");
2211+
sb.Append(item);
2212+
}
2213+
2214+
return StringBuilderCache.ReturnAndFree(sb);
2215+
}
2216+
}
2217+
20772218
public class OrmLiteDataParameter : IDbDataParameter
20782219
{
20792220
public DbType DbType { get; set; }

src/ServiceStack.OrmLite/FieldDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public object GetValue(object onInstance)
7474
public string GetQuotedName(IOrmLiteDialectProvider dialectProvider)
7575
{
7676
return IsRowVersion
77-
? dialectProvider.GetRowVersionColumnName(this)
77+
? dialectProvider.GetRowVersionColumnName(this).ToString()
7878
: dialectProvider.GetQuotedColumnName(FieldName);
7979
}
8080

src/ServiceStack.OrmLite/IOrmLiteDialectProvider.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,10 @@ string ToSelectFromProcedureStatement(object fromObjWithProperties,
157157
bool DoesSequenceExist(IDbCommand dbCmd, string sequencName);
158158

159159
ulong FromDbRowVersion(object value);
160-
string GetRowVersionColumnName(FieldDefinition field);
160+
SelectListItem GetRowVersionColumnName(FieldDefinition field);
161161

162-
string GetColumnNames(ModelDefinition modelDef, bool tableQualified = false);
162+
string GetColumnNames(ModelDefinition modelDef);
163+
SelectList GetColumnNames(ModelDefinition modelDef, bool tableQualified);
163164

164165
SqlExpression<T> SqlExpression<T>();
165166

src/ServiceStack.OrmLite/OrmLiteDialectProviderBase.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -546,41 +546,44 @@ public virtual string ToSelectStatement(ModelDefinition modelDef,
546546
return StringBuilderCache.ReturnAndFree(sb);
547547
}
548548

549-
public virtual string GetRowVersionColumnName(FieldDefinition field)
549+
public virtual SelectListItem GetRowVersionColumnName(FieldDefinition field)
550550
{
551-
return GetQuotedColumnName(field.FieldName);
551+
return new SelectListColumn(this, field.FieldName);
552552
}
553553

554-
public virtual string GetColumnNames(ModelDefinition modelDef, bool tableQualified = false)
554+
public virtual string GetColumnNames(ModelDefinition modelDef)
555+
{
556+
return GetColumnNames(modelDef, false).ToString();
557+
}
558+
559+
public virtual SelectList GetColumnNames(ModelDefinition modelDef, bool tableQualified)
555560
{
556561
var tablePrefix = "";
557562
if (tableQualified)
558563
{
559-
tablePrefix = GetQuotedTableName(modelDef) + ".";
564+
tablePrefix = GetQuotedTableName(modelDef);
560565
}
561566

562-
var sqlColumns = StringBuilderCache.Allocate();
563-
foreach (var field in modelDef.FieldDefinitions)
567+
var sqlColumns = new SelectListItem[modelDef.FieldDefinitions.Count];
568+
for (var i = 0; i < sqlColumns.Length; ++i)
564569
{
565-
if (sqlColumns.Length > 0)
566-
sqlColumns.Append(", ");
570+
var field = modelDef.FieldDefinitions[i];
567571

568572
if (field.CustomSelect != null)
569573
{
570-
sqlColumns.Append(field.CustomSelect + " AS " + field.FieldName);
574+
sqlColumns[i] = new SelectListExpression(this, field.CustomSelect, field.FieldName);
571575
}
572576
else if (field.IsRowVersion)
573577
{
574-
sqlColumns.Append(GetRowVersionColumnName(field));
578+
sqlColumns[i] = GetRowVersionColumnName(field);
575579
}
576580
else
577581
{
578-
sqlColumns.Append(tablePrefix);
579-
sqlColumns.Append(GetQuotedColumnName(field.FieldName));
582+
sqlColumns[i] = new SelectListColumn(this, field.FieldName, null, tablePrefix);
580583
}
581584
}
582585

583-
return StringBuilderCache.ReturnAndFree(sqlColumns);
586+
return new SelectList(sqlColumns);
584587
}
585588

586589
public virtual string ToInsertRowStatement(IDbCommand cmd, object objWithProperties, ICollection<string> insertFields = null)

tests/ServiceStack.OrmLite.Tests/Expression/SqlExpressionTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,25 @@ public override bool Equals(object obj)
874874
}
875875
}
876876

877+
private class JoinSelectResults3
878+
{
879+
// From TableA
880+
public int TableA_Id { get; set; }
881+
public bool TableA_Bool { get; set; }
882+
public string TableA_Name { get; set; }
883+
884+
// From TableB
885+
public int Id { get; set; }
886+
public string TableBName { get; set; }
887+
888+
public override bool Equals(object obj)
889+
{
890+
var other = (JoinSelectResults3)obj;
891+
return TableA_Id == other.TableA_Id && TableA_Bool == other.TableA_Bool && TableA_Name == other.TableA_Name
892+
&& Id == other.Id && TableBName == other.TableBName;
893+
}
894+
}
895+
877896
[Test]
878897
public void Can_select_entire_tables()
879898
{
@@ -920,6 +939,21 @@ public void Can_select_entire_tables()
920939
new JoinSelectResults2 { Id = 2, Bool = true, Name = "NameA2", TableBId = 3, TableBName = "NameB3" },
921940
};
922941
Assert.That(rows2, Is.EqualTo(expected2));
942+
943+
// Use column alias prefixes for all columns in TableA
944+
945+
var q3 = db.From<TableA>()
946+
.Join<TableB>()
947+
.Select<TableA, TableB>((a, b) => new { TableA_ = a, b.Id, TableBName = b.Name });
948+
949+
var rows3 = db.Select<JoinSelectResults3>(q3).OrderBy(r => r.TableA_Id).ThenBy(r => r.Id).ToList();
950+
var expected3 = new[]
951+
{
952+
new JoinSelectResults3 { TableA_Id = 1, TableA_Bool = false, TableA_Name = "NameA1", Id = 1, TableBName = "NameB1" },
953+
new JoinSelectResults3 { TableA_Id = 2, TableA_Bool = true, TableA_Name = "NameA2", Id = 2, TableBName = "NameB2" },
954+
new JoinSelectResults3 { TableA_Id = 2, TableA_Bool = true, TableA_Name = "NameA2", Id = 3, TableBName = "NameB3" },
955+
};
956+
Assert.That(rows3, Is.EqualTo(expected3));
923957
}
924958
finally
925959
{

0 commit comments

Comments
 (0)