2012-03-04 10 views
2

보고서를 생성하기 위해 많은 양의 데이터를로드해야합니다. 그래프의 약 8 또는 9 가지 엔티티 유형이 관련됩니다.엔터티 프레임 워크에서 명시 적로드

내가 필요한 모든 데이터를 포함하도록 Include을 호출하면 결과 쿼리가 너무 복잡하여 (왼쪽 조인, 공용체, 사례 문 많음) 실행하는 데 약 600 초가 소요됩니다.

보고서를 생성 할 때 지연로드가 지연되도록하여 보고서를 생성하는 데 약 180 초가 걸립니다. 더 좋지만 여전히 받아 들일 수는 없습니다.

내가 할 경우

당신이 그것을 잘 알고 있다면 종류의 LLBLGen 같은

방법 ("관련 기업 유형의 ID를 기반으로 다음 개체 유형을로드"), 나는 필요한 모든 데이터를 약 3 초 안에 얻을 수있다.

어떻게해야합니까?

var invoices = objectContext.Invoices.Where(...reportCriteria...).ToArray(); 

objectContext.LoadProperty(invoices, "InvoiceReceivables"); 

objectContext.LoadProperty(invoices.SelectMany(i => i.InvoiceReceivables), "Adjustments"); 

objectContext.LoadProperty(invoices.SelectMany(i => i.InvoiceReceivables.SelectMany(ir => ir.Adjustments)), "AdjustmentComments") 

... and so on 

그러나 LoadProperty은 하나의 개체가 아니라 컬렉션을 위해 작동 :

는 기본적으로 나는 이런 식으로 뭔가를 할 수 있습니다.

쿼리를 수행하고 개체 그래프를 직접 작성하는 것 외에 다른 방법이 있습니까?

답변

2

Althugh E.J.의 대답은 매우 유효하고 더 나은 성과를 내기위한 접근 방식으로, 지금 보고서를 재구성 할 대역폭이 실제로 없습니다. 나는 이것을 합쳐서 트릭을하는 것처럼 보인다. 어쩌면 그것은 다른 누군가에게 약간의 유익을 줄 것입니다 ... 간략하게하기 위해 몇몇 사소한 도우미 방법은 생략했습니다.

사용법 :

Q.GetQueryableFactory(objectContext).Load(invoices, 
    i => i.InvoiceReceivables.SelectMany(ir => ir.Adjustments.SelectMany(
     a => a.AdjustmentComments))) 


public static class Q 
{ 
    /// <summary> 
    /// Gets a queryable factory that returns a queryable for a specific entity type. 
    /// </summary> 
    /// <param name="objectContext">The object context.</param> 
    /// <returns></returns> 
    public static Func<Type, IQueryable> GetQueryableFactory(object objectContext) 
    { 
     var queryablePropertiesByType = objectContext.GetType().GetProperties().Where(p => p.GetIndexParameters().Length == 0 && p.PropertyType.IsGenericTypeFor(typeof(IQueryable<>))) 
      .ToDictionary(p => p.PropertyType.FindElementType()); 

     return t => 
        { 
         PropertyInfo property; 
         if (!queryablePropertiesByType.TryGetValue(t, out property)) 
         { 
          property = queryablePropertiesByType.Values.FirstOrDefault(p => p.PropertyType.FindElementType().IsAssignableFrom(t)) 
           .EnsureNotDefault("Could not find queryable for entity type {0}.".FormatWith(t.Name)); 

          var queryable = property.GetValue(objectContext, null); 

          return (IQueryable)typeof(System.Linq.Queryable).GetMethod("OfType").MakeGenericMethod(t).Invoke(null, new[] { queryable }); 
         } 

         return (IQueryable)property.GetValue(objectContext, null); 
        }; 
    } 

    /// <summary> 
    /// Loads the target along the specified path, using the provided queryable factory. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="queryableFactory">The queryable factory.</param> 
    /// <param name="target">The target.</param> 
    /// <param name="path">The path.</param> 
    public static void Load<T>(this Func<Type, IQueryable> queryableFactory, T target, Expression<Func<T, object>> path) 
    { 
     queryableFactory.Load(target, path.AsEnumerable().Reverse().OfType<MemberExpression>().Select(m => m.Member.Name).Join(".")); 
    } 

    /// <summary> 
    /// Loads the target along the specified path, using the provided queryable factory. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="queryableFactory">The queryable factory.</param> 
    /// <param name="target">The target.</param> 
    /// <param name="path">The path.</param> 
    public static void Load<T>(this Func<Type, IQueryable> queryableFactory, IEnumerable<T> target, Expression<Func<T, object>> path) 
    { 
     queryableFactory.Load(target, path.ToMemberAccessStrings().First()); 
    } 

    /// <summary> 
    /// Loads the target along the specified path, using the provided queryable factory. 
    /// </summary> 
    /// <param name="queryableFactory">The queryable factory.</param> 
    /// <param name="target">The target.</param> 
    /// <param name="path">The path.</param> 
    public static void Load(this Func<Type, IQueryable> queryableFactory, object target, string path) 
    { 
     foreach (var pathPart in path.Split('.')) 
     { 
      var property = (target.GetType().FindElementType() ?? target.GetType()).GetProperty(pathPart); 

      LoadProperty(queryableFactory(property.PropertyType.FindElementType() ?? property.PropertyType), target, pathPart); 

      if (target is IEnumerable) 
      { 
       // select elements along path target.Select(i => i.Part).ToArray() 
       target = target.CastTo<IEnumerable>().AsQueryable().Select(pathPart).ToInferredElementTypeArray(); 

       var propertyElementType = property.PropertyType.FindElementType(); 
       if (propertyElementType != null) 
       { 
        target = target.CastTo<object[]>().SelectMany(i => i.CastTo<IEnumerable>().ToObjectArray()).ToArray(propertyElementType); 
       } 
      } 
      else 
      { 
       target = property.GetValue(target, null); 
      } 
     } 
    } 

    /// <summary> 
    /// Loads the property on the target using the queryable source. 
    /// </summary> 
    /// <param name="source">The source.</param> 
    /// <param name="target">The target.</param> 
    /// <param name="targetProperty">The target property.</param> 
    /// <param name="targetIdProperty">The target id property.</param> 
    /// <param name="sourceProperty">The source property.</param> 
    public static void LoadProperty(this IQueryable source, object target, string targetProperty, string targetIdProperty = null, string sourceProperty = null) 
    { 
     var targetType = target.GetType(); 
     targetType = targetType.FindElementType() ?? (targetType.Assembly.IsDynamic && targetType.BaseType != null ? targetType.BaseType : targetType); 

     // find the property on the source so we can do queryable.Where(i => i.???) 
     var sourceType = source.ElementType; 
     PropertyInfo sourcePropertyInfo; 
     if (sourceProperty == null) 
     { 
      sourcePropertyInfo = sourceType.GetProperty(targetType.Name + "Id") ?? sourceType.GetProperty(targetType.Name + "ID") ?? sourceType.GetProperty("Id") ?? sourceType.GetProperty("ID"); 
     } 
     else 
     { 
      sourcePropertyInfo = sourceType.GetProperty(sourceProperty); 
     } 

     if (sourcePropertyInfo == null || sourcePropertyInfo.GetIndexParameters().Length != 0) throw new InvalidOperationException("Could not resolve id property on source {0}.".FormatWith(source.ElementType.Name)); 


     // find the property on the target so we can find the relevant source objects via queryable.Where(i => i.Property == ???) 
     PropertyInfo targetIdPropertyInfo; 
     if (targetIdProperty == null) 
     { 
      targetIdPropertyInfo = targetType.GetProperty(targetProperty + "Id") ?? targetType.GetProperty(targetProperty + "ID") ?? targetType.GetProperty("Id") ?? targetType.GetProperty("Id").EnsureNotDefault("Could not resolve id property to use on {0}.".FormatWith(targetType.Name)); 
     } 
     else 
     { 
      targetIdPropertyInfo = targetType.GetProperty(targetIdProperty); 
     } 

     if (targetIdPropertyInfo == null || targetIdPropertyInfo.GetIndexParameters().Length != 0) throw new InvalidOperationException("Could not resolve id property for {0} on target {1}.".FormatWith(targetProperty, targetType.Name)); 


     var targetPropertyInfo = targetType.GetProperty(targetProperty); 
     if (targetPropertyInfo == null || targetPropertyInfo.GetIndexParameters().Length != 0) throw new InvalidOperationException("Could not find property {0} on target type {1}.".FormatWith(targetProperty, target.GetType())); 

     // go get the data and set the results. 
     if (target is IEnumerable) 
     { 
      // filter to only non loaded targets 
      var nullOrEmptyPredicate = "{0} == null".FormatWith(targetPropertyInfo.Name); 
      if (targetPropertyInfo.PropertyType.FindElementType() != null) nullOrEmptyPredicate += " or {0}.Count = 0".FormatWith(targetPropertyInfo.Name); 
      target = target.CastTo<IEnumerable>().AsQueryable().Where(nullOrEmptyPredicate).ToInferredElementTypeArray(); 

      var ids = target.CastTo<IEnumerable>().OfType<object>().Select(i => targetIdPropertyInfo.GetValue(i, null)).WhereNotDefault().Distinct().ToArray(); 

      if (!ids.Any()) return; 

      var predicate = ids.Select((id, index) => "{0} = @{1}".FormatWith(sourcePropertyInfo.Name, index)).Join(" or "); 
      // get the results in one shot 
      var results = source.Where(predicate, ids.ToArray()).ToInferredElementTypeArray().AsQueryable(); 

      foreach (var targetItem in target.CastTo<IEnumerable>()) 
      { 
       SetResultsOnTarget(results, targetItem, sourcePropertyInfo, targetIdPropertyInfo, targetPropertyInfo); 
      } 
     } 
     else 
     { 
      // only fetch if not loaded already 
      var value = targetPropertyInfo.GetValue(target, null); 
      if (value == null || value.As<IEnumerable>().IfNotNull(e => e.IsNullOrEmpty())) 
      { 
       SetResultsOnTarget(source, target, sourcePropertyInfo, targetIdPropertyInfo, targetPropertyInfo); 
      } 
     } 

    } 

    /// <summary> 
    /// Sets the results on an individual target entity. 
    /// </summary> 
    /// <param name="source">The source.</param> 
    /// <param name="target">The target.</param> 
    /// <param name="sourcePropertyInfo">The source property info.</param> 
    /// <param name="targetIdPropertyInfo">The target id property info.</param> 
    /// <param name="targetPropertyInfo">The target property info.</param> 
    private static void SetResultsOnTarget(IQueryable source, object target, PropertyInfo sourcePropertyInfo, PropertyInfo targetIdPropertyInfo, PropertyInfo targetPropertyInfo) 
    { 
     var id = targetIdPropertyInfo.GetValue(target, null); 

     var results = source.Where("{0} = @0".FormatWith(sourcePropertyInfo.Name), id).As<IEnumerable>().OfType<object>().ToArray(); 

     var targetPropertyElementType = targetPropertyInfo.PropertyType.FindElementType(); 
     if (targetPropertyElementType != null) 
     { 
      // add all results 
      object collection = targetPropertyInfo.GetValue(target, null); 

      if (collection == null) 
      { 
       // instantiate new collection, otherwise use existing 
       collection = Activator.CreateInstance(typeof(List<>).MakeGenericType(targetPropertyElementType)); 
       targetPropertyInfo.SetValue(target, collection, null); 
      } 

      var addMethod = collection.GetType().GetMethods().FirstOrDefault(m => m.Name == "Add" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsAssignableFrom(targetPropertyElementType)).EnsureNotDefault("Could not find add method for collection type."); 

      foreach (var result in results) 
      { 
       if (!Enumerable.Contains((dynamic)collection, result)) addMethod.Invoke(collection, new[] { result }); 
      } 
     } 
     else 
     { 
      targetPropertyInfo.SetValue(target, results.FirstOrDefault(), null); 
     } 
    } 
} 
1

내 응용 프로그램의 UI 측면에 대한 모든 데이터 액세스에 직선 EF4.0을 사용하지만 보고서의 경우 거의 항상 'EF 방식'을 포기하고 저장 프로 시저 및/또는 뷰를 제공하므로 복잡하고 시간이 많이 소요되는 모든 로직/롤업을 DB 서버에서시기 적절하게 처리 할 수 ​​있습니다. 성능은 항상 이런 식으로 훌륭합니다.

EF가 귀하의 필요에 맞게 충분히 빨리 처리 할 수없는 경우 고려해야 할 사항.

관련 문제