using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Marr.Data.Mapping; using System.Data.Common; using System.Collections; using Marr.Data.QGen.Dialects; namespace Marr.Data.QGen { /// /// This class is responsible for building a select query. /// It uses chaining methods to provide a fluent interface for creating select queries. /// /// public class QueryBuilder : ExpressionVisitor, IEnumerable, IQueryBuilder { #region - Private Members - private DataMapper _db; private Dialect _dialect; private TableCollection _tables; private WhereBuilder _whereBuilder; private SortBuilder _sortBuilder; private bool _isGraph = false; private bool _isFromView = false; private bool _isFromTable = false; private bool _isJoin = false; private bool _isManualQuery = false; private bool _enablePaging = false; private int _skip; private int _take; private string _queryText; private List _childrenToLoad; private SortBuilder SortBuilder { get { // Lazy load if (_sortBuilder == null) { bool useAltNames = _isFromView || _isGraph || _isJoin; _sortBuilder = new SortBuilder(this, _db, _whereBuilder, _dialect, _tables, useAltNames); } return _sortBuilder; } } private List _results = new List(); 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, Dialect dialect) { _db = db; _dialect = dialect; _tables = new TableCollection(); _tables.Add(new Table(typeof(T))); _childrenToLoad = new List(); } #endregion #region - Fluent Methods - /// /// Overrides the base table name that will be used in the query. /// [Obsolete("This method is obsolete. Use either the FromTable or FromView method instead.", true)] public virtual QueryBuilder From(string tableName) { return FromView(tableName); } /// /// Overrides the base view name that will be used in the query. /// Will try to use the mapped "AltName" values when loading the columns. /// public virtual QueryBuilder FromView(string viewName) { if (string.IsNullOrEmpty(viewName)) throw new ArgumentNullException("view"); _isFromView = true; // Replace the base table with a view with tables if (_tables[0] is View) { (_tables[0] as View).Name = viewName; } else { View view = new View(viewName, _tables.ToArray()); _tables.ReplaceBaseTable(view); } return this; } /// /// 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. /// public virtual QueryBuilder FromTable(string table) { if (string.IsNullOrEmpty(table)) throw new ArgumentNullException("view"); _isFromTable = true; // Override the base table name _tables[0].Name = table; return this; } /// /// Allows you to manually specify the query text. /// public virtual QueryBuilder QueryText(string queryText) { _isManualQuery = true; _queryText = queryText; return this; } /// /// 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. /// /// A list of related child entites to load (passed in as properties / lambda expressions). public virtual QueryBuilder Graph(params Expression>[] 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; return this; } public virtual QueryBuilder Page(int pageNumber, int pageSize) { _enablePaging = true; _skip = (pageNumber - 1) * pageSize; _take = pageSize; return this; } private string[] ParseChildrenToLoad(Expression>[] childrenToLoad) { List entitiesToLoad = new List(); // 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(); } /// /// Allows you to interact with the DbDataReader to manually load entities. /// /// An action that takes a DbDataReader. public virtual void DataReader(Action 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; bool useAltNames = _isFromView || _isGraph || _isJoin; IQuery query = QueryFactory.CreateRowCountSelectQuery(_tables, _db, where, SortBuilder, useAltNames); string queryText = query.Generate(); _db.SqlMode = SqlModes.Text; int count = Convert.ToInt32(_db.ExecuteScalar(queryText)); _db.SqlMode = previousSqlMode; return count; } /// /// Executes the query and returns a list of results. /// /// A list of query results of type T. public virtual List ToList() { SqlModes previousSqlMode = _db.SqlMode; ValidateQuery(); BuildQueryOrAppendClauses(); if (_isGraph || _isJoin) { _results = (List)_db.QueryToGraph(_queryText, EntGraph, _childrenToLoad); } else { _results = (List)_db.Query(_queryText, _results, _isFromView); } // Return to previous sql mode _db.SqlMode = previousSqlMode; return _results; } private void ValidateQuery() { if (_isManualQuery && _isFromView) throw new InvalidOperationException("Cannot use FromView in conjunction with QueryText"); if (_isManualQuery && _isFromTable) throw new InvalidOperationException("Cannot use FromTable in conjunction with QueryText"); if (_isManualQuery && _isJoin) throw new InvalidOperationException("Cannot use Join in conjuntion with QueryText"); if (_isManualQuery && _enablePaging) throw new InvalidOperationException("Cannot use Page, Skip or Take in conjunction with QueryText"); if (_isJoin && _isFromView) throw new InvalidOperationException("Cannot use FromView in conjunction with Join"); if (_isJoin && _isFromTable) throw new InvalidOperationException("Cannot use FromView in conjunction with Join"); if (_isJoin && _isGraph) throw new InvalidOperationException("Cannot use Graph in conjunction with Join"); if (_isFromView && _isFromTable) throw new InvalidOperationException("Cannot use FromView in conjunction with FromTable"); } 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; bool useAltNames = _isFromView || _isGraph || _isJoin; IQuery query = null; if (_enablePaging) { query = QueryFactory.CreatePagingSelectQuery(_tables, _db, where, SortBuilder, useAltNames, _skip, _take); } else { query = QueryFactory.CreateSelectQuery(_tables, _db, where, SortBuilder, useAltNames); } _queryText = query.Generate(); return _queryText; } #endregion #region - Helper Methods - private ColumnMapCollection GetColumns(IEnumerable entitiesToLoad) { // If QueryToGraph and no child load entities are specified, load all children bool useAltNames = _isFromView || _isGraph || _isJoin; bool loadAllChildren = useAltNames && entitiesToLoad == null; // If Query if (!useAltNames) { 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(QueryBuilder builder) { return builder.ToList(); } #endregion #region - Linq Support - public virtual SortBuilder Where(Expression> filterExpression) { bool useAltNames = _isFromView || _isGraph; bool addTablePrefixToColumns = true; _whereBuilder = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, useAltNames, addTablePrefixToColumns); return SortBuilder; } public virtual SortBuilder Where(Expression> filterExpression) { bool useAltNames = _isFromView || _isGraph; bool addTablePrefixToColumns = true; _whereBuilder = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, useAltNames, addTablePrefixToColumns); return SortBuilder; } public virtual SortBuilder Where(string whereClause) { if (string.IsNullOrEmpty(whereClause)) throw new ArgumentNullException("whereClause"); if (!whereClause.ToUpper().Contains("WHERE ")) { whereClause = whereClause.Insert(0, " WHERE "); } bool useAltNames = _isFromView || _isGraph || _isJoin; _whereBuilder = new WhereBuilder(whereClause, useAltNames); return SortBuilder; } public virtual SortBuilder OrderBy(Expression> sortExpression) { SortBuilder.OrderBy(sortExpression); return SortBuilder; } public virtual SortBuilder OrderBy(Expression> sortExpression, SortDirection sortDirection) { SortBuilder.OrderBy(sortExpression, sortDirection); return SortBuilder; } public virtual SortBuilder ThenBy(Expression> sortExpression) { SortBuilder.OrderBy(sortExpression); return SortBuilder; } public virtual SortBuilder ThenBy(Expression> sortExpression, SortDirection sortDirection) { SortBuilder.OrderBy(sortExpression, sortDirection); return SortBuilder; } public virtual SortBuilder OrderByDescending(Expression> sortExpression) { SortBuilder.OrderByDescending(sortExpression); return SortBuilder; } public virtual SortBuilder ThenByDescending(Expression> sortExpression) { SortBuilder.OrderByDescending(sortExpression); return SortBuilder; } public virtual SortBuilder 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 Take(int count) { _enablePaging = true; _take = count; return this; } public virtual QueryBuilder Skip(int count) { _enablePaging = true; _skip = count; return this; } /// /// Handles all. /// /// /// protected override Expression Visit(Expression expression) { return base.Visit(expression); } /// /// Handles Where. /// /// /// protected override Expression VisitLamda(LambdaExpression lambdaExpression) { _sortBuilder = Where(lambdaExpression as Expression>); return base.VisitLamda(lambdaExpression); } /// /// Handles OrderBy. /// /// /// protected override Expression VisitMethodCall(MethodCallExpression expression) { if (expression.Method.Name == "OrderBy" || expression.Method.Name == "ThenBy") { var memberExp = ((expression.Arguments[1] as UnaryExpression).Operand as LambdaExpression).Body as 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 LambdaExpression).Body as MemberExpression; _sortBuilder.OrderByDescending(memberExp.Expression.Type, memberExp.Member.Name); } return base.VisitMethodCall(expression); } public virtual QueryBuilder Join(JoinType joinType, Expression>> rightEntity, Expression> filterExpression) { _isJoin = true; MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member; return Join(joinType, rightMember, filterExpression); } public virtual QueryBuilder Join(JoinType joinType, Expression> rightEntity, Expression> filterExpression) { _isJoin = true; MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member; return Join(joinType, rightMember, filterExpression); } public virtual QueryBuilder Join(JoinType joinType, Expression>> rightEntity, Expression> filterExpression) { _isJoin = true; MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member; foreach (var item in EntGraph) { if (item.EntityType == typeof(TLeft)) { var relationship = item.Relationships.Single(v => v.Member == rightMember); item.AddLazyRelationship(relationship); } } return Join(joinType, rightMember, filterExpression); } public virtual QueryBuilder Join(JoinType joinType, Expression>>> rightEntity, Expression> filterExpression) { _isJoin = true; MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member; foreach (var item in EntGraph) { if (item.EntityType == typeof(TLeft)) { var relationship = item.Relationships.Single(v => v.Member == rightMember); item.AddLazyRelationship(relationship); } } return Join(joinType, rightMember, filterExpression); } public virtual QueryBuilder Join(JoinType joinType, MemberInfo rightMember, Expression> filterExpression) { _isJoin = true; if (!_childrenToLoad.ContainsMember(rightMember)) _childrenToLoad.Add(rightMember); Table table = new Table(typeof(TRight), joinType); _tables.Add(table); var builder = new JoinBuilder(_db.Command, _dialect, filterExpression, _tables); table.JoinClause = builder.ToString(); return this; } public virtual bool Any(Expression> filterExpression) { bool useAltNames = _isFromView || _isGraph; bool addTablePrefixToColumns = true; _whereBuilder = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, useAltNames, addTablePrefixToColumns); return Any(); } public virtual bool Any() { SqlModes previousSqlMode = _db.SqlMode; // Generate a row count query string where = _whereBuilder != null ? _whereBuilder.ToString() : string.Empty; bool useAltNames = _isFromView || _isGraph || _isJoin; IQuery query = QueryFactory.CreateRowCountSelectQuery(_tables, _db, where, SortBuilder, useAltNames); string queryText = query.Generate(); _db.SqlMode = SqlModes.Text; int count = Convert.ToInt32(_db.ExecuteScalar(queryText)); _db.SqlMode = previousSqlMode; return count > 0; } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { var list = ToList(); return list.GetEnumerator(); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { var list = ToList(); return list.GetEnumerator(); } #endregion } }