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 { /// /// 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 Dialects.Dialect _dialect; private DataMapper _db; private TableCollection _tables; private WhereBuilder _whereBuilder; private SortBuilder _sortBuilder; private bool _useAltName = false; private bool _isGraph = false; private string _queryText; private List _childrenToLoad; private bool _enablePaging = false; private int _skip; private int _take; private SortBuilder SortBuilder { get { // Lazy load if (_sortBuilder == null) _sortBuilder = new SortBuilder(this, _db, _whereBuilder, _dialect, _tables, _useAltName); 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, Dialects.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"); _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; } /// /// 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"); _useAltName = false; // 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) { _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; _useAltName = 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; 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; } /// /// 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; BuildQueryOrAppendClauses(); if (_isGraph) { _results = (List)_db.QueryToGraph(_queryText, EntGraph, _childrenToLoad); } else { _results = (List)_db.Query(_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 entitiesToLoad) { // If QueryToGraph and no child load entities are specified, load all children bool loadAllChildren = _useAltName && entitiesToLoad == null; // If Query 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(QueryBuilder builder) { return builder.ToList(); } #endregion #region - Linq Support - public virtual SortBuilder Where(Expression> filterExpression) { _whereBuilder = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, _useAltName, true); return SortBuilder; } public virtual SortBuilder Where(Expression> filterExpression) { _whereBuilder = new WhereBuilder(_db.Command, _dialect, filterExpression, _tables, false, true); 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 "); } _whereBuilder = new WhereBuilder(whereClause, _useAltName); return SortBuilder; } public virtual SortBuilder OrderBy(Expression> sortExpression) { SortBuilder.OrderBy(sortExpression); return SortBuilder; } public virtual SortBuilder ThenBy(Expression> sortExpression) { SortBuilder.OrderBy(sortExpression); 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 System.Linq.Expressions.Expression Visit(System.Linq.Expressions.Expression expression) { return base.Visit(expression); } /// /// Handles Where. /// /// /// protected override System.Linq.Expressions.Expression VisitLamda(System.Linq.Expressions.LambdaExpression lambdaExpression) { _sortBuilder = this.Where(lambdaExpression as Expression>); return base.VisitLamda(lambdaExpression); } /// /// Handles OrderBy. /// /// /// 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 Join(JoinType joinType, Expression>> rightEntity, Expression> filterExpression) { MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member; return this.Join(joinType, rightMember, filterExpression); } public virtual QueryBuilder Join(JoinType joinType, Expression> rightEntity, Expression> filterExpression) { MemberInfo rightMember = (rightEntity.Body as MemberExpression).Member; return this.Join(joinType, rightMember, filterExpression); } public virtual QueryBuilder Join(JoinType joinType, MemberInfo rightMember, Expression> 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(_db.Command, _dialect, filterExpression, _tables); table.JoinClause = builder.ToString(); return this; } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { var list = this.ToList(); return list.GetEnumerator(); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { var list = this.ToList(); return list.GetEnumerator(); } #endregion } }