| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Collections;
- using System.Data.Linq.SqlClient;
- using System.Diagnostics.CodeAnalysis;
- namespace System.Data.Linq {
- sealed public class DataLoadOptions {
- bool frozen;
- Dictionary<MetaPosition, MemberInfo> includes = new Dictionary<MetaPosition, MemberInfo>();
- Dictionary<MetaPosition, LambdaExpression> subqueries = new Dictionary<MetaPosition, LambdaExpression>();
- /// <summary>
- /// Describe a property that is automatically loaded when the containing instance is loaded
- /// </summary>
- [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "[....]: Generic types are an important part of Linq APIs and they could not exist without nested generic support.")]
- [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "[....]: Need to provide static typing.")]
- public void LoadWith<T>(Expression<Func<T, object>> expression) {
- if (expression == null) {
- throw Error.ArgumentNull("expression");
- }
- MemberInfo mi = GetLoadWithMemberInfo(expression);
- this.Preload(mi);
- }
- /// <summary>
- /// Describe a property that is automatically loaded when the containing instance is loaded
- /// </summary>
- public void LoadWith(LambdaExpression expression) {
- if (expression == null) {
- throw Error.ArgumentNull("expression");
- }
- MemberInfo mi = GetLoadWithMemberInfo(expression);
- this.Preload(mi);
- }
- /// <summary>
- /// Place a subquery on the given association.
- /// </summary>
- [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "[....]: Generic types are an important part of Linq APIs and they could not exist without nested generic support.")]
- [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "[....]: Need to provide static typing.")]
- public void AssociateWith<T>(Expression<Func<T, object>> expression) {
- if (expression == null) {
- throw Error.ArgumentNull("expression");
- }
- this.AssociateWithInternal(expression);
- }
- /// <summary>
- /// Place a subquery on the given association.
- /// </summary>
- public void AssociateWith(LambdaExpression expression) {
- if (expression == null) {
- throw Error.ArgumentNull("expression");
- }
- this.AssociateWithInternal(expression);
- }
- private void AssociateWithInternal(LambdaExpression expression) {
- // Strip the cast-to-object.
- Expression op = expression.Body;
- while (op.NodeType == ExpressionType.Convert || op.NodeType == ExpressionType.ConvertChecked) {
- op = ((UnaryExpression)op).Operand;
- }
- LambdaExpression lambda = Expression.Lambda(op, expression.Parameters.ToArray());
- MemberInfo mi = Searcher.MemberInfoOf(lambda);
- this.Subquery(mi, lambda);
- }
- /// <summary>
- /// Determines if the member is automatically loaded with its containing instances.
- /// </summary>
- /// <param name="member">The member this is automatically loaded.</param>
- /// <returns>True if the member is automatically loaded.</returns>
- internal bool IsPreloaded(MemberInfo member) {
- if (member == null) {
- throw Error.ArgumentNull("member");
- }
- return includes.ContainsKey(new MetaPosition(member));
- }
- /// <summary>
- /// Two shapes are equivalent if any of the following are true:
- /// (1) They are the same object instance
- /// (2) They are both null or empty
- /// (3) They contain the same preloaded members
- /// </summary>
- internal static bool ShapesAreEquivalent(DataLoadOptions ds1, DataLoadOptions ds2) {
- bool shapesAreSameOrEmpty = (ds1 == ds2) || ((ds1 == null || ds1.IsEmpty) && (ds2 == null || ds2.IsEmpty));
- if (!shapesAreSameOrEmpty) {
- if (ds1 == null || ds2 == null || ds1.includes.Count != ds2.includes.Count) {
- return false;
- }
- foreach (MetaPosition metaPosition in ds2.includes.Keys) {
- if (!ds1.includes.ContainsKey(metaPosition)) {
- return false;
- }
- }
- }
- return true;
- }
- /// <summary>
- /// Gets the subquery expression associated with the member.
- /// </summary>
- /// <param name="member">The member with the subquery.</param>
- /// <returns></returns>
- internal LambdaExpression GetAssociationSubquery(MemberInfo member) {
- if (member == null) {
- throw Error.ArgumentNull("member");
- }
- LambdaExpression expression = null;
- subqueries.TryGetValue(new MetaPosition(member), out expression);
- return expression;
- }
- /// <summary>
- /// Freeze the shape. Any further attempts to modify the shape will result in
- /// an exception.
- /// </summary>
- internal void Freeze() {
- this.frozen = true;
- }
- /// <summary>
- /// Describe a property that is automatically loaded when the containing instance is loaded
- /// </summary>
- internal void Preload(MemberInfo association) {
- if (association == null) {
- throw Error.ArgumentNull("association");
- }
- if (this.frozen) {
- throw Error.IncludeNotAllowedAfterFreeze();
- }
- this.includes.Add(new MetaPosition(association), association);
- ValidateTypeGraphAcyclic();
- }
- /// <summary>
- /// Place a subquery on the given association.
- /// </summary>
- private void Subquery(MemberInfo association, LambdaExpression subquery) {
- if (this.frozen) {
- throw Error.SubqueryNotAllowedAfterFreeze();
- }
- subquery = (LambdaExpression)System.Data.Linq.SqlClient.Funcletizer.Funcletize(subquery); // Layering violation.
- ValidateSubqueryMember(association);
- ValidateSubqueryExpression(subquery);
- this.subqueries[new MetaPosition(association)] = subquery;
- }
- /// <summary>
- /// If the lambda specified is of the form p.A, where p is the parameter
- /// and A is a member on p, the MemberInfo for A is returned. If
- /// the expression is not of this form, an exception is thrown.
- /// </summary>
- private static MemberInfo GetLoadWithMemberInfo(LambdaExpression lambda)
- {
- // When the specified member is a value type, there will be a conversion
- // to object that we need to strip
- Expression body = lambda.Body;
- if (body != null && (body.NodeType == ExpressionType.Convert || body.NodeType == ExpressionType.ConvertChecked))
- {
- body = ((UnaryExpression)body).Operand;
- }
- MemberExpression mex = body as MemberExpression;
- if (mex != null && mex.Expression.NodeType == ExpressionType.Parameter)
- {
- return mex.Member;
- }
- else
- {
- throw Error.InvalidLoadOptionsLoadMemberSpecification();
- }
- }
- private static class Searcher {
- static internal MemberInfo MemberInfoOf(LambdaExpression lambda) {
- Visitor v = new Visitor();
- v.VisitLambda(lambda);
- return v.MemberInfo;
- }
- private class Visitor : System.Data.Linq.SqlClient.ExpressionVisitor {
- internal MemberInfo MemberInfo;
- internal override Expression VisitMemberAccess(MemberExpression m) {
- this.MemberInfo = m.Member;
- return base.VisitMemberAccess(m);
- }
- internal override Expression VisitMethodCall(MethodCallExpression m) {
- this.Visit(m.Object);
- foreach (Expression arg in m.Arguments) {
- this.Visit(arg);
- break; // Only follow the extension method 'this'
- }
- return m;
- }
- }
- }
- private void ValidateTypeGraphAcyclic() {
- IEnumerable<MemberInfo> edges = this.includes.Values;
- int removed = 0;
- for (int loop = 0; loop < this.includes.Count; ++loop) {
- // Build a list of all edge targets.
- HashSet<Type> edgeTargets = new HashSet<Type>();
- foreach (MemberInfo edge in edges) {
- edgeTargets.Add(GetIncludeTarget(edge));
- }
- // Remove all edges with sources matching no target.
- List<MemberInfo> newEdges = new List<MemberInfo>();
- bool someRemoved = false;
- foreach (MemberInfo edge in edges) {
- if (edgeTargets.Where(et=>et.IsAssignableFrom(edge.DeclaringType) || edge.DeclaringType.IsAssignableFrom(et)).Any()) {
- newEdges.Add(edge);
- }
- else {
- ++removed;
- someRemoved = true;
- if (removed == this.includes.Count)
- return;
- }
- }
- if (!someRemoved) {
- throw Error.IncludeCycleNotAllowed(); // No edges removed, there must be a loop.
- }
- edges = newEdges;
- }
- throw new InvalidOperationException("Bug in ValidateTypeGraphAcyclic"); // Getting here means a bug.
- }
- private static Type GetIncludeTarget(MemberInfo mi) {
- Type mt = System.Data.Linq.SqlClient.TypeSystem.GetMemberType(mi);
- if (mt.IsGenericType) {
- return mt.GetGenericArguments()[0];
- }
- return mt;
- }
- private static void ValidateSubqueryMember(MemberInfo mi) {
- Type memberType = System.Data.Linq.SqlClient.TypeSystem.GetMemberType(mi);
- if (memberType == null) {
- throw Error.SubqueryNotSupportedOn(mi);
- }
- if (!typeof(IEnumerable).IsAssignableFrom(memberType)) {
- throw Error.SubqueryNotSupportedOnType(mi.Name, mi.DeclaringType);
- }
- }
- private static void ValidateSubqueryExpression(LambdaExpression subquery) {
- if (!typeof(IEnumerable).IsAssignableFrom(subquery.Body.Type)) {
- throw Error.SubqueryMustBeSequence();
- }
- new SubqueryValidator().VisitLambda(subquery);
- }
- /// <summary>
- /// Ensure that the subquery follows the rules for subqueries.
- /// </summary>
- private class SubqueryValidator : System.Data.Linq.SqlClient.ExpressionVisitor {
- bool isTopLevel = true;
- internal override Expression VisitMethodCall(MethodCallExpression m) {
- bool was = isTopLevel;
- try {
- if (isTopLevel && !SubqueryRules.IsSupportedTopLevelMethod(m.Method))
- throw Error.SubqueryDoesNotSupportOperator(m.Method.Name);
- isTopLevel = false;
- return base.VisitMethodCall(m);
- }
- finally {
- isTopLevel = was;
- }
- }
- }
- /// <summary>
- /// Whether there have been LoadOptions specified.
- /// </summary>
- internal bool IsEmpty {
- get { return this.includes.Count == 0 && this.subqueries.Count == 0; }
- }
- }
- }
|