2 using System.Collections.Generic;
4 using System.Reflection;
6 using System.Linq.Expressions;
7 using System.Collections;
8 using System.Data.Linq.SqlClient;
9 using System.Diagnostics.CodeAnalysis;
11 namespace System.Data.Linq {
12 sealed public class DataLoadOptions {
14 Dictionary<MetaPosition, MemberInfo> includes = new Dictionary<MetaPosition, MemberInfo>();
15 Dictionary<MetaPosition, LambdaExpression> subqueries = new Dictionary<MetaPosition, LambdaExpression>();
18 /// Describe a property that is automatically loaded when the containing instance is loaded
20 [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Microsoft: Generic types are an important part of Linq APIs and they could not exist without nested generic support.")]
21 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Microsoft: Need to provide static typing.")]
22 public void LoadWith<T>(Expression<Func<T, object>> expression) {
23 if (expression == null) {
24 throw Error.ArgumentNull("expression");
26 MemberInfo mi = GetLoadWithMemberInfo(expression);
31 /// Describe a property that is automatically loaded when the containing instance is loaded
33 public void LoadWith(LambdaExpression expression) {
34 if (expression == null) {
35 throw Error.ArgumentNull("expression");
37 MemberInfo mi = GetLoadWithMemberInfo(expression);
42 /// Place a subquery on the given association.
44 [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Microsoft: Generic types are an important part of Linq APIs and they could not exist without nested generic support.")]
45 [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Microsoft: Need to provide static typing.")]
46 public void AssociateWith<T>(Expression<Func<T, object>> expression) {
47 if (expression == null) {
48 throw Error.ArgumentNull("expression");
50 this.AssociateWithInternal(expression);
54 /// Place a subquery on the given association.
56 public void AssociateWith(LambdaExpression expression) {
57 if (expression == null) {
58 throw Error.ArgumentNull("expression");
60 this.AssociateWithInternal(expression);
63 private void AssociateWithInternal(LambdaExpression expression) {
64 // Strip the cast-to-object.
65 Expression op = expression.Body;
66 while (op.NodeType == ExpressionType.Convert || op.NodeType == ExpressionType.ConvertChecked) {
67 op = ((UnaryExpression)op).Operand;
69 LambdaExpression lambda = Expression.Lambda(op, expression.Parameters.ToArray());
70 MemberInfo mi = Searcher.MemberInfoOf(lambda);
71 this.Subquery(mi, lambda);
75 /// Determines if the member is automatically loaded with its containing instances.
77 /// <param name="member">The member this is automatically loaded.</param>
78 /// <returns>True if the member is automatically loaded.</returns>
79 internal bool IsPreloaded(MemberInfo member) {
81 throw Error.ArgumentNull("member");
83 return includes.ContainsKey(new MetaPosition(member));
87 /// Two shapes are equivalent if any of the following are true:
88 /// (1) They are the same object instance
89 /// (2) They are both null or empty
90 /// (3) They contain the same preloaded members
92 internal static bool ShapesAreEquivalent(DataLoadOptions ds1, DataLoadOptions ds2) {
93 bool shapesAreSameOrEmpty = (ds1 == ds2) || ((ds1 == null || ds1.IsEmpty) && (ds2 == null || ds2.IsEmpty));
94 if (!shapesAreSameOrEmpty) {
95 if (ds1 == null || ds2 == null || ds1.includes.Count != ds2.includes.Count) {
99 foreach (MetaPosition metaPosition in ds2.includes.Keys) {
100 if (!ds1.includes.ContainsKey(metaPosition)) {
109 /// Gets the subquery expression associated with the member.
111 /// <param name="member">The member with the subquery.</param>
112 /// <returns></returns>
113 internal LambdaExpression GetAssociationSubquery(MemberInfo member) {
114 if (member == null) {
115 throw Error.ArgumentNull("member");
117 LambdaExpression expression = null;
118 subqueries.TryGetValue(new MetaPosition(member), out expression);
123 /// Freeze the shape. Any further attempts to modify the shape will result in
126 internal void Freeze() {
131 /// Describe a property that is automatically loaded when the containing instance is loaded
133 internal void Preload(MemberInfo association) {
134 if (association == null) {
135 throw Error.ArgumentNull("association");
138 throw Error.IncludeNotAllowedAfterFreeze();
140 this.includes.Add(new MetaPosition(association), association);
141 ValidateTypeGraphAcyclic();
145 /// Place a subquery on the given association.
147 private void Subquery(MemberInfo association, LambdaExpression subquery) {
149 throw Error.SubqueryNotAllowedAfterFreeze();
151 subquery = (LambdaExpression)System.Data.Linq.SqlClient.Funcletizer.Funcletize(subquery); // Layering violation.
152 ValidateSubqueryMember(association);
153 ValidateSubqueryExpression(subquery);
154 this.subqueries[new MetaPosition(association)] = subquery;
158 /// If the lambda specified is of the form p.A, where p is the parameter
159 /// and A is a member on p, the MemberInfo for A is returned. If
160 /// the expression is not of this form, an exception is thrown.
162 private static MemberInfo GetLoadWithMemberInfo(LambdaExpression lambda)
164 // When the specified member is a value type, there will be a conversion
165 // to object that we need to strip
166 Expression body = lambda.Body;
167 if (body != null && (body.NodeType == ExpressionType.Convert || body.NodeType == ExpressionType.ConvertChecked))
169 body = ((UnaryExpression)body).Operand;
172 MemberExpression mex = body as MemberExpression;
173 if (mex != null && mex.Expression.NodeType == ExpressionType.Parameter)
179 throw Error.InvalidLoadOptionsLoadMemberSpecification();
183 private static class Searcher {
184 static internal MemberInfo MemberInfoOf(LambdaExpression lambda) {
185 Visitor v = new Visitor();
186 v.VisitLambda(lambda);
189 private class Visitor : System.Data.Linq.SqlClient.ExpressionVisitor {
190 internal MemberInfo MemberInfo;
191 internal override Expression VisitMemberAccess(MemberExpression m) {
192 this.MemberInfo = m.Member;
193 return base.VisitMemberAccess(m);
196 internal override Expression VisitMethodCall(MethodCallExpression m) {
197 this.Visit(m.Object);
198 foreach (Expression arg in m.Arguments) {
200 break; // Only follow the extension method 'this'
208 private void ValidateTypeGraphAcyclic() {
209 IEnumerable<MemberInfo> edges = this.includes.Values;
212 for (int loop = 0; loop < this.includes.Count; ++loop) {
213 // Build a list of all edge targets.
214 HashSet<Type> edgeTargets = new HashSet<Type>();
215 foreach (MemberInfo edge in edges) {
216 edgeTargets.Add(GetIncludeTarget(edge));
218 // Remove all edges with sources matching no target.
219 List<MemberInfo> newEdges = new List<MemberInfo>();
220 bool someRemoved = false;
221 foreach (MemberInfo edge in edges) {
222 if (edgeTargets.Where(et=>et.IsAssignableFrom(edge.DeclaringType) || edge.DeclaringType.IsAssignableFrom(et)).Any()) {
228 if (removed == this.includes.Count)
233 throw Error.IncludeCycleNotAllowed(); // No edges removed, there must be a loop.
237 throw new InvalidOperationException("Bug in ValidateTypeGraphAcyclic"); // Getting here means a bug.
240 private static Type GetIncludeTarget(MemberInfo mi) {
241 Type mt = System.Data.Linq.SqlClient.TypeSystem.GetMemberType(mi);
242 if (mt.IsGenericType) {
243 return mt.GetGenericArguments()[0];
248 private static void ValidateSubqueryMember(MemberInfo mi) {
249 Type memberType = System.Data.Linq.SqlClient.TypeSystem.GetMemberType(mi);
250 if (memberType == null) {
251 throw Error.SubqueryNotSupportedOn(mi);
253 if (!typeof(IEnumerable).IsAssignableFrom(memberType)) {
254 throw Error.SubqueryNotSupportedOnType(mi.Name, mi.DeclaringType);
258 private static void ValidateSubqueryExpression(LambdaExpression subquery) {
259 if (!typeof(IEnumerable).IsAssignableFrom(subquery.Body.Type)) {
260 throw Error.SubqueryMustBeSequence();
262 new SubqueryValidator().VisitLambda(subquery);
266 /// Ensure that the subquery follows the rules for subqueries.
268 private class SubqueryValidator : System.Data.Linq.SqlClient.ExpressionVisitor {
269 bool isTopLevel = true;
270 internal override Expression VisitMethodCall(MethodCallExpression m) {
271 bool was = isTopLevel;
273 if (isTopLevel && !SubqueryRules.IsSupportedTopLevelMethod(m.Method))
274 throw Error.SubqueryDoesNotSupportOperator(m.Method.Name);
276 return base.VisitMethodCall(m);
285 /// Whether there have been LoadOptions specified.
287 internal bool IsEmpty {
288 get { return this.includes.Count == 0 && this.subqueries.Count == 0; }