1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-17 21:26:22 -04:00

added marr.datamapper source code for easy debugging.

This commit is contained in:
kay.one
2013-03-30 14:56:34 -07:00
parent 58a05fcef8
commit 3cdff3bb71
96 changed files with 9198 additions and 363 deletions

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
using System.Data.Common;
namespace Marr.Data.QGen
{
/// <summary>
/// This class creates a SQL delete query.
/// </summary>
public class DeleteQuery : IQuery
{
protected Table TargetTable { get; set; }
protected string WhereClause { get; set; }
protected Dialects.Dialect Dialect { get; set; }
public DeleteQuery(Dialects.Dialect dialect, Table targetTable, string whereClause)
{
Dialect = dialect;
TargetTable = targetTable;
WhereClause = whereClause;
}
public string Generate()
{
return string.Format("DELETE FROM {0} {1} ",
Dialect.CreateToken(TargetTable.Name),
WhereClause);
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen.Dialects
{
public class Dialect
{
/// <summary>
/// The default token is surrounded by brackets.
/// </summary>
/// <param name="token"></param>
public virtual string CreateToken(string token)
{
if (string.IsNullOrEmpty(token))
{
return string.Empty;
}
string[] parts = token.Replace('[', new Char()).Replace(']', new Char()).Split('.');
StringBuilder sb = new StringBuilder();
foreach (string part in parts)
{
if (sb.Length > 0)
sb.Append(".");
sb.Append("[").Append(part).Append("]");
}
return sb.ToString();
}
public virtual string IdentityQuery
{
get
{
return null;
}
}
public bool HasIdentityQuery
{
get
{
return !string.IsNullOrEmpty(IdentityQuery);
}
}
public virtual bool SupportsBatchQueries
{
get
{
return true;
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen.Dialects
{
public class FirebirdDialect : Dialect
{
public override string CreateToken(string token)
{
if (string.IsNullOrEmpty(token))
{
return string.Empty;
}
return token.Replace('[', new Char()).Replace(']', new Char());
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen.Dialects
{
public class OracleDialect : Dialect
{
public override string CreateToken(string token)
{
if (string.IsNullOrEmpty(token))
{
return string.Empty;
}
string[] parts = token.Replace('[', new Char()).Replace(']', new Char()).Split('.');
StringBuilder sb = new StringBuilder();
foreach (string part in parts)
{
if (sb.Length > 0)
sb.Append(".");
bool hasSpaces = part.Contains(' ');
if (hasSpaces)
sb.Append("[").Append(part).Append("]");
else
sb.Append(part);
}
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen.Dialects
{
public class SqlServerCeDialect : Dialect
{
public override string IdentityQuery
{
get
{
return "SELECT @@IDENTITY;";
}
}
public override bool SupportsBatchQueries
{
get
{
return false;
}
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen.Dialects
{
public class SqlServerDialect : Dialect
{
public override string IdentityQuery
{
get
{
return "SELECT SCOPE_IDENTITY();";
}
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen.Dialects
{
public class SqliteDialect : Dialect
{
public override string IdentityQuery
{
get
{
return "SELECT last_insert_rowid();";
}
}
}
}

View File

@@ -0,0 +1,149 @@
/* This class was copied from Mehfuz's LinqExtender project, which is available from github.
* http://mehfuzh.github.com/LinqExtender/
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
namespace Marr.Data.QGen
{
///<summary>
/// Expression visitor
///</summary>
public class ExpressionVisitor
{
/// <summary>
/// Visits expression and delegates call to different to branch.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression Visit(Expression expression)
{
if (expression == null)
return null;
switch (expression.NodeType)
{
case ExpressionType.Lambda:
return VisitLamda((LambdaExpression)expression);
case ExpressionType.ArrayLength:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.Negate:
case ExpressionType.UnaryPlus:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
case ExpressionType.Quote:
case ExpressionType.TypeAs:
return this.VisitUnary((UnaryExpression)expression);
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.And:
case ExpressionType.AndAlso:
case ExpressionType.ArrayIndex:
case ExpressionType.Coalesce:
case ExpressionType.Divide:
case ExpressionType.Equal:
case ExpressionType.ExclusiveOr:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LeftShift:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.Modulo:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.NotEqual:
case ExpressionType.Or:
case ExpressionType.OrElse:
case ExpressionType.Power:
case ExpressionType.RightShift:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
return this.VisitBinary((BinaryExpression)expression);
case ExpressionType.Call:
return this.VisitMethodCall((MethodCallExpression)expression);
case ExpressionType.Constant:
return this.VisitConstant((ConstantExpression)expression);
case ExpressionType.MemberAccess:
return this.VisitMemberAccess((MemberExpression)expression);
case ExpressionType.Parameter:
return this.VisitParameter((ParameterExpression)expression);
}
throw new ArgumentOutOfRangeException("expression", expression.NodeType.ToString());
}
/// <summary>
/// Visits the constance expression. To be implemented by user.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitConstant(ConstantExpression expression)
{
return expression;
}
/// <summary>
/// Visits the memeber access expression. To be implemented by user.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitMemberAccess(MemberExpression expression)
{
return expression;
}
/// <summary>
/// Visits the method call expression. To be implemented by user.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitMethodCall(MethodCallExpression expression)
{
throw new NotImplementedException();
}
/// <summary>
/// Visits the binary expression.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitBinary(BinaryExpression expression)
{
this.Visit(expression.Left);
this.Visit(expression.Right);
return expression;
}
/// <summary>
/// Visits the unary expression.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitUnary(UnaryExpression expression)
{
this.Visit(expression.Operand);
return expression;
}
/// <summary>
/// Visits the lamda expression.
/// </summary>
/// <param name="lambdaExpression"></param>
/// <returns></returns>
protected virtual Expression VisitLamda(LambdaExpression lambdaExpression)
{
this.Visit(lambdaExpression.Body);
return lambdaExpression;
}
private Expression VisitParameter(ParameterExpression expression)
{
return expression;
}
}
}

16
Marr.Data/QGen/IQuery.cs Normal file
View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen
{
internal interface IQuery
{
/// <summary>
/// Generates a SQL query for a given entity.
/// </summary>
/// <returns></returns>
string Generate();
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen
{
public interface IQueryBuilder
{
string BuildQuery();
}
public interface ISortQueryBuilder : IQueryBuilder
{
string BuildQuery(bool useAltNames);
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
using System.Data.Common;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
/// <summary>
/// This class creates an insert query.
/// </summary>
public class InsertQuery : IQuery
{
protected Dialect Dialect { get; set; }
protected string Target { get; set; }
protected ColumnMapCollection Columns { get; set; }
protected DbCommand Command { get; set; }
public InsertQuery(Dialect dialect, ColumnMapCollection columns, DbCommand command, string target)
{
if (string.IsNullOrEmpty(target))
{
throw new DataMappingException("A target table must be passed in or set in a TableAttribute.");
}
Dialect = dialect;
Target = target;
Columns = columns;
Command = command;
}
public virtual string Generate()
{
StringBuilder sql = new StringBuilder();
StringBuilder values = new StringBuilder(") VALUES (");
sql.AppendFormat("INSERT INTO {0} (", Dialect.CreateToken(Target));
int sqlStartIndex = sql.Length;
int valuesStartIndex = values.Length;
foreach (DbParameter p in Command.Parameters)
{
var c = Columns.GetByColumnName(p.ParameterName);
if (c == null)
break; // All insert columns have been added
if (sql.Length > sqlStartIndex)
sql.Append(",");
if (values.Length > valuesStartIndex)
values.Append(",");
if (!c.ColumnInfo.IsAutoIncrement)
{
sql.AppendFormat(Dialect.CreateToken(c.ColumnInfo.Name));
values.AppendFormat("{0}{1}", Command.ParameterPrefix(), p.ParameterName);
}
}
values.Append(")");
sql.Append(values);
return sql.ToString();
}
}
}

View File

@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
using System.Linq.Expressions;
namespace Marr.Data.QGen
{
public class InsertQueryBuilder<T> : IQueryBuilder
{
private DataMapper _db;
private string _tableName;
private T _entity;
private MappingHelper _mappingHelper;
private ColumnMapCollection _mappings;
private SqlModes _previousSqlMode;
private bool _generateQuery = true;
private bool _getIdentityValue;
private Dialects.Dialect _dialect;
private ColumnMapCollection _columnsToInsert;
public InsertQueryBuilder()
{
// Used only for unit testing with mock frameworks
}
public InsertQueryBuilder(DataMapper db)
{
_db = db;
_tableName = MapRepository.Instance.GetTableName(typeof(T));
_previousSqlMode = _db.SqlMode;
_mappingHelper = new MappingHelper(_db);
_mappings = MapRepository.Instance.GetColumns(typeof(T));
_dialect = QueryFactory.CreateDialect(_db);
}
public virtual InsertQueryBuilder<T> TableName(string tableName)
{
_tableName = tableName;
return this;
}
public virtual InsertQueryBuilder<T> QueryText(string queryText)
{
_generateQuery = false;
_db.Command.CommandText = queryText;
return this;
}
public virtual InsertQueryBuilder<T> Entity(T entity)
{
_entity = entity;
return this;
}
/// <summary>
/// Runs an identity query to get the value of an autoincrement field.
/// </summary>
/// <returns></returns>
public virtual InsertQueryBuilder<T> GetIdentity()
{
if (!_dialect.HasIdentityQuery)
{
string err = string.Format("The current dialect '{0}' does not have an identity query implemented.", _dialect.ToString());
throw new DataMappingException(err);
}
_getIdentityValue = true;
return this;
}
public virtual InsertQueryBuilder<T> ColumnsIncluding(params Expression<Func<T, object>>[] properties)
{
List<string> columnList = new List<string>();
foreach (var column in properties)
{
columnList.Add(column.GetMemberName());
}
return ColumnsIncluding(columnList.ToArray());
}
public virtual InsertQueryBuilder<T> ColumnsIncluding(params string[] properties)
{
_columnsToInsert = new ColumnMapCollection();
foreach (string propertyName in properties)
{
_columnsToInsert.Add(_mappings.GetByFieldName(propertyName));
}
return this;
}
public virtual InsertQueryBuilder<T> ColumnsExcluding(params Expression<Func<T, object>>[] properties)
{
List<string> columnList = new List<string>();
foreach (var column in properties)
{
columnList.Add(column.GetMemberName());
}
return ColumnsExcluding(columnList.ToArray());
}
public virtual InsertQueryBuilder<T> ColumnsExcluding(params string[] properties)
{
_columnsToInsert = new ColumnMapCollection();
_columnsToInsert.AddRange(_mappings);
foreach (string propertyName in properties)
{
_columnsToInsert.RemoveAll(c => c.FieldName == propertyName);
}
return this;
}
public virtual object Execute()
{
if (_generateQuery)
{
BuildQuery();
}
else
{
TryAppendIdentityQuery();
_mappingHelper.CreateParameters<T>(_entity, _mappings.NonReturnValues, _generateQuery);
}
object scalar = null;
try
{
_db.OpenConnection();
scalar = _db.Command.ExecuteScalar();
if (_getIdentityValue && !_dialect.SupportsBatchQueries)
{
// Run identity query as a separate query
_db.Command.CommandText = _dialect.IdentityQuery;
scalar = _db.Command.ExecuteScalar();
}
_mappingHelper.SetOutputValues<T>(_entity, _mappings.OutputFields);
if (scalar != null)
{
_mappingHelper.SetOutputValues<T>(_entity, _mappings.ReturnValues, scalar);
}
}
finally
{
_db.CloseConnection();
}
if (_generateQuery)
{
// Return to previous sql mode
_db.SqlMode = _previousSqlMode;
}
return scalar;
}
public virtual string BuildQuery()
{
if (_entity == null)
throw new ArgumentNullException("You must specify an entity to insert.");
// Override SqlMode since we know this will be a text query
_db.SqlMode = SqlModes.Text;
var columns = _columnsToInsert ?? _mappings;
_mappingHelper.CreateParameters<T>(_entity, columns, _generateQuery);
IQuery query = QueryFactory.CreateInsertQuery(columns, _db, _tableName);
_db.Command.CommandText = query.Generate();
TryAppendIdentityQuery();
return _db.Command.CommandText;
}
private void TryAppendIdentityQuery()
{
if (_getIdentityValue && _dialect.SupportsBatchQueries)
{
// Append a batched identity query
if (!_db.Command.CommandText.EndsWith(";"))
{
_db.Command.CommandText += ";";
}
_db.Command.CommandText += _dialect.IdentityQuery;
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using Marr.Data.QGen.Dialects;
using System.Linq.Expressions;
namespace Marr.Data.QGen
{
/// <summary>
/// This class overrides the WhereBuilder which utilizes the ExpressionVisitor base class,
/// and it is responsible for translating the lambda expression into a "JOIN ON" clause.
/// It populates the protected string builder, which outputs the "JOIN ON" clause when the ToString method is called.
/// </summary>
/// <typeparam name="T">The entity that is on the left side of the join.</typeparam>
/// <typeparam name="T2">The entity that is on the right side of the join.</typeparam>
public class JoinBuilder<T, T2> : WhereBuilder<T>
{
public JoinBuilder(DbCommand command, Dialect dialect, Expression<Func<T, T2, bool>> filter, TableCollection tables)
: base(command, dialect, filter.Body, tables, false, true)
{ }
protected override string PrefixText
{
get
{
return "ON";
}
}
protected override Expression VisitMemberAccess(MemberExpression expression)
{
string fqColumn = GetFullyQualifiedColumnName(expression.Member, expression.Expression.Type);
_sb.Append(fqColumn);
return expression;
}
}
}

View File

@@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
/// <summary>
/// Decorates the SelectQuery by wrapping it in a paging query.
/// </summary>
public class PagingQueryDecorator : IQuery
{
private SelectQuery _innerQuery;
private int _firstRow;
private int _lastRow;
public PagingQueryDecorator(SelectQuery innerQuery, int skip, int take)
{
if (string.IsNullOrEmpty(innerQuery.OrderBy.ToString()))
{
throw new DataMappingException("A paged query must specify an order by clause.");
}
_innerQuery = innerQuery;
_firstRow = skip + 1;
_lastRow = skip + take;
}
public string Generate()
{
// Decide which type of paging query to create
if (_innerQuery.IsView || _innerQuery.IsJoin)
{
return ComplexPaging();
}
else
{
return SimplePaging();
}
}
/// <summary>
/// Generates a query that pages a simple inner query.
/// </summary>
/// <returns></returns>
private string SimplePaging()
{
// Create paged query
StringBuilder sql = new StringBuilder();
sql.AppendLine("WITH RowNumCTE AS");
sql.AppendLine("(");
_innerQuery.BuildSelectClause(sql);
BuildRowNumberColumn(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
sql.AppendLine(")");
BuildSimpleOuterSelect(sql);
return sql.ToString();
}
/// <summary>
/// Generates a query that pages a view or joined inner query.
/// </summary>
/// <returns></returns>
private string ComplexPaging()
{
// Create paged query
StringBuilder sql = new StringBuilder();
sql.AppendLine("WITH GroupCTE AS (");
BuildSelectClause(sql);
BuildGroupColumn(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
sql.AppendLine("),");
sql.AppendLine("RowNumCTE AS (");
sql.AppendLine("SELECT *");
BuildRowNumberColumn(sql);
sql.AppendLine("FROM GroupCTE");
sql.AppendLine("WHERE GroupRow = 1");
sql.AppendLine(")");
_innerQuery.BuildSelectClause(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
BuildJoinBackToCTE(sql);
sql.AppendFormat("WHERE RowNumber BETWEEN {0} AND {1}", _firstRow, _lastRow);
return sql.ToString();
}
private void BuildJoinBackToCTE(StringBuilder sql)
{
Table baseTable = GetBaseTable();
sql.AppendLine("INNER JOIN RowNumCTE cte");
int pksAdded = 0;
foreach (var pk in baseTable.Columns.PrimaryKeys)
{
if (pksAdded > 0)
sql.Append(" AND ");
string cteQueryPkName = _innerQuery.NameOrAltName(pk.ColumnInfo);
string outerQueryPkName = _innerQuery.IsJoin ? pk.ColumnInfo.Name : _innerQuery.NameOrAltName(pk.ColumnInfo);
sql.AppendFormat("ON cte.{0} = {1} ", cteQueryPkName, _innerQuery.Dialect.CreateToken(string.Concat("t0", ".", outerQueryPkName)));
pksAdded++;
}
sql.AppendLine();
}
private void BuildSimpleOuterSelect(StringBuilder sql)
{
sql.Append("SELECT ");
int startIndex = sql.Length;
// COLUMNS
foreach (Table join in _innerQuery.Tables)
{
for (int i = 0; i < join.Columns.Count; i++)
{
var c = join.Columns[i];
if (sql.Length > startIndex)
sql.Append(",");
string token = _innerQuery.NameOrAltName(c.ColumnInfo);
sql.Append(_innerQuery.Dialect.CreateToken(token));
}
}
sql.AppendLine("FROM RowNumCTE");
sql.AppendFormat("WHERE RowNumber BETWEEN {0} AND {1}", _firstRow, _lastRow).AppendLine();
sql.AppendLine("ORDER BY RowNumber ASC;");
}
private void BuildGroupColumn(StringBuilder sql)
{
bool isView = _innerQuery.IsView;
sql.AppendFormat(", ROW_NUMBER() OVER (PARTITION BY {0} {1}) As GroupRow ", BuildBaseTablePKColumns(isView), _innerQuery.OrderBy.BuildQuery(isView));
}
private string BuildBaseTablePKColumns(bool useAltName = true)
{
Table baseTable = GetBaseTable();
StringBuilder sb = new StringBuilder();
foreach (var col in baseTable.Columns.PrimaryKeys)
{
if (sb.Length > 0)
sb.AppendLine(", ");
string columnName = useAltName ?
_innerQuery.NameOrAltName(col.ColumnInfo) :
col.ColumnInfo.Name;
sb.AppendFormat(_innerQuery.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", columnName)));
}
return sb.ToString();
}
private void BuildRowNumberColumn(StringBuilder sql)
{
string orderBy = _innerQuery.OrderBy.ToString();
// Remove table prefixes from order columns
foreach (Table t in _innerQuery.Tables)
{
orderBy = orderBy.Replace(string.Format("[{0}].", t.Alias), "");
}
sql.AppendFormat(", ROW_NUMBER() OVER ({0}) As RowNumber ", orderBy);
}
private Table GetBaseTable()
{
Table baseTable = null;
if (_innerQuery.Tables[0] is View)
{
baseTable = (_innerQuery.Tables[0] as View).Tables[0];
}
else
{
baseTable = _innerQuery.Tables[0];
}
return baseTable;
}
public void BuildSelectClause(StringBuilder sql)
{
List<string> appended = new List<string>();
sql.Append("SELECT ");
int startIndex = sql.Length;
// COLUMNS
foreach (Table join in _innerQuery.Tables)
{
for (int i = 0; i < join.Columns.Count; i++)
{
var c = join.Columns[i];
if (sql.Length > startIndex && sql[sql.Length - 1] != ',')
sql.Append(",");
if (join is View)
{
string token = _innerQuery.Dialect.CreateToken(string.Concat(join.Alias, ".", _innerQuery.NameOrAltName(c.ColumnInfo)));
if (appended.Contains(token))
continue;
sql.Append(token);
appended.Add(token);
}
else
{
string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name);
if (appended.Contains(token))
continue;
sql.Append(_innerQuery.Dialect.CreateToken(token));
if (_innerQuery.UseAltName && c.ColumnInfo.AltName != null && c.ColumnInfo.AltName != c.ColumnInfo.Name)
{
string altName = c.ColumnInfo.AltName;
sql.AppendFormat(" AS {0}", altName);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,532 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data.QGen;
using System.Linq.Expressions;
using System.Reflection;
using Marr.Data.Mapping;
using System.Data.Common;
using System.Collections;
namespace Marr.Data.QGen
{
/// <summary>
/// This class is responsible for building a select query.
/// It uses chaining methods to provide a fluent interface for creating select queries.
/// </summary>
/// <typeparam name="T"></typeparam>
public class QueryBuilder<T> : ExpressionVisitor, IEnumerable<T>, IQueryBuilder
{
#region - Private Members -
private Dialects.Dialect _dialect;
private DataMapper _db;
private TableCollection _tables;
private WhereBuilder<T> _whereBuilder;
private SortBuilder<T> _sortBuilder;
private bool _useAltName = false;
private bool _isGraph = false;
private string _queryText;
private List<MemberInfo> _childrenToLoad;
private bool _enablePaging = false;
private int _skip;
private int _take;
private SortBuilder<T> SortBuilder
{
get
{
// Lazy load
if (_sortBuilder == null)
_sortBuilder = new SortBuilder<T>(this, _db, _whereBuilder, _dialect, _tables, _useAltName);
return _sortBuilder;
}
}
private List<T> _results = new List<T>();
private EntityGraph _entityGraph;
private EntityGraph EntGraph
{
get
{
if (_entityGraph == null)
{
_entityGraph = new EntityGraph(typeof(T), _results);
}
return _entityGraph;
}
}
#endregion
#region - Constructor -
public QueryBuilder()
{
// Used only for unit testing with mock frameworks
}
public QueryBuilder(DataMapper db, Dialects.Dialect dialect)
{
_db = db;
_dialect = dialect;
_tables = new TableCollection();
_tables.Add(new Table(typeof(T)));
_childrenToLoad = new List<MemberInfo>();
}
#endregion
#region - Fluent Methods -
/// <summary>
/// Overrides the base table name that will be used in the query.
/// </summary>
[Obsolete("This method is obsolete. Use either the FromTable or FromView method instead.", true)]
public virtual QueryBuilder<T> From(string tableName)
{
return FromView(tableName);
}
/// <summary>
/// Overrides the base view name that will be used in the query.
/// Will try to use the mapped "AltName" values when loading the columns.
/// </summary>
public virtual QueryBuilder<T> FromView(string viewName)
{
if (string.IsNullOrEmpty(viewName))
throw new ArgumentNullException("view");
_useAltName = true;
// Replace the base table with a view with tables
View view = new View(viewName, _tables.ToArray());
_tables.ReplaceBaseTable(view);
//// Override the base table name
//_tables[0].Name = view;
return this;
}
/// <summary>
/// Overrides the base table name that will be used in the query.
/// Will not try to use the mapped "AltName" values when loading the columns.
/// </summary>
public virtual QueryBuilder<T> FromTable(string table)
{
if (string.IsNullOrEmpty(table))
throw new ArgumentNullException("view");
_useAltName = false;
// Override the base table name
_tables[0].Name = table;
return this;
}
/// <summary>
/// Allows you to manually specify the query text.
/// </summary>
public virtual QueryBuilder<T> QueryText(string queryText)
{
_queryText = queryText;
return this;
}
/// <summary>
/// If no parameters are passed in, this method instructs the DataMapper to load all related entities in the graph.
/// If specific entities are passed in, only these relationships will be loaded.
/// </summary>
/// <param name="childrenToLoad">A list of related child entites to load (passed in as properties / lambda expressions).</param>
public virtual QueryBuilder<T> Graph(params Expression<Func<T, object>>[] childrenToLoad)
{
TableCollection tablesInView = new TableCollection();
if (childrenToLoad.Length > 0)
{
// Add base table
tablesInView.Add(_tables[0]);
foreach (var exp in childrenToLoad)
{
MemberInfo child = (exp.Body as MemberExpression).Member;
var node = EntGraph.Where(g => g.Member != null && g.Member.EqualsMember(child)).FirstOrDefault();
if (node != null)
{
tablesInView.Add(new Table(node.EntityType, JoinType.None));
}
if (!_childrenToLoad.ContainsMember(child))
{
_childrenToLoad.Add(child);
}
}
}
else
{
// Add all tables in the graph
foreach (var node in EntGraph)
{
tablesInView.Add(new Table(node.EntityType, JoinType.None));
}
}
// Replace the base table with a view with tables
View view = new View(_tables[0].Name, tablesInView.ToArray());
_tables.ReplaceBaseTable(view);
_isGraph = true;
_useAltName = true;
return this;
}
public virtual QueryBuilder<T> Page(int pageNumber, int pageSize)
{
_enablePaging = true;
_skip = (pageNumber - 1) * pageSize;
_take = pageSize;
return this;
}
private string[] ParseChildrenToLoad(Expression<Func<T, object>>[] childrenToLoad)
{
List<string> entitiesToLoad = new List<string>();
// Parse relationship member names from expression array
foreach (var exp in childrenToLoad)
{
MemberInfo member = (exp.Body as MemberExpression).Member;
entitiesToLoad.Add(member.Name);
}
return entitiesToLoad.ToArray();
}
/// <summary>
/// Allows you to interact with the DbDataReader to manually load entities.
/// </summary>
/// <param name="readerAction">An action that takes a DbDataReader.</param>
public virtual void DataReader(Action<DbDataReader> readerAction)
{
if (string.IsNullOrEmpty(_queryText))
throw new ArgumentNullException("The query text cannot be blank.");
var mappingHelper = new MappingHelper(_db);
_db.Command.CommandText = _queryText;
try
{
_db.OpenConnection();
using (DbDataReader reader = _db.Command.ExecuteReader())
{
readerAction.Invoke(reader);
}
}
finally
{
_db.CloseConnection();
}
}
public virtual int GetRowCount()
{
SqlModes previousSqlMode = _db.SqlMode;
// Generate a row count query
string where = _whereBuilder != null ? _whereBuilder.ToString() : string.Empty;
IQuery query = QueryFactory.CreateRowCountSelectQuery(_tables, _db, where, SortBuilder, _useAltName);
string queryText = query.Generate();
_db.SqlMode = SqlModes.Text;
int count = (int)_db.ExecuteScalar(queryText);
_db.SqlMode = previousSqlMode;
return count;
}
/// <summary>
/// Executes the query and returns a list of results.
/// </summary>
/// <returns>A list of query results of type T.</returns>
public virtual List<T> ToList()
{
SqlModes previousSqlMode = _db.SqlMode;
BuildQueryOrAppendClauses();
if (_isGraph)
{
_results = (List<T>)_db.QueryToGraph<T>(_queryText, EntGraph, _childrenToLoad);
}
else
{
_results = (List<T>)_db.Query<T>(_queryText, _results, _useAltName);
}
// Return to previous sql mode
_db.SqlMode = previousSqlMode;
return _results;
}
private void BuildQueryOrAppendClauses()
{
if (_queryText == null)
{
// Build entire query
_db.SqlMode = SqlModes.Text;
BuildQuery();
}
else if (_whereBuilder != null || _sortBuilder != null)
{
_db.SqlMode = SqlModes.Text;
if (_whereBuilder != null)
{
// Append a where clause to an existing query
_queryText = string.Concat(_queryText, " ", _whereBuilder.ToString());
}
if (_sortBuilder != null)
{
// Append an order clause to an existing query
_queryText = string.Concat(_queryText, " ", _sortBuilder.ToString());
}
}
}
public virtual string BuildQuery()
{
// Generate a query
string where = _whereBuilder != null ? _whereBuilder.ToString() : string.Empty;
IQuery query = null;
if (_enablePaging)
{
query = QueryFactory.CreatePagingSelectQuery(_tables, _db, where, SortBuilder, _useAltName, _skip, _take);
}
else
{
query = QueryFactory.CreateSelectQuery(_tables, _db, where, SortBuilder, _useAltName);
}
_queryText = query.Generate();
return _queryText;
}
#endregion
#region - Helper Methods -
private ColumnMapCollection GetColumns(IEnumerable<string> entitiesToLoad)
{
// If QueryToGraph<T> and no child load entities are specified, load all children
bool loadAllChildren = _useAltName && entitiesToLoad == null;
// If Query<T>
if (!_useAltName)
{
return MapRepository.Instance.GetColumns(typeof(T));
}
ColumnMapCollection columns = new ColumnMapCollection();
Type baseEntityType = typeof(T);
EntityGraph graph = new EntityGraph(baseEntityType, null);
foreach (var lvl in graph)
{
if (loadAllChildren || lvl.IsRoot || entitiesToLoad.Contains(lvl.Member.Name))
{
columns.AddRange(lvl.Columns);
}
}
return columns;
}
public static implicit operator List<T>(QueryBuilder<T> builder)
{
return builder.ToList();
}
#endregion
#region - Linq Support -
public virtual SortBuilder<T> Where<TObj>(Expression<Func<TObj, bool>> filterExpression)
{
_whereBuilder = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, _useAltName, true);
return SortBuilder;
}
public virtual SortBuilder<T> Where(Expression<Func<T, bool>> filterExpression)
{
_whereBuilder = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, false, true);
return SortBuilder;
}
public virtual SortBuilder<T> Where(string whereClause)
{
if (string.IsNullOrEmpty(whereClause))
throw new ArgumentNullException("whereClause");
if (!whereClause.ToUpper().Contains("WHERE "))
{
whereClause = whereClause.Insert(0, " WHERE ");
}
_whereBuilder = new WhereBuilder<T>(whereClause, _useAltName);
return SortBuilder;
}
public virtual SortBuilder<T> OrderBy(Expression<Func<T, object>> sortExpression)
{
SortBuilder.OrderBy(sortExpression);
return SortBuilder;
}
public virtual SortBuilder<T> ThenBy(Expression<Func<T, object>> sortExpression)
{
SortBuilder.OrderBy(sortExpression);
return SortBuilder;
}
public virtual SortBuilder<T> OrderByDescending(Expression<Func<T, object>> sortExpression)
{
SortBuilder.OrderByDescending(sortExpression);
return SortBuilder;
}
public virtual SortBuilder<T> ThenByDescending(Expression<Func<T, object>> sortExpression)
{
SortBuilder.OrderByDescending(sortExpression);
return SortBuilder;
}
public virtual SortBuilder<T> OrderBy(string orderByClause)
{
if (string.IsNullOrEmpty(orderByClause))
throw new ArgumentNullException("orderByClause");
if (!orderByClause.ToUpper().Contains("ORDER BY "))
{
orderByClause = orderByClause.Insert(0, " ORDER BY ");
}
SortBuilder.OrderBy(orderByClause);
return SortBuilder;
}
public virtual QueryBuilder<T> Take(int count)
{
_enablePaging = true;
_take = count;
return this;
}
public virtual QueryBuilder<T> Skip(int count)
{
_enablePaging = true;
_skip = count;
return this;
}
/// <summary>
/// Handles all.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected override System.Linq.Expressions.Expression Visit(System.Linq.Expressions.Expression expression)
{
return base.Visit(expression);
}
/// <summary>
/// Handles Where.
/// </summary>
/// <param name="lambdaExpression"></param>
/// <returns></returns>
protected override System.Linq.Expressions.Expression VisitLamda(System.Linq.Expressions.LambdaExpression lambdaExpression)
{
_sortBuilder = this.Where(lambdaExpression as Expression<Func<T, bool>>);
return base.VisitLamda(lambdaExpression);
}
/// <summary>
/// Handles OrderBy.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected override System.Linq.Expressions.Expression VisitMethodCall(MethodCallExpression expression)
{
if (expression.Method.Name == "OrderBy" || expression.Method.Name == "ThenBy")
{
var memberExp = ((expression.Arguments[1] as UnaryExpression).Operand as System.Linq.Expressions.LambdaExpression).Body as System.Linq.Expressions.MemberExpression;
_sortBuilder.Order(memberExp.Expression.Type, memberExp.Member.Name);
}
if (expression.Method.Name == "OrderByDescending" || expression.Method.Name == "ThenByDescending")
{
var memberExp = ((expression.Arguments[1] as UnaryExpression).Operand as System.Linq.Expressions.LambdaExpression).Body as System.Linq.Expressions.MemberExpression;
_sortBuilder.OrderByDescending(memberExp.Expression.Type, memberExp.Member.Name);
}
return base.VisitMethodCall(expression);
}
public virtual QueryBuilder<T> Join<TLeft, TRight>(JoinType joinType, Expression<Func<TLeft, IEnumerable<TRight>>> rightEntity, Expression<Func<TLeft, TRight, bool>> filterExpression)
{
MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member;
return this.Join(joinType, rightMember, filterExpression);
}
public virtual QueryBuilder<T> Join<TLeft, TRight>(JoinType joinType, Expression<Func<TLeft, TRight>> rightEntity, Expression<Func<TLeft, TRight, bool>> filterExpression)
{
MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member;
return this.Join(joinType, rightMember, filterExpression);
}
public virtual QueryBuilder<T> Join<TLeft, TRight>(JoinType joinType, MemberInfo rightMember, Expression<Func<TLeft, TRight, bool>> filterExpression)
{
_useAltName = true;
_isGraph = true;
if (!_childrenToLoad.ContainsMember(rightMember))
_childrenToLoad.Add(rightMember);
Table table = new Table(typeof(TRight), joinType);
_tables.Add(table);
var builder = new JoinBuilder<TLeft, TRight>(_db.Command, _dialect, filterExpression, _tables);
table.JoinClause = builder.ToString();
return this;
}
#endregion
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
var list = this.ToList();
return list.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
var list = this.ToList();
return list.GetEnumerator();
}
#endregion
}
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
/// <summary>
/// This class contains the factory logic that determines which type of IQuery object should be created.
/// </summary>
internal class QueryFactory
{
private const string DB_SqlClient = "System.Data.SqlClient.SqlClientFactory";
private const string DB_OleDb = "System.Data.OleDb.OleDbFactory";
private const string DB_SqlCeClient = "System.Data.SqlServerCe.SqlCeProviderFactory";
private const string DB_SystemDataOracleClient = "System.Data.OracleClientFactory";
private const string DB_OracleDataAccessClient = "Oracle.DataAccess.Client.OracleClientFactory";
private const string DB_FireBirdClient = "FirebirdSql.Data.FirebirdClient.FirebirdClientFactory";
private const string DB_SQLiteClient = "System.Data.SQLite.SQLiteFactory";
public static IQuery CreateUpdateQuery(Mapping.ColumnMapCollection columns, IDataMapper dataMapper, string target, string whereClause)
{
Dialect dialect = CreateDialect(dataMapper);
return new UpdateQuery(dialect, columns, dataMapper.Command, target, whereClause);
}
public static IQuery CreateInsertQuery(Mapping.ColumnMapCollection columns, IDataMapper dataMapper, string target)
{
Dialect dialect = CreateDialect(dataMapper);
return new InsertQuery(dialect, columns, dataMapper.Command, target);
}
public static IQuery CreateDeleteQuery(Dialects.Dialect dialect, Table targetTable, string whereClause)
{
return new DeleteQuery(dialect, targetTable, whereClause);
}
public static IQuery CreateSelectQuery(TableCollection tables, IDataMapper dataMapper, string where, ISortQueryBuilder orderBy, bool useAltName)
{
Dialect dialect = CreateDialect(dataMapper);
return new SelectQuery(dialect, tables, where, orderBy, useAltName);
}
public static IQuery CreateRowCountSelectQuery(TableCollection tables, IDataMapper dataMapper, string where, ISortQueryBuilder orderBy, bool useAltName)
{
SelectQuery innerQuery = (SelectQuery)CreateSelectQuery(tables, dataMapper, where, orderBy, useAltName);
string providerString = dataMapper.ProviderFactory.ToString();
switch (providerString)
{
case DB_SqlClient:
return new RowCountQueryDecorator(innerQuery);
case DB_SqlCeClient:
return new RowCountQueryDecorator(innerQuery);
default:
throw new NotImplementedException("Row count has not yet been implemented for this provider.");
}
}
public static IQuery CreatePagingSelectQuery(TableCollection tables, IDataMapper dataMapper, string where, ISortQueryBuilder orderBy, bool useAltName, int skip, int take)
{
SelectQuery innerQuery = (SelectQuery)CreateSelectQuery(tables, dataMapper, where, orderBy, useAltName);
string providerString = dataMapper.ProviderFactory.ToString();
switch (providerString)
{
case DB_SqlClient:
return new PagingQueryDecorator(innerQuery, skip, take);
case DB_SqlCeClient:
return new PagingQueryDecorator(innerQuery, skip, take);
default:
throw new NotImplementedException("Paging has not yet been implemented for this provider.");
}
}
public static Dialects.Dialect CreateDialect(IDataMapper dataMapper)
{
string providerString = dataMapper.ProviderFactory.ToString();
switch (providerString)
{
case DB_SqlClient:
return new SqlServerDialect();
case DB_OracleDataAccessClient:
return new OracleDialect();
case DB_SystemDataOracleClient:
return new OracleDialect();
case DB_SqlCeClient:
return new SqlServerCeDialect();
case DB_FireBirdClient:
return new FirebirdDialect();
case DB_SQLiteClient:
return new SqliteDialect();
default:
return new Dialect();
}
}
}
}

View File

@@ -0,0 +1,19 @@
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
//namespace Marr.Data.QGen
//{
// public class QueryQueueItem
// {
// public QueryQueueItem(string queryText, IEnumerable<string> entitiesToLoad)
// {
// QueryText = queryText;
// EntitiesToLoad = entitiesToLoad;
// }
// public string QueryText { get; set; }
// public IEnumerable<string> EntitiesToLoad { get; private set; }
// }
//}

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Marr.Data.QGen
{
public class RowCountQueryDecorator : IQuery
{
private SelectQuery _innerQuery;
public RowCountQueryDecorator(SelectQuery innerQuery)
{
_innerQuery = innerQuery;
}
public string Generate()
{
// Decide which type of paging query to create
if (_innerQuery.IsView || _innerQuery.IsJoin)
{
return ComplexRowCount();
}
else
{
return SimpleRowCount();
}
}
/// <summary>
/// Generates a row count query for a multiple table joined query (groups by the parent entity).
/// </summary>
/// <returns></returns>
private string ComplexRowCount()
{
// Create paged query
StringBuilder sql = new StringBuilder();
sql.AppendLine("WITH GroupCTE AS (");
sql.Append("SELECT ").AppendLine(BuildBaseTablePKColumns());
BuildGroupColumn(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
sql.AppendLine(")");
BuildSelectCountClause(sql);
sql.AppendLine("FROM GroupCTE");
sql.AppendLine("WHERE GroupRow = 1");
return sql.ToString();
}
/// <summary>
/// Generates a row count query for a single table query (no joins).
/// </summary>
/// <returns></returns>
private string SimpleRowCount()
{
StringBuilder sql = new StringBuilder();
BuildSelectCountClause(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
return sql.ToString();
}
private void BuildGroupColumn(StringBuilder sql)
{
string baseTablePKColumns = BuildBaseTablePKColumns();
sql.AppendFormat(", ROW_NUMBER() OVER (PARTITION BY {0} ORDER BY {1}) As GroupRow ", baseTablePKColumns, baseTablePKColumns);
}
private string BuildBaseTablePKColumns()
{
Table baseTable = GetBaseTable();
StringBuilder sb = new StringBuilder();
foreach (var col in baseTable.Columns.PrimaryKeys)
{
if (sb.Length > 0)
sb.AppendLine(", ");
string colName = _innerQuery.IsView ?
_innerQuery.NameOrAltName(col.ColumnInfo) :
col.ColumnInfo.Name;
sb.AppendFormat(_innerQuery.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", colName)));
}
return sb.ToString();
}
private void BuildSelectCountClause(StringBuilder sql)
{
sql.AppendLine("SELECT COUNT(*)");
}
private Table GetBaseTable()
{
Table baseTable = null;
if (_innerQuery.Tables[0] is View)
{
baseTable = (_innerQuery.Tables[0] as View).Tables[0];
}
else
{
baseTable = _innerQuery.Tables[0];
}
return baseTable;
}
}
}
/*
WITH GroupCTE AS
(
SELECT [t0].[ID],[t0].[OrderName],[t1].[ID] AS OrderItemID,[t1].[OrderID],[t1].[ItemDescription],[t1].[Price],
ROW_NUMBER() OVER (PARTITION BY [t0].[ID] ORDER BY [t0].[OrderName]) As GroupRow
FROM [Order] [t0]
LEFT JOIN [OrderItem] [t1] ON (([t0].[ID] = [t1].[OrderID]))
--WHERE (([t0].[OrderName] = @P0))
)
SELECT * FROM GroupCTE
WHERE GroupRow = 1
*/

View File

@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data;
using Marr.Data.Mapping;
using System.Data.Common;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
/// <summary>
/// This class is responsible for creating a select query.
/// </summary>
public class SelectQuery : IQuery
{
public Dialect Dialect { get; set; }
public string WhereClause { get; set; }
public ISortQueryBuilder OrderBy { get; set; }
public TableCollection Tables { get; set; }
public bool UseAltName;
public SelectQuery(Dialect dialect, TableCollection tables, string whereClause, ISortQueryBuilder orderBy, bool useAltName)
{
Dialect = dialect;
Tables = tables;
WhereClause = whereClause;
OrderBy = orderBy;
UseAltName = useAltName;
}
public bool IsView
{
get
{
return Tables[0] is View;
}
}
public bool IsJoin
{
get
{
return Tables.Count > 1;
}
}
public virtual string Generate()
{
StringBuilder sql = new StringBuilder();
BuildSelectClause(sql);
BuildFromClause(sql);
BuildJoinClauses(sql);
BuildWhereClause(sql);
BuildOrderClause(sql);
return sql.ToString();
}
public void BuildSelectClause(StringBuilder sql)
{
sql.Append("SELECT ");
int startIndex = sql.Length;
// COLUMNS
foreach (Table join in Tables)
{
for (int i = 0; i < join.Columns.Count; i++)
{
var c = join.Columns[i];
if (sql.Length > startIndex)
sql.Append(",");
if (join is View)
{
string token = string.Concat(join.Alias, ".", NameOrAltName(c.ColumnInfo));
sql.Append(Dialect.CreateToken(token));
}
else
{
string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name);
sql.Append(Dialect.CreateToken(token));
if (UseAltName && c.ColumnInfo.AltName != null && c.ColumnInfo.AltName != c.ColumnInfo.Name)
{
string altName = c.ColumnInfo.AltName;
sql.AppendFormat(" AS {0}", altName);
}
}
}
}
}
public string NameOrAltName(IColumnInfo columnInfo)
{
if (UseAltName && columnInfo.AltName != null && columnInfo.AltName != columnInfo.Name)
{
return columnInfo.AltName;
}
else
{
return columnInfo.Name;
}
}
public void BuildFromClause(StringBuilder sql)
{
// BASE TABLE
Table baseTable = Tables[0];
sql.AppendFormat(" FROM {0} {1} ", Dialect.CreateToken(baseTable.Name), Dialect.CreateToken(baseTable.Alias));
}
public void BuildJoinClauses(StringBuilder sql)
{
// JOINS
for (int i = 1; i < Tables.Count; i++)
{
if (Tables[i].JoinType != JoinType.None)
{
sql.AppendFormat("{0} {1} {2} {3} ",
TranslateJoin(Tables[i].JoinType),
Dialect.CreateToken(Tables[i].Name),
Dialect.CreateToken(Tables[i].Alias),
Tables[i].JoinClause);
}
}
}
public void BuildWhereClause(StringBuilder sql)
{
sql.Append(WhereClause);
}
public void BuildOrderClause(StringBuilder sql)
{
sql.Append(OrderBy.ToString());
}
private string TranslateJoin(JoinType join)
{
switch (join)
{
case JoinType.Inner:
return "INNER JOIN";
case JoinType.Left:
return "LEFT JOIN";
case JoinType.Right:
return "RIGHT JOIN";
default:
return string.Empty;
}
}
}
}

View File

@@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
/// <summary>
/// This class is responsible for creating an "ORDER BY" clause.
/// It uses chaining methods to provide a fluent interface.
/// It also has some methods that coincide with Linq methods, to provide Linq compatibility.
/// </summary>
/// <typeparam name="T"></typeparam>
public class SortBuilder<T> : IEnumerable<T>, ISortQueryBuilder
{
private string _constantOrderByClause;
private QueryBuilder<T> _baseBuilder;
private Dialect _dialect;
private List<SortColumn<T>> _sortExpressions;
private bool _useAltName;
private TableCollection _tables;
private IDataMapper _db;
private WhereBuilder<T> _whereBuilder;
public SortBuilder()
{
// Used only for unit testing with mock frameworks
}
public SortBuilder(QueryBuilder<T> baseBuilder, IDataMapper db, WhereBuilder<T> whereBuilder, Dialect dialect, TableCollection tables, bool useAltName)
{
_baseBuilder = baseBuilder;
_db = db;
_whereBuilder = whereBuilder;
_dialect = dialect;
_sortExpressions = new List<SortColumn<T>>();
_useAltName = useAltName;
_tables = tables;
}
#region - AndWhere / OrWhere -
public virtual SortBuilder<T> OrWhere(Expression<Func<T, bool>> filterExpression)
{
var orWhere = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, _useAltName, true);
_whereBuilder.Append(orWhere, WhereAppendType.OR);
return this;
}
public virtual SortBuilder<T> OrWhere(string whereClause)
{
var orWhere = new WhereBuilder<T>(whereClause, _useAltName);
_whereBuilder.Append(orWhere, WhereAppendType.OR);
return this;
}
public virtual SortBuilder<T> AndWhere(Expression<Func<T, bool>> filterExpression)
{
var andWhere = new WhereBuilder<T>(_db.Command, _dialect, filterExpression, _tables, _useAltName, true);
_whereBuilder.Append(andWhere, WhereAppendType.AND);
return this;
}
public virtual SortBuilder<T> AndWhere(string whereClause)
{
var andWhere = new WhereBuilder<T>(whereClause, _useAltName);
_whereBuilder.Append(andWhere, WhereAppendType.AND);
return this;
}
#endregion
#region - Order -
internal SortBuilder<T> Order(Type declaringType, string propertyName)
{
_sortExpressions.Add(new SortColumn<T>(declaringType, propertyName, SortDirection.Asc));
return this;
}
internal SortBuilder<T> OrderByDescending(Type declaringType, string propertyName)
{
_sortExpressions.Add(new SortColumn<T>(declaringType, propertyName, SortDirection.Desc));
return this;
}
public virtual SortBuilder<T> OrderBy(string orderByClause)
{
if (string.IsNullOrEmpty(orderByClause))
throw new ArgumentNullException("orderByClause");
if (!orderByClause.ToUpper().Contains("ORDER BY "))
{
orderByClause = orderByClause.Insert(0, " ORDER BY ");
}
_constantOrderByClause = orderByClause;
return this;
}
public virtual SortBuilder<T> OrderBy(Expression<Func<T, object>> sortExpression)
{
_sortExpressions.Add(new SortColumn<T>(sortExpression, SortDirection.Asc));
return this;
}
public virtual SortBuilder<T> OrderByDescending(Expression<Func<T, object>> sortExpression)
{
_sortExpressions.Add(new SortColumn<T>(sortExpression, SortDirection.Desc));
return this;
}
public virtual SortBuilder<T> ThenBy(Expression<Func<T, object>> sortExpression)
{
_sortExpressions.Add(new SortColumn<T>(sortExpression, SortDirection.Asc));
return this;
}
public virtual SortBuilder<T> ThenByDescending(Expression<Func<T, object>> sortExpression)
{
_sortExpressions.Add(new SortColumn<T>(sortExpression, SortDirection.Desc));
return this;
}
#endregion
#region - Paging -
public virtual SortBuilder<T> Take(int count)
{
_baseBuilder.Take(count);
return this;
}
public virtual SortBuilder<T> Skip(int count)
{
_baseBuilder.Skip(count);
return this;
}
public virtual SortBuilder<T> Page(int pageNumber, int pageSize)
{
_baseBuilder.Page(pageNumber, pageSize);
return this;
}
#endregion
#region - GetRowCount -
public virtual int GetRowCount()
{
return _baseBuilder.GetRowCount();
}
#endregion
#region - ToList / ToString / BuildQuery -
public virtual List<T> ToList()
{
return _baseBuilder.ToList();
}
public virtual string BuildQuery()
{
return _baseBuilder.BuildQuery();
}
public virtual string BuildQuery(bool useAltName)
{
if (!string.IsNullOrEmpty(_constantOrderByClause))
{
return _constantOrderByClause;
}
StringBuilder sb = new StringBuilder();
foreach (var sort in _sortExpressions)
{
if (sb.Length > 0)
sb.Append(",");
Table table = _tables.FindTable(sort.DeclaringType);
if (table == null)
{
string msg = string.Format("The property '{0} -> {1}' you are trying to reference in the 'ORDER BY' statement belongs to an entity that has not been joined in your query. To reference this property, you must join the '{0}' entity using the Join method.",
sort.DeclaringType.Name,
sort.PropertyName);
throw new DataMappingException(msg);
}
string columnName = DataHelper.GetColumnName(sort.DeclaringType, sort.PropertyName, useAltName);
sb.Append(_dialect.CreateToken(string.Format("{0}.{1}", table.Alias, columnName)));
if (sort.Direction == SortDirection.Desc)
sb.Append(" DESC");
}
if (sb.Length > 0)
sb.Insert(0, " ORDER BY ");
return sb.ToString();
}
public override string ToString()
{
return BuildQuery(_useAltName);
}
#endregion
#region - Implicit List<T> Operator -
public static implicit operator List<T>(SortBuilder<T> builder)
{
return builder.ToList();
}
#endregion
#region IEnumerable<T> Members
public virtual IEnumerator<T> GetEnumerator()
{
var list = this.ToList();
return list.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
namespace Marr.Data.QGen
{
public class SortColumn<T>
{
public SortColumn(Expression<Func<T, object>> sortExpression, SortDirection direction)
{
MemberExpression me = GetMemberExpression(sortExpression.Body);
DeclaringType = me.Expression.Type;
PropertyName = me.Member.Name;
Direction = direction;
}
public SortColumn(Type declaringType, string propertyName, SortDirection direction)
{
DeclaringType = declaringType;
PropertyName = propertyName;
Direction = direction;
}
public SortDirection Direction { get; private set; }
public Type DeclaringType { get; private set; }
public string PropertyName { get; private set; }
private MemberExpression GetMemberExpression(Expression exp)
{
MemberExpression me = exp as MemberExpression;
if (me == null)
{
var ue = exp as UnaryExpression;
me = ue.Operand as MemberExpression;
}
return me;
}
}
public enum SortDirection
{
Asc,
Desc
}
}

50
Marr.Data/QGen/Table.cs Normal file
View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
namespace Marr.Data.QGen
{
/// <summary>
/// This class represents a table in a query.
/// A table contains corresponding columns.
/// </summary>
public class Table
{
public Table(Type memberType)
: this(memberType, JoinType.None)
{ }
public Table(Type memberType, JoinType joinType)
{
EntityType = memberType;
Name = memberType.GetTableName();
JoinType = joinType;
Columns = MapRepository.Instance.GetColumns(memberType);
}
public bool IsBaseTable
{
get
{
return Alias == "t0";
}
}
public Type EntityType { get; private set; }
public virtual string Name { get; set; }
public JoinType JoinType { get; private set; }
public virtual ColumnMapCollection Columns { get; private set; }
public virtual string Alias { get; set; }
public string JoinClause { get; set; }
}
public enum JoinType
{
None,
Inner,
Left,
Right
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection;
namespace Marr.Data.QGen
{
/// <summary>
/// This class holds a collection of Table objects.
/// </summary>
public class TableCollection : IEnumerable<Table>
{
private List<Table> _tables;
public TableCollection()
{
_tables = new List<Table>();
}
public void Add(Table table)
{
if (this.Any(t => t.EntityType == table.EntityType))
{
// Already exists -- don't add
return;
}
// Create an alias (ex: "t0", "t1", "t2", etc...)
table.Alias = string.Format("t{0}", _tables.Count);
_tables.Add(table);
}
public void ReplaceBaseTable(View view)
{
_tables.RemoveAt(0);
Add(view);
}
/// <summary>
/// Tries to find a table for a given member.
/// </summary>
public Table FindTable(Type declaringType)
{
return this.EnumerateViewsAndTables().Where(t => t.EntityType == declaringType).FirstOrDefault();
}
public Table this[int index]
{
get
{
return _tables[index];
}
}
public int Count
{
get
{
return _tables.Count;
}
}
/// <summary>
/// Recursively enumerates through all tables, including tables embedded in views.
/// </summary>
/// <returns></returns>
public IEnumerable<Table> EnumerateViewsAndTables()
{
foreach (Table table in _tables)
{
if (table is View)
{
foreach (Table viewTable in (table as View))
{
yield return viewTable;
}
}
else
{
yield return table;
}
}
}
public IEnumerator<Table> GetEnumerator()
{
return _tables.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _tables.GetEnumerator();
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using Marr.Data.Mapping;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
public class UpdateQuery : IQuery
{
protected Dialect Dialect { get; set; }
protected string Target { get; set; }
protected ColumnMapCollection Columns { get; set; }
protected DbCommand Command { get; set; }
protected string WhereClause { get; set; }
public UpdateQuery(Dialect dialect, ColumnMapCollection columns, DbCommand command, string target, string whereClause)
{
Dialect = dialect;
Target = target;
Columns = columns;
Command = command;
WhereClause = whereClause;
}
public string Generate()
{
StringBuilder sql = new StringBuilder();
sql.AppendFormat("UPDATE {0} SET ", Dialect.CreateToken(Target));
int startIndex = sql.Length;
foreach (DbParameter p in Command.Parameters)
{
var c = Columns.GetByColumnName(p.ParameterName);
if (c == null)
break; // All SET columns have been added
if (sql.Length > startIndex)
sql.Append(",");
if (!c.ColumnInfo.IsAutoIncrement)
{
sql.AppendFormat("{0}={1}{2}", Dialect.CreateToken(c.ColumnInfo.Name), Command.ParameterPrefix(), p.ParameterName);
}
}
sql.AppendFormat(" {0}", WhereClause);
return sql.ToString();
}
}
}

View File

@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
using System.Linq.Expressions;
namespace Marr.Data.QGen
{
public class UpdateQueryBuilder<T>
{
private DataMapper _db;
private string _tableName;
private T _entity;
private MappingHelper _mappingHelper;
private ColumnMapCollection _mappings;
private SqlModes _previousSqlMode;
private bool _generateQuery = true;
private TableCollection _tables;
private Expression<Func<T, bool>> _filterExpression;
private Dialects.Dialect _dialect;
private ColumnMapCollection _columnsToUpdate;
public UpdateQueryBuilder()
{
// Used only for unit testing with mock frameworks
}
public UpdateQueryBuilder(DataMapper db)
{
_db = db;
_tableName = MapRepository.Instance.GetTableName(typeof(T));
_tables = new TableCollection();
_tables.Add(new Table(typeof(T)));
_previousSqlMode = _db.SqlMode;
_mappingHelper = new MappingHelper(_db);
_mappings = MapRepository.Instance.GetColumns(typeof(T));
_dialect = QueryFactory.CreateDialect(_db);
}
public virtual UpdateQueryBuilder<T> TableName(string tableName)
{
_tableName = tableName;
return this;
}
public virtual UpdateQueryBuilder<T> QueryText(string queryText)
{
_generateQuery = false;
_db.Command.CommandText = queryText;
return this;
}
public virtual UpdateQueryBuilder<T> Entity(T entity)
{
_entity = entity;
return this;
}
public virtual UpdateQueryBuilder<T> Where(Expression<Func<T, bool>> filterExpression)
{
_filterExpression = filterExpression;
return this;
}
public virtual UpdateQueryBuilder<T> ColumnsIncluding(params Expression<Func<T, object>>[] properties)
{
List<string> columnList = new List<string>();
foreach (var column in properties)
{
columnList.Add(column.GetMemberName());
}
return ColumnsIncluding(columnList.ToArray());
}
public virtual UpdateQueryBuilder<T> ColumnsIncluding(params string[] properties)
{
_columnsToUpdate = new ColumnMapCollection();
foreach (string propertyName in properties)
{
_columnsToUpdate.Add(_mappings.GetByFieldName(propertyName));
}
return this;
}
public virtual UpdateQueryBuilder<T> ColumnsExcluding(params Expression<Func<T, object>>[] properties)
{
List<string> columnList = new List<string>();
foreach (var column in properties)
{
columnList.Add(column.GetMemberName());
}
return ColumnsExcluding(columnList.ToArray());
}
public virtual UpdateQueryBuilder<T> ColumnsExcluding(params string[] properties)
{
_columnsToUpdate = new ColumnMapCollection();
_columnsToUpdate.AddRange(_mappings);
foreach (string propertyName in properties)
{
_columnsToUpdate.RemoveAll(c => c.FieldName == propertyName);
}
return this;
}
public virtual string BuildQuery()
{
if (_entity == null)
throw new ArgumentNullException("You must specify an entity to update.");
// Override SqlMode since we know this will be a text query
_db.SqlMode = SqlModes.Text;
var columnsToUpdate = _columnsToUpdate ?? _mappings;
_mappingHelper.CreateParameters<T>(_entity, columnsToUpdate, _generateQuery);
string where = string.Empty;
if (_filterExpression != null)
{
var whereBuilder = new WhereBuilder<T>(_db.Command, _dialect, _filterExpression, _tables, false, false);
where = whereBuilder.ToString();
}
IQuery query = QueryFactory.CreateUpdateQuery(columnsToUpdate, _db, _tableName, where);
_db.Command.CommandText = query.Generate();
return _db.Command.CommandText;
}
public virtual int Execute()
{
if (_generateQuery)
{
BuildQuery();
}
else
{
_mappingHelper.CreateParameters<T>(_entity, _mappings, _generateQuery);
}
int rowsAffected = 0;
try
{
_db.OpenConnection();
rowsAffected = _db.Command.ExecuteNonQuery();
_mappingHelper.SetOutputValues<T>(_entity, _mappings.OutputFields);
}
finally
{
_db.CloseConnection();
}
if (_generateQuery)
{
// Return to previous sql mode
_db.SqlMode = _previousSqlMode;
}
return rowsAffected;
}
}
}

91
Marr.Data/QGen/View.cs Normal file
View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
namespace Marr.Data.QGen
{
/// <summary>
/// This class represents a View. A view can hold multiple tables (and their columns).
/// </summary>
public class View : Table, IEnumerable<Table>
{
private string _viewName;
private Table[] _tables;
private Mapping.ColumnMapCollection _columns;
public View(string viewName, Table[] tables)
: base(tables[0].EntityType, JoinType.None)
{
_viewName = viewName;
_tables = tables;
}
public Table[] Tables
{
get { return _tables; }
}
public override string Name
{
get
{
return _viewName;
}
set
{
_viewName = value;
}
}
public override string Alias
{
get
{
return base.Alias;
}
set
{
base.Alias = value;
// Sync view tables
foreach (Table table in _tables)
{
table.Alias = value;
}
}
}
/// <summary>
/// Gets all the columns from all the tables included in the view.
/// </summary>
public override Mapping.ColumnMapCollection Columns
{
get
{
if (_columns == null)
{
var allColumns = _tables.SelectMany(t => t.Columns);
_columns = new ColumnMapCollection();
_columns.AddRange(allColumns);
}
return _columns;
}
}
public IEnumerator<Table> GetEnumerator()
{
foreach (Table table in _tables)
{
yield return table;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
}

View File

@@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Marr.Data;
using Marr.Data.Mapping;
using System.Data.Common;
using Marr.Data.Parameters;
using System.Reflection;
using Marr.Data.QGen.Dialects;
namespace Marr.Data.QGen
{
/// <summary>
/// This class utilizes the ExpressionVisitor base class, and it is responsible for creating the "WHERE" clause.
/// It builds a protected StringBuilder class whose output is created when the ToString method is called.
/// It also has some methods that coincide with Linq methods, to provide Linq compatibility.
/// </summary>
/// <typeparam name="T"></typeparam>
public class WhereBuilder<T> : ExpressionVisitor
{
private string _constantWhereClause;
private MapRepository _repos;
private DbCommand _command;
private string _paramPrefix;
private bool isLeftSide = true;
protected bool _useAltName;
protected Dialect _dialect;
protected StringBuilder _sb;
protected TableCollection _tables;
protected bool _tablePrefix;
public WhereBuilder(string whereClause, bool useAltName)
{
_constantWhereClause = whereClause;
_useAltName = useAltName;
}
public WhereBuilder(DbCommand command, Dialect dialect, Expression filter, TableCollection tables, bool useAltName, bool tablePrefix)
{
_repos = MapRepository.Instance;
_command = command;
_dialect = dialect;
_paramPrefix = command.ParameterPrefix();
_sb = new StringBuilder();
_useAltName = useAltName;
_tables = tables;
_tablePrefix = tablePrefix;
if (filter != null)
{
_sb.AppendFormat("{0} ", PrefixText);
base.Visit(filter);
}
}
protected virtual string PrefixText
{
get
{
return "WHERE";
}
}
protected override Expression VisitBinary(BinaryExpression expression)
{
_sb.Append("(");
isLeftSide = true;
Visit(expression.Left);
_sb.AppendFormat(" {0} ", Decode(expression.NodeType));
isLeftSide = false;
Visit(expression.Right);
_sb.Append(")");
return expression;
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
string method = (expression as System.Linq.Expressions.MethodCallExpression).Method.Name;
switch (method)
{
case "Contains":
Write_Contains(expression);
break;
case "StartsWith":
Write_StartsWith(expression);
break;
case "EndsWith":
Write_EndsWith(expression);
break;
default:
string msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
throw new NotImplementedException(msg);
}
return expression;
}
protected override Expression VisitMemberAccess(MemberExpression expression)
{
if (isLeftSide)
{
string fqColumn = GetFullyQualifiedColumnName(expression.Member, expression.Expression.Type);
_sb.Append(fqColumn);
}
else
{
// Add parameter to Command.Parameters
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
_sb.Append(paramName);
object value = GetRightValue(expression);
new ParameterChainMethods(_command, paramName, value);
}
return expression;
}
protected override Expression VisitConstant(ConstantExpression expression)
{
// Add parameter to Command.Parameters
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
_sb.Append(paramName);
var parameter = new ParameterChainMethods(_command, paramName, expression.Value).Parameter;
return expression;
}
private object GetRightValue(Expression rightExpression)
{
object rightValue = null;
var right = rightExpression as ConstantExpression;
if (right == null) // Value is not directly passed in as a constant
{
var rightMemberExp = (rightExpression as MemberExpression);
var parentMemberExpression = rightMemberExp.Expression as MemberExpression;
if (parentMemberExpression != null) // Value is passed in as a property on a parent entity
{
string entityName = (rightMemberExp.Expression as MemberExpression).Member.Name;
var container = ((rightMemberExp.Expression as MemberExpression).Expression as ConstantExpression).Value;
var entity = _repos.ReflectionStrategy.GetFieldValue(container, entityName);
rightValue = _repos.ReflectionStrategy.GetFieldValue(entity, rightMemberExp.Member.Name);
}
else // Value is passed in as a variable
{
var parent = (rightMemberExp.Expression as ConstantExpression).Value;
rightValue = _repos.ReflectionStrategy.GetFieldValue(parent, rightMemberExp.Member.Name);
}
}
else // Value is passed in directly as a constant
{
rightValue = right.Value;
}
return rightValue;
}
protected string GetFullyQualifiedColumnName(MemberInfo member, Type declaringType)
{
if (_tablePrefix)
{
Table table = _tables.FindTable(declaringType);
if (table == null)
{
string msg = string.Format("The property '{0} -> {1}' you are trying to reference in the 'WHERE' statement belongs to an entity that has not been joined in your query. To reference this property, you must join the '{0}' entity using the Join method.",
declaringType,
member.Name);
throw new DataMappingException(msg);
}
string columnName = DataHelper.GetColumnName(declaringType, member.Name, _useAltName);
return _dialect.CreateToken(string.Format("{0}.{1}", table.Alias, columnName));
}
else
{
string columnName = DataHelper.GetColumnName(declaringType, member.Name, _useAltName);
return _dialect.CreateToken(columnName);
}
}
private string Decode(ExpressionType expType)
{
switch (expType)
{
case ExpressionType.AndAlso: return "AND";
case ExpressionType.And: return "AND";
case ExpressionType.Equal: return "=";
case ExpressionType.GreaterThan: return ">";
case ExpressionType.GreaterThanOrEqual: return ">=";
case ExpressionType.LessThan: return "<";
case ExpressionType.LessThanOrEqual: return "<=";
case ExpressionType.NotEqual: return "<>";
case ExpressionType.OrElse: return "OR";
case ExpressionType.Or: return "OR";
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expType.ToString()));
}
}
private void Write_Contains(MethodCallExpression body)
{
// Add parameter to Command.Parameters
object value = GetRightValue(body.Arguments[0]);
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
var parameter = new ParameterChainMethods(_command, paramName, value).Parameter;
MemberExpression memberExp = (body.Object as MemberExpression);
string fqColumn = GetFullyQualifiedColumnName(memberExp.Member, memberExp.Expression.Type);
_sb.AppendFormat("({0} LIKE '%' + {1} + '%')", fqColumn, paramName);
}
private void Write_StartsWith(MethodCallExpression body)
{
// Add parameter to Command.Parameters
object value = GetRightValue(body.Arguments[0]);
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
var parameter = new ParameterChainMethods(_command, paramName, value).Parameter;
MemberExpression memberExp = (body.Object as MemberExpression);
string fqColumn = GetFullyQualifiedColumnName(memberExp.Member, memberExp.Expression.Type);
_sb.AppendFormat("({0} LIKE {1} + '%')", fqColumn, paramName);
}
private void Write_EndsWith(MethodCallExpression body)
{
// Add parameter to Command.Parameters
object value = GetRightValue(body.Arguments[0]);
string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
var parameter = new ParameterChainMethods(_command, paramName, value).Parameter;
MemberExpression memberExp = (body.Object as MemberExpression);
string fqColumn = GetFullyQualifiedColumnName(memberExp.Member, memberExp.Expression.Type);
_sb.AppendFormat("({0} LIKE '%' + {1})", fqColumn, paramName);
}
/// <summary>
/// Appends the current where clause with another where clause.
/// </summary>
/// <param name="where">The second where clause that is being appended.</param>
/// <param name="appendType">AND / OR</param>
internal void Append(WhereBuilder<T> where, WhereAppendType appendType)
{
_constantWhereClause = string.Format("{0} {1} {2}",
this.ToString(),
appendType.ToString(),
where.ToString().Replace("WHERE ", string.Empty));
}
public override string ToString()
{
if (string.IsNullOrEmpty(_constantWhereClause))
{
return _sb.ToString();
}
else
{
return _constantWhereClause;
}
}
}
internal enum WhereAppendType
{
AND,
OR
}
}