/// <summary> /// Add the Fetch clauses to the query according to the given expand paths /// </summary> /// <param name="queryable">The query to expand</param> /// <param name="expandsQueryString">Comma-separated list of properties to expand. May include nested paths of the form "Property/SubProperty"</param> /// <param name="sessionFactory">Provides the NHibernate metadata for the classes</param> /// <param name="expandMap">Will be populated with the names of the expanded properties for each type.</param> /// <param name="expandCollections">If true, eagerly fetch collections. Caution: this causes problems with $skip and $top operations. /// Default is false. expandMap will still be populated with the collection property, so it will be lazy loaded. /// Be sure to set default_batch_fetch_size in the configuration for lazy loaded collections.</param> /// <returns></returns> public IQueryable ApplyExpansions(IQueryable queryable, string expandsQueryString, ExpandTypeMap expandMap, bool expandCollections = false) { string[] expandPaths = expandsQueryString.Split(',').Select(s => s.Trim()).ToArray(); if (!expandPaths.Any()) throw new Exception("Expansion Paths cannot be null"); if (queryable == null) throw new Exception("Query cannot be null"); return ApplyExpansions(queryable, expandPaths, expandMap, expandCollections); }
/// <summary> /// Recursively forces loading of each NHibernate proxy in the tree that matches an entry in the map. /// </summary> /// <param name="list">Top-level collection of objects</param> /// <param name="expandMap">Properties to initialize for each type</param> public static void InitializeList(IEnumerable list, ExpandTypeMap expandMap) { if (expandMap == null) { return; } var map = expandMap.map; var depth = expandMap.maxDepth; foreach (var el in list) { InitializeWithCascade(el, map, depth); } }
/// <summary> /// Create an ExpandTypeMap populated according to the expandPaths. /// </summary> /// <param name="type">The type of the root element.</param> /// <param name="expandPaths">The names of the properties to expand. May include nested paths of the form "Property/SubProperty"</param> /// <param name="expandMap">Will be populated with the names of the expanded properties for each type. If null, a new one is created.</param> /// <returns>expandMap</returns> public static ExpandTypeMap MapExpansions(Type type, string[] expandPaths, ExpandTypeMap expandMap = null) { if (!expandPaths.Any()) { throw new ArgumentException("Expansion Paths cannot be null"); } if (expandMap == null) { expandMap = new ExpandTypeMap(); } foreach (string expand in expandPaths) { // We always start with the resulting element type var currentType = type; // split on '/' or '.' var segments = expand.Split('/', '.'); expandMap.Deepen(segments.Length); foreach (string seg in segments) { if (expandMap != null && !expandMap.map.ContainsKey(currentType)) { expandMap.map.Add(currentType, new List <string>()); } // Gather information about the property var propInfo = currentType.GetProperty(seg); if (propInfo == null) { throw new ArgumentException("Type '" + currentType.Name + "' does not have property '" + seg + "'"); } if (expandMap != null && !expandMap.map[currentType].Contains(seg)) { expandMap.map[currentType].Add(seg); } var propType = propInfo.PropertyType; currentType = propType; } } return(expandMap); }
/// <summary> /// Add the Fetch clauses to the query according to the given expand paths /// </summary> /// <param name="queryable">The query to expand</param> /// <param name="expandPaths">The names of the properties to expand. May include nested paths of the form "Property/SubProperty"</param> /// <param name="sessionFactory">Provides the NHibernate metadata for the classes</param> /// <param name="expandMap">Will be populated with the names of the expanded properties for each type.</param> /// <param name="expandCollections">If true, eagerly fetch collections. Caution: this causes problems with $skip and $top operations. /// Default is false. expandMap will still be populated with the collection property, so it will be lazy loaded. /// Be sure to set default_batch_fetch_size in the configuration for lazy loaded collections.</param> /// <returns></returns> public IQueryable ApplyExpansions(IQueryable queryable, string[] expandPaths, ExpandTypeMap expandMap, bool expandCollections = false) { if (queryable == null) throw new ArgumentException("Query cannot be null"); var nHibQuery = queryable.Provider as DefaultQueryProvider; if (nHibQuery == null) throw new ArgumentException("Expansion only supported on INHibernateQueryable queries"); if (!expandPaths.Any()) throw new ArgumentException("Expansion Paths cannot be null"); var currentQueryable = queryable; foreach (string expand in expandPaths) { // We always start with the resulting element type var currentType = currentQueryable.ElementType; var isFirstFetch = true; var isInvoking = true; // split on '/' or '.' var segments = expand.Split('/', '.'); expandMap.Deepen(segments.Length); foreach (string seg in segments) { if (expandMap != null && !expandMap.map.ContainsKey(currentType)) expandMap.map.Add(currentType, new List<string>()); IClassMetadata metadata = sessionFactory.GetClassMetadata(currentType); if (metadata == null) { throw new ArgumentException("Type '" + currentType + "' not recognized as a valid type for this Context"); } // Gather information about the property var propInfo = currentType.GetProperty(seg); if (propInfo == null) { throw new ArgumentException("Type '" + currentType.Name + "' does not have property '" + seg + "'"); } if (expandMap != null && !expandMap.map[currentType].Contains(seg)) expandMap.map[currentType].Add(seg); var propType = propInfo.PropertyType; var metaPropType = metadata.GetPropertyType(seg); // When this is the first segment of a path, we have to use Fetch instead of ThenFetch var propFetchFunctionName = (isFirstFetch ? "Fetch" : "ThenFetch"); // The delegateType is a type for the lambda creation to create the correct return value System.Type delegateType; if (metaPropType.IsCollectionType) { // We have to use "FetchMany" or "ThenFetchMany" when the target property is a collection propFetchFunctionName += "Many"; // We only support IList<T> or something similar propType = propType.GetGenericArguments().Single(); delegateType = typeof(Func<,>).MakeGenericType(currentType, typeof(IEnumerable<>).MakeGenericType(propType)); if (!expandCollections) { // if we don't expand this collection, we won't invoke any sub-expansions of it either // but we still need to traverse the tree top populate expandMap isInvoking = false; } } else { delegateType = typeof(Func<,>).MakeGenericType(currentType, propType); } if (isInvoking) { // Get the correct extension method (Fetch, FetchMany, ThenFetch, or ThenFetchMany) var fetchMethodInfo = typeof(EagerFetchingExtensionMethods).GetMethod(propFetchFunctionName, BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod); var fetchMethodTypes = new List<System.Type>(); fetchMethodTypes.AddRange(currentQueryable.GetType().GetGenericArguments().Take(isFirstFetch ? 1 : 2)); fetchMethodTypes.Add(propType); fetchMethodInfo = fetchMethodInfo.MakeGenericMethod(fetchMethodTypes.ToArray()); // Create an expression of type new delegateType(x => x.{seg.Name}) var exprParam = System.Linq.Expressions.Expression.Parameter(currentType, "x"); var exprProp = System.Linq.Expressions.Expression.Property(exprParam, seg); var exprLambda = System.Linq.Expressions.Expression.Lambda(delegateType, exprProp, new System.Linq.Expressions. ParameterExpression[] { exprParam }); // Call the *Fetch* function var args = new object[] { currentQueryable, exprLambda }; currentQueryable = (IQueryable)fetchMethodInfo.Invoke(null, args) as IQueryable; } currentType = propType; isFirstFetch = false; } } return currentQueryable; }
/// <summary> /// Create an ExpandTypeMap populated according to the expandPaths. /// </summary> /// <param name="type">The type of the root element.</param> /// <param name="expandPaths">The names of the properties to expand. May include nested paths of the form "Property/SubProperty"</param> /// <param name="expandMap">Will be populated with the names of the expanded properties for each type. If null, a new one is created.</param> /// <returns>expandMap</returns> public static ExpandTypeMap MapExpansions(Type type, string[] expandPaths, ExpandTypeMap expandMap = null) { if (!expandPaths.Any()) throw new ArgumentException("Expansion Paths cannot be null"); if (expandMap == null) expandMap = new ExpandTypeMap(); foreach (string expand in expandPaths) { // We always start with the resulting element type var currentType = type; // split on '/' or '.' var segments = expand.Split('/','.'); expandMap.Deepen(segments.Length); foreach (string seg in segments) { if (expandMap != null && !expandMap.map.ContainsKey(currentType)) expandMap.map.Add(currentType, new List<string>()); // Gather information about the property var propInfo = currentType.GetProperty(seg); if (propInfo == null) { throw new ArgumentException("Type '" + currentType.Name + "' does not have property '" + seg + "'"); } if (expandMap != null && !expandMap.map[currentType].Contains(seg)) expandMap.map[currentType].Add(seg); var propType = propInfo.PropertyType; currentType = propType; } } return expandMap; }
/// <summary> /// Add the Fetch clauses to the query according to the given expand paths /// </summary> /// <param name="queryable">The query to expand</param> /// <param name="expandPaths">The names of the properties to expand. May include nested paths of the form "Property/SubProperty"</param> /// <param name="sessionFactory">Provides the NHibernate metadata for the classes</param> /// <param name="expandMap">Will be populated with the names of the expanded properties for each type.</param> /// <param name="expandCollections">If true, eagerly fetch collections. Caution: this causes problems with $skip and $top operations. /// Default is false. expandMap will still be populated with the collection property, so it will be lazy loaded. /// Be sure to set default_batch_fetch_size in the configuration for lazy loaded collections.</param> /// <returns></returns> public IQueryable ApplyExpansions(IQueryable queryable, string[] expandPaths, ExpandTypeMap expandMap, bool expandCollections = false) { if (queryable == null) { throw new ArgumentException("Query cannot be null"); } var nHibQuery = queryable.Provider as DefaultQueryProvider; if (nHibQuery == null) { throw new ArgumentException("Expansion only supported on INHibernateQueryable queries"); } if (!expandPaths.Any()) { throw new ArgumentException("Expansion Paths cannot be null"); } var currentQueryable = queryable; foreach (string expand in expandPaths) { // We always start with the resulting element type var currentType = currentQueryable.ElementType; var isFirstFetch = true; var isInvoking = true; // split on '/' or '.' var segments = expand.Split('/', '.'); expandMap.Deepen(segments.Length); foreach (string seg in segments) { if (expandMap != null && !expandMap.map.ContainsKey(currentType)) { expandMap.map.Add(currentType, new List <string>()); } IClassMetadata metadata = sessionFactory.GetClassMetadata(currentType); if (metadata == null) { throw new ArgumentException("Type '" + currentType + "' not recognized as a valid type for this Context"); } // Gather information about the property var propInfo = currentType.GetProperty(seg); if (propInfo == null) { throw new ArgumentException("Type '" + currentType.Name + "' does not have property '" + seg + "'"); } if (expandMap != null && !expandMap.map[currentType].Contains(seg)) { expandMap.map[currentType].Add(seg); } var propType = propInfo.PropertyType; var metaPropType = metadata.GetPropertyType(seg); // When this is the first segment of a path, we have to use Fetch instead of ThenFetch var propFetchFunctionName = (isFirstFetch ? "Fetch" : "ThenFetch"); // The delegateType is a type for the lambda creation to create the correct return value System.Type delegateType; if (metaPropType.IsCollectionType) { // We have to use "FetchMany" or "ThenFetchMany" when the target property is a collection propFetchFunctionName += "Many"; // We only support IList<T> or something similar propType = propType.GetGenericArguments().Single(); delegateType = typeof(Func <,>).MakeGenericType(currentType, typeof(IEnumerable <>).MakeGenericType(propType)); if (!expandCollections) { // if we don't expand this collection, we won't invoke any sub-expansions of it either // but we still need to traverse the tree top populate expandMap isInvoking = false; } } else { delegateType = typeof(Func <,>).MakeGenericType(currentType, propType); } if (isInvoking) { // Get the correct extension method (Fetch, FetchMany, ThenFetch, or ThenFetchMany) var fetchMethodInfo = typeof(EagerFetchingExtensionMethods).GetMethod(propFetchFunctionName, BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod); var fetchMethodTypes = new List <System.Type>(); fetchMethodTypes.AddRange(currentQueryable.GetType().GetGenericArguments().Take(isFirstFetch ? 1 : 2)); fetchMethodTypes.Add(propType); fetchMethodInfo = fetchMethodInfo.MakeGenericMethod(fetchMethodTypes.ToArray()); // Create an expression of type new delegateType(x => x.{seg.Name}) var exprParam = System.Linq.Expressions.Expression.Parameter(currentType, "x"); var exprProp = System.Linq.Expressions.Expression.Property(exprParam, seg); var exprLambda = System.Linq.Expressions.Expression.Lambda(delegateType, exprProp, new System.Linq.Expressions. ParameterExpression[] { exprParam }); // Call the *Fetch* function var args = new object[] { currentQueryable, exprLambda }; currentQueryable = (IQueryable)fetchMethodInfo.Invoke(null, args) as IQueryable; } currentType = propType; isFirstFetch = false; } } return(currentQueryable); }
/// <summary> /// Add the Fetch clauses to the query according to the given expand paths /// </summary> /// <param name="queryable">The query to expand</param> /// <param name="expandsQueryString">Comma-separated list of properties to expand. May include nested paths of the form "Property/SubProperty"</param> /// <param name="sessionFactory">Provides the NHibernate metadata for the classes</param> /// <param name="expandMap">Will be populated with the names of the expanded properties for each type.</param> /// <param name="expandCollections">If true, eagerly fetch collections. Caution: this causes problems with $skip and $top operations. /// Default is false. expandMap will still be populated with the collection property, so it will be lazy loaded. /// Be sure to set default_batch_fetch_size in the configuration for lazy loaded collections.</param> /// <returns></returns> public IQueryable ApplyExpansions(IQueryable queryable, string expandsQueryString, ExpandTypeMap expandMap, bool expandCollections = false) { string[] expandPaths = expandsQueryString.Split(',').Select(s => s.Trim()).ToArray(); if (!expandPaths.Any()) { throw new Exception("Expansion Paths cannot be null"); } if (queryable == null) { throw new Exception("Query cannot be null"); } return(ApplyExpansions(queryable, expandPaths, expandMap, expandCollections)); }