private void LoadAssociationsForEach <T>(List <T> entities, LoadOptions loadOptions) { var typeTLoadOptions = loadOptions.LoadWithOptions .Where(lo => lo.Member.Parameters.Single().Type == typeof(T)) .Select(lo => new { LoadOption = lo, Member = ((MemberExpression)lo.Member.Body).Member }); foreach (var loadOption in typeTLoadOptions) { var thisSideAssociationProperty = loadOption.Member as PropertyInfo; if (thisSideAssociationProperty == null) { throw new NotSupportedException("Only properties are supported"); } MetaType type = this.GetMetaType <T>(); MetaAssociation association = type.Associations.Where(assoc => assoc.ThisMember.Name == thisSideAssociationProperty.Name).SingleOrDefault(); if (association == null) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Association mapping for property {0} of class {1} is not defined.", thisSideAssociationProperty.Name, typeof(T).FullName)); } // find all possible associated entities Type associatedType = thisSideAssociationProperty.PropertyType; if (!association.IsForeignKey) { // there is a collection on this side, get collection item type Type listInterface = associatedType.GetInterfaces().Where(i => (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList <>))).SingleOrDefault(); if (listInterface == null) { throw new InvalidOperationException("Non-FK association property type must implement IList<T> interface"); } associatedType = listInterface.GetGenericArguments().Single(); // fill with empty lists foreach (T entity in entities) { object localEntity = entity; association.ThisMember.StorageAccessor.SetBoxedValue(ref localEntity, Activator.CreateInstance(association.ThisMember.Type)); } } // first list to join Expression allTsConst = Expression.Constant(entities.AsQueryable()); // second list to join var allAssociatedEntitiesQuery = this.All(associatedType, loadOptions); // appying Filter expressions if present if (loadOption.LoadOption.Association != null) { var allAssociatedEntitiesFilteredEnumerableExpression = new ExpressionReplacer(loadOption.LoadOption.Member.Body, allAssociatedEntitiesQuery.Expression).Visit(loadOption.LoadOption.Association.Body); var asQueryableGenericMethod = TypeSystem.FindQueryableMethod("AsQueryable", new[] { typeof(IEnumerable <>).MakeGenericType(associatedType) }, new[] { associatedType }); var allAssociatedEntitiesFilteredQueryableExpression = Expression.Call(asQueryableGenericMethod, allAssociatedEntitiesFilteredEnumerableExpression); allAssociatedEntitiesQuery = allAssociatedEntitiesQuery.Provider.CreateQuery(allAssociatedEntitiesFilteredQueryableExpression); } Expression allAssociatedEntitiesExpression = allAssociatedEntitiesQuery.Expression; // outerKeySelector var paramA1 = Expression.Parameter(typeof(T), "a"); MemberExpression thisKeyPropertyExpression = paramA1.Property(association.ThisKey.First().Member as PropertyInfo); // innerKeySelector var paramB1 = Expression.Parameter(associatedType, "b"); MemberExpression otherKeyPropertyExpression = paramB1.Property(association.OtherKey.First().Member as PropertyInfo); if (thisKeyPropertyExpression.Type.IsGenericType && thisKeyPropertyExpression.Type.GetGenericTypeDefinition() == typeof(Nullable <>)) { var nullableFilterParamA1 = Expression.Parameter(typeof(T), "a"); MemberExpression nullableFilterPropertyExpression = nullableFilterParamA1 .Property(association.ThisKey.First().Member as PropertyInfo) .Property("HasValue"); LambdaExpression whereHasValueFilter = nullableFilterParamA1.ToLambda(nullableFilterPropertyExpression); allTsConst = allTsConst.Where(whereHasValueFilter); thisKeyPropertyExpression = thisKeyPropertyExpression.Property("Value"); } if (otherKeyPropertyExpression.Type.IsGenericType && otherKeyPropertyExpression.Type.GetGenericTypeDefinition() == typeof(Nullable <>)) { var nullableFilterParamB1 = Expression.Parameter(associatedType, "b"); MemberExpression nullableFilterPropertyExpression = nullableFilterParamB1 .Property(association.OtherKey.First().Member as PropertyInfo) .Property("HasValue"); LambdaExpression whereHasValueFilter = nullableFilterParamB1.ToLambda(nullableFilterPropertyExpression); allAssociatedEntitiesExpression = allAssociatedEntitiesExpression.Where(whereHasValueFilter); otherKeyPropertyExpression = otherKeyPropertyExpression.Property("Value"); } var outerKeySelector = Expression.Quote(paramA1.ToLambda(thisKeyPropertyExpression)); var innerKeySelector = Expression.Quote(paramB1.ToLambda(otherKeyPropertyExpression)); var keyValueType = typeof(KeyValuePair <,>).MakeGenericType(typeof(T), associatedType); // resultSelector var paramA2 = Expression.Parameter(typeof(T), "a"); var paramB2 = Expression.Parameter(associatedType, "b"); var resultSelector = Expression.Quote( Expression.Lambda( Expression.New( keyValueType.GetConstructors().Single(), paramA2, paramB2), paramA2, paramB2)); var joinMethod = typeof(Queryable).GetMethods().Where(m => m.Name == "Join" && m.GetParameters().Count() == 5).Single(); var joinMethodGeneric = joinMethod.MakeGenericMethod(typeof(T), associatedType, thisKeyPropertyExpression.Type, keyValueType); // join! var joinExpression = Expression.Call(null, joinMethodGeneric, allTsConst, allAssociatedEntitiesExpression, outerKeySelector, innerKeySelector, resultSelector); var joinResult = allAssociatedEntitiesQuery.Provider.Execute(joinExpression) as IEnumerable; var keyProperty = keyValueType.GetProperty("Key"); var valueProperty = keyValueType.GetProperty("Value"); foreach (var pair in joinResult) { var thisObject = keyProperty.GetValue(pair, new object[0]); var otherObject = valueProperty.GetValue(pair, new object[0]); if (association.IsForeignKey) { association.ThisMember.StorageAccessor.SetBoxedValue( ref thisObject, otherObject); } else { var list = association.ThisMember.StorageAccessor.GetBoxedValue(thisObject); list.GetType().GetMethod("Add").Invoke(list, new[] { otherObject }); } } } }