Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Linq / DataShape.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Reflection;
5 using System.Linq;
6 using System.Linq.Expressions;
7 using System.Collections;
8 using System.Data.Linq.SqlClient;
9 using System.Diagnostics.CodeAnalysis;
10
11 namespace System.Data.Linq {
12     sealed public class DataLoadOptions {
13         bool frozen;
14         Dictionary<MetaPosition, MemberInfo> includes = new Dictionary<MetaPosition, MemberInfo>();
15         Dictionary<MetaPosition, LambdaExpression> subqueries = new Dictionary<MetaPosition, LambdaExpression>();
16
17         /// <summary>
18         /// Describe a property that is automatically loaded when the containing instance is loaded
19         /// </summary>
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");
25             }
26             MemberInfo mi = GetLoadWithMemberInfo(expression);
27             this.Preload(mi);
28         }
29
30         /// <summary>
31         /// Describe a property that is automatically loaded when the containing instance is loaded
32         /// </summary>
33         public void LoadWith(LambdaExpression expression) {
34             if (expression == null) {
35                 throw Error.ArgumentNull("expression");
36             }
37             MemberInfo mi = GetLoadWithMemberInfo(expression);
38             this.Preload(mi);
39         }
40
41         /// <summary>
42         /// Place a subquery on the given association.
43         /// </summary>
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");
49             }
50             this.AssociateWithInternal(expression);
51         }
52
53         /// <summary>
54         /// Place a subquery on the given association.
55         /// </summary>
56         public void AssociateWith(LambdaExpression expression) {
57             if (expression == null) {
58                 throw Error.ArgumentNull("expression");
59             }
60             this.AssociateWithInternal(expression);
61         }
62
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;
68             }
69             LambdaExpression lambda = Expression.Lambda(op, expression.Parameters.ToArray());
70             MemberInfo mi = Searcher.MemberInfoOf(lambda);
71             this.Subquery(mi, lambda);
72         }
73
74         /// <summary>
75         /// Determines if the member is automatically loaded with its containing instances.
76         /// </summary>
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) {
80             if (member == null) {
81                 throw Error.ArgumentNull("member");
82             }
83             return includes.ContainsKey(new MetaPosition(member));
84         }
85
86         /// <summary>        
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
91         /// </summary>
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) {
96                     return false;
97                 }
98
99                 foreach (MetaPosition metaPosition in ds2.includes.Keys) {
100                     if (!ds1.includes.ContainsKey(metaPosition)) {
101                         return false;
102                     }
103                 }
104             }
105             return true;
106         }
107
108         /// <summary>
109         /// Gets the subquery expression associated with the member.
110         /// </summary>
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");
116             }
117             LambdaExpression expression = null;
118             subqueries.TryGetValue(new MetaPosition(member), out expression);
119             return expression;
120         }
121
122         /// <summary>
123         /// Freeze the shape. Any further attempts to modify the shape will result in 
124         /// an exception.
125         /// </summary>
126         internal void Freeze() {
127             this.frozen = true;
128         }
129
130         /// <summary>
131         /// Describe a property that is automatically loaded when the containing instance is loaded
132         /// </summary>
133         internal void Preload(MemberInfo association) {
134             if (association == null) {
135                 throw Error.ArgumentNull("association");
136             }
137             if (this.frozen) {
138                 throw Error.IncludeNotAllowedAfterFreeze();
139             }
140             this.includes.Add(new MetaPosition(association), association);
141             ValidateTypeGraphAcyclic();
142         }
143
144         /// <summary>
145         /// Place a subquery on the given association.
146         /// </summary>
147         private void Subquery(MemberInfo association, LambdaExpression subquery) {
148             if (this.frozen) {
149                 throw Error.SubqueryNotAllowedAfterFreeze();
150             }
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;
155         }
156
157         /// <summary>
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.
161         /// </summary>
162         private static MemberInfo GetLoadWithMemberInfo(LambdaExpression lambda)
163         {
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))
168             {
169                 body = ((UnaryExpression)body).Operand;
170             }
171
172             MemberExpression mex = body as MemberExpression;
173             if (mex != null && mex.Expression.NodeType == ExpressionType.Parameter)
174             {
175                 return mex.Member;
176             }
177             else
178             {
179                 throw Error.InvalidLoadOptionsLoadMemberSpecification();
180             }
181         }
182
183         private static class Searcher {           
184             static internal MemberInfo MemberInfoOf(LambdaExpression lambda) {
185                 Visitor v = new Visitor();
186                 v.VisitLambda(lambda);
187                 return v.MemberInfo;
188             }
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);
194                 }
195
196                 internal override Expression VisitMethodCall(MethodCallExpression m) {
197                     this.Visit(m.Object);
198                     foreach (Expression arg in m.Arguments) {
199                         this.Visit(arg);
200                         break; // Only follow the extension method 'this'
201                     }
202                     return m;
203                 }
204
205             }
206         }
207
208         private void ValidateTypeGraphAcyclic() {
209             IEnumerable<MemberInfo> edges = this.includes.Values;
210             int removed = 0;
211
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));
217                 }
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()) {
223                         newEdges.Add(edge);
224                     }
225                     else {
226                         ++removed;
227                         someRemoved = true;
228                         if (removed == this.includes.Count)
229                             return;
230                     }
231                 }
232                 if (!someRemoved) {
233                     throw Error.IncludeCycleNotAllowed(); // No edges removed, there must be a loop.
234                 }
235                 edges = newEdges;
236             }
237             throw new InvalidOperationException("Bug in ValidateTypeGraphAcyclic"); // Getting here means a bug.
238         }
239
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];
244             }
245             return mt;
246         }
247
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);
252             }
253             if (!typeof(IEnumerable).IsAssignableFrom(memberType)) {
254                 throw Error.SubqueryNotSupportedOnType(mi.Name, mi.DeclaringType);
255             }
256         }
257
258         private static void ValidateSubqueryExpression(LambdaExpression subquery) {
259             if (!typeof(IEnumerable).IsAssignableFrom(subquery.Body.Type)) {
260                 throw Error.SubqueryMustBeSequence();
261             }
262             new SubqueryValidator().VisitLambda(subquery);
263         }
264
265         /// <summary>
266         /// Ensure that the subquery follows the rules for subqueries.
267         /// </summary>
268         private class SubqueryValidator : System.Data.Linq.SqlClient.ExpressionVisitor { 
269             bool isTopLevel = true;
270             internal override Expression VisitMethodCall(MethodCallExpression m) {
271                 bool was = isTopLevel;
272                 try {
273                     if (isTopLevel && !SubqueryRules.IsSupportedTopLevelMethod(m.Method))
274                         throw Error.SubqueryDoesNotSupportOperator(m.Method.Name);
275                     isTopLevel = false;
276                     return base.VisitMethodCall(m);
277                 }
278                 finally {
279                     isTopLevel = was;
280                 }
281             }
282         }
283
284         /// <summary>
285         /// Whether there have been LoadOptions specified.
286         /// </summary>
287         internal bool IsEmpty {
288             get { return this.includes.Count == 0 && this.subqueries.Count == 0; }
289         }
290     }
291 }