2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[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.")]
2016-08-03 10:59:49 +00:00
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>
2017-08-21 15:34:15 +00:00
[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.")]
2016-08-03 10:59:49 +00:00
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 ; }
}
}
}