|
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 = "Microsoft: Generic types are an important part of Linq APIs and they could not exist without nested generic support.")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Microsoft: 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 = "Microsoft: Generic types are an important part of Linq APIs and they could not exist without nested generic support.")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Microsoft: 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; }
}
}
}
|