2012-03-04 13 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を行うことができない場合は、何かを検討してください。

関連する問題