EF6:使用IQueryable

时间:2015-11-22 05:45:46

标签: c# linq entity-framework-6 lookup iqueryable

我想使用查询中列表中的预加载查找数据。我需要将查询作为IQueryable返回,因为它用于网格&被分页(不包括在这里)。我需要从查找中加载标签以避免连接(更多是在实际代码中)。

我知道我可以做一个ToList()并对其进行后处理,但我需要IQueryable。这是代码:

// Run in intialization other code...
var contactLookups = new ContactsDbContext();
List<StatusType> _statusTypes = contactLookups.StatusTypes.ToList();


    public IQueryable GetQuery()
    {
        var contactsCtx = new ContactsDbContext();
        IQueryable query = contactsCtx.Select(contact => new
        {
            Id = contact.Id,
            FullName = contact.FirstName + " " + contact.LastName,
            CompanyName = contact.CompanyName,
            Status = _statusTypes.FirstOrDefault(l => contact.StatusId == l.Id).Label
        });
        return query;
    }

上面的代码抛出一个错误,因为EF / LINQ无法将内存列表形成为SQL(出于显而易见的原因) - 除非添加/更改了某些内容。

我想以某种方式告诉EF在SQL之后应用查找或其他东西。我读过关于使用EF Helper代码和表达式做类似的事情,但我找不到那篇文章。

注意,我对查找本身很灵活。唯一不可协商的是“contact.StatusId”(int),但结构的其余部分“_statusTypes.FirstOrDefault(l =&gt; contact.StatusId == l.Id)”与List类型一样,是打开的。

感谢任何帮助。

2 个答案:

答案 0 :(得分:3)

您可以将EF查询包装到您自己的IQueryable拦截实现中,您可以在将对象返回应用程序之前注入内存中查找的值。

听起来可能很复杂,但实际上并不难实现。需要完成以下工作:

  1. 将您实体中的Status属性标记为非映射(使用带有Fluent API的Ignore()或属性上的[NotMapped]属性)。

  2. InterceptingQueryable<T>(让我们这样命名)IOrderedQueryable<T>的实现,它包装来自EF的IQueryable<T>对象(由你的Select方法返回)例)。

  3. 编写InterceptingQueryProvider<T>的{​​{1}}实现,后者又包含从EF获取的查询提供程序。

  4. IQueryProvider<T> InterceptingEnumerator<T>的实现,它继承EF返回的枚举器对象。 此枚举器将在执行IEnumerator<T>后立即注入Status属性的值(可以通过这种方式轻松推广以填充任何查找属性),以便MoveNext返回的对象完全填充。

  5. 以上链接如下:

    1. Current转发到EF的查询对象,在构造函数中传递。

    2. InterceptingQueryable属性返回InterceptingQueryable.Provider

    3. InterceptingQueryProvider方法返回InterceptingQueryable.GetEnumerator

    4. InterceptingEnumerator方法从EF查询提供程序获取查询对象,然后将其包装在另一个InterceptingQueryProvider.CreateQuery实例中。

    5. InterceptingQueryable在EF查询提供程序上调用InterceptingQueryProvider.Execute,然后在获得一个受查询注入的实体的情况下,它以与{{{{{{{{{{{{ 1}}(提取重用方法)。

    6. <强>更新

      以下是代码:

      Execute

      这是测试用例/示例。首先,我们不应忘记将未映射的Status属性添加到Contact实体:

      InterceptingEnumerator

      然后,我们可以使用拦截器机制如下:

      using System;
      using System.Collections;
      using System.Collections.Generic;
      using System.Linq;
      using System.Linq.Expressions;
      using System.Text;
      using System.Threading.Tasks;
      
      namespace Examples.Queryables
      {
          public class InterceptingQueryable<T> : IOrderedQueryable<T>
          {
              private readonly Action<T> _interceptor;
              private readonly IQueryable<T> _underlyingQuery;
              private InterceptingQueryProvider _provider;
      
              public InterceptingQueryable(Action<T> interceptor, IQueryable<T> underlyingQuery)
              {
                  _interceptor = interceptor;
                  _underlyingQuery = underlyingQuery;
                  _provider = null;
              }
              public IEnumerator<T> GetEnumerator()
              {
                  return new InterceptingEnumerator(_interceptor, _underlyingQuery.GetEnumerator());
              }
              IEnumerator IEnumerable.GetEnumerator()
              {
                  return GetEnumerator();
              }
              public Expression Expression 
              {
                  get { return _underlyingQuery.Expression; }
              }
              public Type ElementType 
              {
                  get { return _underlyingQuery.ElementType; }
              }
              public IQueryProvider Provider 
              {
                  get
                  {
                      if ( _provider == null )
                      {
                          _provider = new InterceptingQueryProvider(_interceptor, _underlyingQuery.Provider); 
                      }
                      return _provider;
                  }
              }
      
              private class InterceptingQueryProvider : IQueryProvider
              {
                  private readonly Action<T> _interceptor;
                  private readonly IQueryProvider _underlyingQueryProvider;
      
                  public InterceptingQueryProvider(Action<T> interceptor, IQueryProvider underlyingQueryProvider)
                  {
                      _interceptor = interceptor;
                      _underlyingQueryProvider = underlyingQueryProvider;
                  }
                  public IQueryable CreateQuery(Expression expression)
                  {
                      throw new NotImplementedException();
                  }
                  public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
                  {
                      var query = _underlyingQueryProvider.CreateQuery<TElement>(expression);
      
                      if ( typeof(T).IsAssignableFrom(typeof(TElement)) )
                      {
                          return new InterceptingQueryable<TElement>((Action<TElement>)(object)_interceptor, query);
                      }
                      else
                      {
                          return query;
                      }
                  }
                  public object Execute(Expression expression)
                  {
                      throw new NotImplementedException();
                  }
                  public TResult Execute<TResult>(Expression expression)
                  {
                      var result = _underlyingQueryProvider.Execute<TResult>(expression);
      
                      if ( result is T )
                      {
                          _interceptor((T)(object)result);
                      }
      
                      return result;
                  }
              }
      
              private class InterceptingEnumerator : IEnumerator<T>
              {
                  private readonly Action<T> _interceptor;
                  private readonly IEnumerator<T> _underlyingEnumerator;
                  private bool _hasCurrent;
      
                  public InterceptingEnumerator(Action<T> interceptor, IEnumerator<T> underlyingEnumerator)
                  {
                      _interceptor = interceptor;
                      _underlyingEnumerator = underlyingEnumerator;
                      _hasCurrent = false;
                  }
                  public void Dispose()
                  {
                      _underlyingEnumerator.Dispose();
                  }
                  public bool MoveNext()
                  {
                      _hasCurrent = _underlyingEnumerator.MoveNext();
      
                      if ( _hasCurrent )
                      {
                          _interceptor(_underlyingEnumerator.Current);
                      }
      
                      return _hasCurrent;
                  }
                  public void Reset()
                  {
                      _underlyingEnumerator.Reset();
                  }
                  public T Current 
                  {
                      get
                      {
                          return _underlyingEnumerator.Current;
                      }
                  }
                  object IEnumerator.Current
                  {
                      get { return Current; }
                  }
              }
          }
      
          public static class QueryableExtensions
          {
              public static IOrderedQueryable<T> InterceptWith<T>(this IQueryable<T> query, Action<T> interceptor)
              {
                  return new InterceptingQueryable<T>(interceptor, query);
              }
          }
      }
      

      打印:

      public partial class Contact
      {
          [NotMapped]
          public StatusType Status { get; set; }
      }
      

      并将查询翻译成:

      var contactLookups = contactsCtx.StatusTypes.ToList();
      
      Action<Contact> interceptor = contact => {
          contact.Status = contactLookups.FirstOrDefault(l => contact.StatusId == l.Id);
      };
      
      // note that we add InterceptWith(...) to entity set
      var someContacts = 
          from c in contactsCtx.Contacts.InterceptWith(interceptor) 
          where c.FullName.StartsWith("Jo")
          orderby c.FullName, c.CompanyName
          select c;
      
      Console.WriteLine("--- SOME CONTACTS ---");
      foreach ( var c in someContacts )
      {
          Console.WriteLine(
              "{0}: {1}, {2}, {3}={4}", 
              c.Id, c.FullName, c.CompanyName, c.StatusId, c.Status.Name);
      }
      

答案 1 :(得分:0)

我不确定避免连接的好处与无法在数据库端处理整个查询的明显缺点相比,但是你所要求的可以通过做多少来实现使用Linq to Entities尽可能(过滤,排序,分组,投影),然后将其转换为IEnumerable,并使用Linq To Objects完成剩下的工作。您始终可以使用Enumerable.AsQueryable切换到IQueryable实施IEnumerable。像这样的东西

public IQueryable GetQuery()
{
    var db = new ContactsDbContext();
    var query = db.Contacts.Select(contact => new
    {
        Id = contact.Id,
        FullName = contact.FirstName + " " + contact.LastName,
        CompanyName = contact.CompanyName,
        StatusId = contact.StatusId
    })
    .AsEnumerable()
    .Select(contact => new
    {
        Id = contact.Id,
        FullName = contact.FullName,
        CompanyName = contact.CompanyName,
        Status = _statusTypes.FirstOrDefault(l => contact.StatusId == l.Id).Label
    })
    .AsQueryable();
    return query;
}