00001 using System;
00002 using System.Collections.Generic;
00003 using System.Diagnostics;
00004 using System.Linq;
00005 using System.Linq.Expressions;
00006 using System.Reflection;
00007 using N2.Details;
00008
00009 namespace N2.Linq
00010 {
00014 public class ContentQueryProvider : IQueryProvider
00015 {
00016 static MethodInfo anyMethodInfo = typeof(Enumerable).GetMethods().First(m => m.Name == "Any" && m.GetParameters().Length == 2).GetGenericMethodDefinition();
00017 static MethodInfo ofTypeMethodInfo = typeof(Enumerable).GetMethod("OfType").GetGenericMethodDefinition();
00018
00019 readonly IQueryProvider queryProvider;
00020 internal Dictionary<Expression, bool> WhereDetailExpressions = new Dictionary<Expression, bool>();
00021
00022 public ContentQueryProvider(IQueryProvider queryProvider)
00023 {
00024 this.queryProvider = queryProvider;
00025 }
00026
00027 #region IQueryProvider Members
00028
00029 public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
00030 {
00031 MethodCallExpression mcExpression = expression as MethodCallExpression;
00032
00033 if (mcExpression != null && mcExpression.NodeType == ExpressionType.Call && mcExpression.Arguments.Count > 1 && mcExpression.Arguments[1].NodeType == ExpressionType.Quote)
00034 {
00035 UnaryExpression ue = mcExpression.Arguments[1] as UnaryExpression;
00036
00037 if (ue != null && ue.Operand.NodeType == ExpressionType.Lambda)
00038 {
00039 var expressionToReplace = ue.Operand as Expression<Func<TElement, bool>>;
00040
00041 if (expressionToReplace != null && WhereDetailExpressions.ContainsKey(expressionToReplace))
00042 {
00043 ParameterExpression itemParameter = expressionToReplace.Parameters[0];
00044 Expression translation = Translate<TElement>(itemParameter, expressionToReplace.Body);
00045
00046 var translationLambda = Expression.Lambda(translation, itemParameter);
00047 var quote = Expression.Quote(translationLambda);
00048 Debug.WriteLine("Translating: " + expression);
00049 expression = Expression.Call(mcExpression.Object, mcExpression.Method, mcExpression.Arguments[0], quote);
00050 Debug.WriteLine("Into: " + expression);
00051 }
00052 }
00053 }
00054
00055 return new ContentQueryable<TElement>(this, queryProvider.CreateQuery<TElement>(expression));
00056 }
00057
00058 Expression Translate<TElement>(ParameterExpression itemParameter, Expression expressionBody)
00059 {
00060 switch (expressionBody.NodeType)
00061 {
00062 case ExpressionType.Equal:
00063 case ExpressionType.GreaterThan:
00064 case ExpressionType.GreaterThanOrEqual:
00065 case ExpressionType.LessThan:
00066 case ExpressionType.LessThanOrEqual:
00067 case ExpressionType.NotEqual:
00068 return TranslatePropertyComparisonIntoDetailSubselect(itemParameter, expressionBody as BinaryExpression);
00069 case ExpressionType.AndAlso:
00070 case ExpressionType.OrElse:
00071 return CombineExpressions<TElement>(itemParameter, expressionBody as BinaryExpression);
00072 case ExpressionType.Call:
00073 return TranslateCallExpression(itemParameter, expressionBody as MethodCallExpression);
00074 default:
00075 throw new NotSupportedException("Not supported expression type " + expressionBody.NodeType + " in expression " + expressionBody);
00076 }
00077 }
00078
00079 Expression TranslateCallExpression(ParameterExpression itemParameter, MethodCallExpression expressionBody)
00080 {
00081 MemberExpression member = expressionBody.Object as MemberExpression;
00082 DetailInfo detail = GetDetailFromPropertyType(member.Type);
00083 LambdaExpression nameAndValueExpression = DetailMethodCallExpression(expressionBody, detail, member.Member.Name);
00084
00085 return EmbedDetailExpressionInSubselect(detail, itemParameter, nameAndValueExpression);
00086 }
00087
00088 LambdaExpression DetailMethodCallExpression(MethodCallExpression expressionBody, DetailInfo detail, string propertyName)
00089 {
00090 ParameterExpression detailParameter = Expression.Parameter(detail.DetailType, "cd");
00091 var nameEqual = GetPropertyEquals(detailParameter, "Name", Expression.Constant(propertyName));
00092
00093 var valueProperty = Expression.Property(detailParameter, detail.ValuePropertyName);
00094 var valueExpression = Expression.Call(valueProperty, expressionBody.Method, expressionBody.Arguments);
00095 Expression nameAndValue = Expression.AndAlso(nameEqual, valueExpression);
00096 return Expression.Lambda(nameAndValue, detailParameter);
00097 }
00098
00099 private Expression CombineExpressions<TElement>(ParameterExpression itemParameter, BinaryExpression expression)
00100 {
00101 Func<Expression, Expression, Expression> joiner;
00102 switch (expression.NodeType)
00103 {
00104 case ExpressionType.AndAlso:
00105 joiner = Expression.AndAlso;
00106 break;
00107 case ExpressionType.OrElse:
00108 joiner = Expression.OrElse;
00109 break;
00110 default:
00111 throw new NotSupportedException("Not supported expression type " + expression.NodeType + " in expression " + expression);
00112 }
00113 return joiner(Translate<TElement>(itemParameter, expression.Left), Translate<TElement>(itemParameter, expression.Right));
00114 }
00115
00116 MethodCallExpression TranslatePropertyComparisonIntoDetailSubselect(ParameterExpression itemParameter, BinaryExpression expressionBody)
00117 {
00118 ComparisonInfo comparison = ComparisonInfo.Get(expressionBody);
00119
00120 Type propertyType = GetExpressionType(comparison.NameExpression);
00121 var detail = GetDetailFromPropertyType(propertyType);
00122
00123 var detailExpression = GetDetailExpression(comparison, detail);
00124
00125 return EmbedDetailExpressionInSubselect(detail, itemParameter, detailExpression);
00126 }
00127
00128 MethodCallExpression EmbedDetailExpressionInSubselect(DetailInfo detail, ParameterExpression itemParameter, Expression nameAndValueExpression)
00129 {
00130 var valuesProperty = Expression.Property(Expression.Property(itemParameter, "Details"), "Values");
00131 var ofTypeMethod = ofTypeMethodInfo.MakeGenericMethod(detail.DetailType);
00132 var ofTypeCall = Expression.Call(valuesProperty, ofTypeMethod, valuesProperty);
00133 var anyMethod = anyMethodInfo.MakeGenericMethod(detail.DetailType);
00134 return Expression.Call(ofTypeCall, anyMethod, ofTypeCall, nameAndValueExpression);
00135 }
00136
00137 private Type GetExpressionType(MemberExpression nameExpression)
00138 {
00139 if (nameExpression.Member.MemberType == MemberTypes.Property)
00140 return ((PropertyInfo) nameExpression.Member).PropertyType;
00141
00142 throw new NotSupportedException("Comparison of " + nameExpression);
00143 }
00144
00145 private DetailInfo GetDetailFromPropertyType(Type propertyType)
00146 {
00147 if (propertyType == typeof(string))
00148 return new DetailInfo(typeof(StringDetail), "StringValue");
00149 if (propertyType == typeof(bool))
00150 return new DetailInfo(typeof(BooleanDetail), "BoolValue");
00151 if (propertyType == typeof(int))
00152 return new DetailInfo(typeof(IntegerDetail), "IntValue");
00153 if (propertyType == typeof(double))
00154 return new DetailInfo(typeof (DoubleDetail), "DoubleValue");
00155 if (propertyType == typeof(DateTime))
00156 return new DetailInfo(typeof(DateTimeDetail), "DateTimeValue");
00157 if (typeof(ContentItem).IsAssignableFrom(propertyType))
00158 return new DetailInfo(typeof(LinkDetail), "LinkedItem");
00159
00160 return new DetailInfo(typeof(ObjectDetail), "Value");
00161 }
00162
00163 static Expression GetDetailExpression(ComparisonInfo comparison, DetailInfo detail)
00164 {
00165 ParameterExpression detailParameter = Expression.Parameter(detail.DetailType, "cd");
00166 var nameEqual = GetPropertyEquals(detailParameter, "Name", Expression.Constant(comparison.NameExpression.Member.Name));
00167 var valueExpression = GetPropertyComparison(detailParameter, detail.ValuePropertyName, comparison);
00168 Expression nameAndValue = Expression.AndAlso(nameEqual, valueExpression);
00169 var nameAndValueExpression = Expression.Lambda(nameAndValue, detailParameter);
00170 return nameAndValueExpression;
00171 }
00172
00173 static Expression GetPropertyEquals(ParameterExpression parameterExpression, string propertyName, Expression valueExpression)
00174 {
00175 MemberExpression propertyAccess = Expression.Property(parameterExpression, propertyName);
00176 BinaryExpression binaryExpression = Expression.Equal(propertyAccess, valueExpression);
00177 return binaryExpression;
00178 }
00179
00180 static Expression GetPropertyComparison(ParameterExpression parameterExpression, string propertyName, ComparisonInfo comparison)
00181 {
00182 MemberExpression propertyAccess = Expression.Property(parameterExpression, propertyName);
00183
00184 Expression left;
00185 Expression right;
00186 if(comparison.IsLeftToRight)
00187 {
00188 left = propertyAccess;
00189 right = comparison.ValueExpression;
00190 }
00191 else
00192 {
00193 right = propertyAccess;
00194 left = comparison.ValueExpression;
00195 }
00196
00197 switch (comparison.Type)
00198 {
00199 case ExpressionType.Equal:
00200 return Expression.Equal(left, right);
00201 case ExpressionType.GreaterThan:
00202 return Expression.GreaterThan(left, right);
00203 case ExpressionType.GreaterThanOrEqual:
00204 return Expression.GreaterThanOrEqual(left, right);
00205 case ExpressionType.LessThan:
00206 return Expression.LessThan(left, right);
00207 case ExpressionType.LessThanOrEqual:
00208 return Expression.LessThanOrEqual(left, right);
00209 case ExpressionType.NotEqual:
00210 return Expression.NotEqual(left, right);
00211
00212 default:
00213 throw new NotSupportedException("Expression of type " + comparison.Type + " is not supported");
00214 }
00215 }
00216
00217 public IQueryable CreateQuery(Expression expression)
00218 {
00219 Debug.WriteLine("CreateQuery: " + expression);
00220 return queryProvider.CreateQuery(expression);
00221 }
00222
00223 public TResult Execute<TResult>(Expression expression)
00224 {
00225 Debug.WriteLine("Execute: " + expression);
00226 return queryProvider.Execute<TResult>(expression);
00227 }
00228
00229 public object Execute(Expression expression)
00230 {
00231 Debug.WriteLine("Execute: " + expression);
00232 return queryProvider.Execute(expression);
00233 }
00234
00235 #endregion
00236
00237 struct ComparisonInfo
00238 {
00239 public MemberExpression NameExpression;
00240 public Expression ValueExpression;
00241 public bool IsLeftToRight;
00242 public ExpressionType Type;
00243
00244 internal static ComparisonInfo Get(BinaryExpression expressionBody)
00245 {
00246 ComparisonInfo info;
00247
00248 info.NameExpression = expressionBody.Left as MemberExpression;
00249 info.ValueExpression = expressionBody.Right;
00250 info.IsLeftToRight = true;
00251 info.Type = expressionBody.NodeType;
00252
00253 if (info.NameExpression == null)
00254 {
00255
00256 info.NameExpression = expressionBody.Right as MemberExpression;
00257 info.ValueExpression = expressionBody.Left;
00258 info.IsLeftToRight = false;
00259 }
00260
00261 return info;
00262 }
00263 }
00264 struct DetailInfo
00265 {
00266 public DetailInfo(Type detailType, string valuePropertyName)
00267 {
00268 DetailType = detailType;
00269 ValuePropertyName = valuePropertyName;
00270 }
00271
00272 public Type DetailType;
00273 public string ValuePropertyName;
00274 }
00275 }
00276
00277 }