繁体   English   中英

EF6:使用IQueryable的参考/查找数据

[英]EF6: Use reference/lookup data with 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 => contact.StatusId == l.Id)”与List类型一样,是打开的。

任何帮助表示赞赏。

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

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

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

  2. 写入InterceptingQueryable<T> (让我们这样命名) IOrderedQueryable<T> ,它包装来自EF的IQueryable<T>对象(在您的示例中由Select方法返回)。

  3. 编写IQueryProvider<T> InterceptingQueryProvider<T>实现,后者又包装从EF获取的查询提供程序。

  4. 写入IEnumerator<T> InterceptingEnumerator<T>实现,它继承EF返回的枚举器对象。 这个枚举器将在执行MoveNext后立即注入Status属性的值(可以很容易地通用化以便以这种方式填充任何查找属性),以便完全填充Current返回的对象。

上述链连接如下:

  1. InterceptingQueryable中继到EF的查询对象,在构造函数中传递。

  2. InterceptingQueryable.Provider属性返回InterceptingQueryProvider

  3. InterceptingQueryable.GetEnumerator方法返回InterceptingEnumerator

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

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

UPDATE

这是代码:

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);
        }
    }
}

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

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);
}

打印:

--- SOME CONTACTS ---
1: John Smith, Acme Corp, 3=Status Three
3: Jon Snow, The Wall, 2=Status Two

并将查询转换为:

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[FullName] AS [FullName], 
    [Extent1].[CompanyName] AS [CompanyName], 
    [Extent1].[StatusId] AS [StatusId]
    FROM [dbo].[Contacts] AS [Extent1]
    WHERE [Extent1].[FullName] LIKE 'Jo%'
    ORDER BY [Extent1].[FullName] ASC, [Extent1].[CompanyName] ASC

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

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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM