PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/DataAccess.Repository/Memory/MemoryQueryProvider.cs

http://lsda.codeplex.com
C# | 311 lines | 137 code | 48 blank | 126 comment | 21 complexity | e958a4b50ef910349f50fcc6f5915d78 MD5 | raw file
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright file="MemoryQueryProvider.cs" company="Logic Software">
  3. // (c) Logic Software
  4. // </copyright>
  5. // <summary>
  6. //
  7. // </summary>
  8. // --------------------------------------------------------------------------------------------------------------------
  9. namespace LogicSoftware.DataAccess.Repository.Memory
  10. {
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Data.Linq.Mapping;
  14. using System.Linq;
  15. using System.Linq.Expressions;
  16. using System.Reflection;
  17. using Infrastructure.Helpers;
  18. /// <summary>
  19. /// Query provider for memory repository results
  20. /// </summary>
  21. /// <typeparam name="T">
  22. /// Entity Type
  23. /// </typeparam>
  24. internal class MemoryQueryProvider<T> : ExpressionVisitor, IQueryProvider
  25. {
  26. #region Constants and Fields
  27. /// <summary>
  28. /// Lock object for Execute method serialization
  29. /// </summary>
  30. private readonly object executionLockObject = new object();
  31. #endregion
  32. #region Constructors and Destructors
  33. /// <summary>
  34. /// Initializes a new instance of the <see cref="MemoryQueryProvider&lt;T&gt;"/> class.
  35. /// </summary>
  36. /// <param name="innerQuery">
  37. /// The inner query.
  38. /// </param>
  39. /// <param name="repository">
  40. /// The repository.
  41. /// </param>
  42. public MemoryQueryProvider(IQueryable<T> innerQuery, MemoryRepository repository)
  43. {
  44. this.InnerQuery = innerQuery;
  45. this.Repository = repository;
  46. }
  47. #endregion
  48. #region Properties
  49. /// <summary>
  50. /// Gets or sets the indexes cache.
  51. /// </summary>
  52. /// <value>The indexes cache.</value>
  53. protected object IndexesCache { get; set; }
  54. /// <summary>
  55. /// Gets or sets the inner query.
  56. /// </summary>
  57. /// <value>The inner query.</value>
  58. private IQueryable<T> InnerQuery { get; set; }
  59. /// <summary>
  60. /// Gets or sets the repository.
  61. /// </summary>
  62. /// <value>The repository.</value>
  63. private MemoryRepository Repository { get; set; }
  64. #endregion
  65. #region Implemented Interfaces (Methods)
  66. #region IQueryProvider methods
  67. /// <summary>
  68. /// Constructs an <see cref="T:System.Linq.IQueryable"/> object that can evaluate the query represented by a specified expression tree.
  69. /// </summary>
  70. /// <param name="expression">
  71. /// An expression tree that represents a LINQ query.
  72. /// </param>
  73. /// <returns>
  74. /// An <see cref="T:System.Linq.IQueryable"/> that can evaluate the query represented by the specified expression tree.
  75. /// </returns>
  76. /// <exception cref="NotImplementedException">
  77. /// </exception>
  78. public IQueryable CreateQuery(Expression expression)
  79. {
  80. if (expression == null)
  81. {
  82. throw new ArgumentNullException("expression");
  83. }
  84. Type elementType = TypeSystem.GetElementType(expression.Type);
  85. if (!typeof(IQueryable<>).MakeGenericType(elementType).IsAssignableFrom(expression.Type))
  86. {
  87. throw new ArgumentException("Invalid type expression", "expression");
  88. }
  89. return (IQueryable)Activator.CreateInstance(typeof(MemoryQueryable<>).MakeGenericType(elementType), new object[] { this, expression });
  90. }
  91. /// <summary>
  92. /// Creates the query.
  93. /// </summary>
  94. /// <typeparam name="TElement">
  95. /// The type of the element.
  96. /// </typeparam>
  97. /// <param name="expression">
  98. /// The expression.
  99. /// </param>
  100. /// <returns>
  101. /// The query.
  102. /// </returns>
  103. /// <exception cref="NotImplementedException">
  104. /// </exception>
  105. public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
  106. {
  107. return new MemoryQueryable<TElement>(this, expression);
  108. }
  109. /// <summary>
  110. /// Executes the query represented by a specified expression tree.
  111. /// </summary>
  112. /// <param name="expression">
  113. /// An expression tree that represents a LINQ query.
  114. /// </param>
  115. /// <returns>
  116. /// The value that results from executing the specified query.
  117. /// </returns>
  118. /// <exception cref="NotImplementedException">
  119. /// </exception>
  120. public object Execute(Expression expression)
  121. {
  122. return this.Execute<object>(expression);
  123. }
  124. /// <summary>
  125. /// Executes the specified expression.
  126. /// </summary>
  127. /// <typeparam name="TResult">
  128. /// The type of the result.
  129. /// </typeparam>
  130. /// <param name="expression">
  131. /// The expression.
  132. /// </param>
  133. /// <returns>
  134. /// Execution result
  135. /// </returns>
  136. /// <exception cref="NotImplementedException">
  137. /// </exception>
  138. public TResult Execute<TResult>(Expression expression)
  139. {
  140. // lock to avoid cross-thread cache usage
  141. lock (this.executionLockObject)
  142. {
  143. try
  144. {
  145. this.IndexesCache = new QueryIndexesCache(this.Repository);
  146. Expression convertedExpression = this.Visit(expression);
  147. var innerProvider = this.InnerQuery.Provider;
  148. return innerProvider.Execute<TResult>(convertedExpression);
  149. }
  150. finally
  151. {
  152. this.IndexesCache = null;
  153. }
  154. }
  155. }
  156. #endregion
  157. #endregion
  158. #region Methods
  159. /// <summary>
  160. /// Analyzes the constant expression provided as parameter and
  161. /// returns an appropiated constant expression.
  162. /// </summary>
  163. /// <param name="constant">
  164. /// The constant expression to analyze.
  165. /// </param>
  166. /// <returns>
  167. /// A System.Linq.Expressions.Expression.
  168. /// </returns>
  169. protected override Expression VisitConstant(ConstantExpression constant)
  170. {
  171. if (constant == null)
  172. {
  173. throw new ArgumentNullException("constant");
  174. }
  175. if (constant.Value is MemoryQueryable<T>)
  176. {
  177. return Expression.Constant(this.InnerQuery);
  178. }
  179. return base.VisitConstant(constant);
  180. }
  181. /// <summary>
  182. /// Analyzes the member access expression provided as parameter and
  183. /// returns an appropiated member access.
  184. /// </summary>
  185. /// <param name="member">
  186. /// The member access to analyze.
  187. /// </param>
  188. /// <returns>
  189. /// A System.Linq.Expressions.Expression.
  190. /// </returns>
  191. protected override Expression VisitMember(MemberExpression member)
  192. {
  193. if (member == null)
  194. {
  195. throw new ArgumentNullException("member");
  196. }
  197. Type memberType = member.Type;
  198. if (member.Expression != null)
  199. {
  200. Type objectType = member.Expression.Type;
  201. MetaModel model = this.Repository.GetModel();
  202. MetaType memberMetaType = model.GetMetaType(memberType);
  203. MetaType objectMetaType = model.GetMetaType(objectType);
  204. if (objectMetaType.Table != null)
  205. {
  206. if (memberMetaType.Table != null)
  207. {
  208. // old implementation
  209. MethodInfo selector = this.IndexesCache.GetType().GetMethod("GetSingleByPropertyValue", BindingFlags.Instance | BindingFlags.NonPublic)
  210. .MakeGenericMethod(memberType);
  211. return this.CreateExplicitJoinMethodCall(member, objectMetaType, selector);
  212. }
  213. else
  214. {
  215. // replace with join on associated table [grouped by fk], to grouping key
  216. var listInterface = memberType.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)).SingleOrDefault();
  217. if (listInterface != null)
  218. {
  219. var listElementType = listInterface.GetGenericArguments()[0];
  220. var listElementMetaType = model.GetMetaType(listElementType);
  221. if (listElementMetaType.Table != null)
  222. {
  223. MethodInfo selector = this.IndexesCache.GetType().GetMethod("GetAllByPropertyValue", BindingFlags.Instance | BindingFlags.NonPublic)
  224. .MakeGenericMethod(listElementType);
  225. return this.CreateExplicitJoinMethodCall(member, objectMetaType, selector);
  226. }
  227. }
  228. }
  229. }
  230. }
  231. return base.VisitMember(member);
  232. }
  233. /// <summary>
  234. /// Creates the explicit join method call.
  235. /// </summary>
  236. /// <param name="member">
  237. /// The member.
  238. /// </param>
  239. /// <param name="objectMetaType">
  240. /// Type of the object meta.
  241. /// </param>
  242. /// <param name="selector">
  243. /// The selector.
  244. /// </param>
  245. /// <returns>
  246. /// Explicit join method call expression
  247. /// </returns>
  248. private Expression CreateExplicitJoinMethodCall(MemberExpression member, MetaType objectMetaType, MethodInfo selector)
  249. {
  250. MetaAssociation association = objectMetaType.Associations.Where(a => a.ThisMember.Member == member.Member).SingleOrDefault();
  251. Expression visitedMemberExpression = this.Visit(member.Expression);
  252. var thisSideKeyExpression =
  253. Expression.Convert(
  254. Expression.Property(visitedMemberExpression, association.ThisKey.Single().Member as PropertyInfo),
  255. typeof(object));
  256. var thisPropertyExpression = Expression.Constant(association.OtherKey.Single().Member, typeof(PropertyInfo));
  257. var repositoryConst = Expression.Constant(this.IndexesCache);
  258. var explicitJoinMethodCallExpression = Expression.Call(repositoryConst, selector, thisPropertyExpression, thisSideKeyExpression);
  259. return explicitJoinMethodCallExpression;
  260. }
  261. #endregion
  262. }
  263. }