C# EF6:将引用/查找数据与IQueryable一起使用
我想使用查询中列表中预加载的查找数据。我需要查询作为IQueryable返回,因为它在网格中使用&正在分页(此处不包括)。我需要从查找中加载标签以避免连接(实际代码中有更多) 我知道我可以做一个ToList()并对其进行后期处理,但我需要IQueryable。代码如下:C# EF6:将引用/查找数据与IQueryable一起使用,c#,linq,entity-framework-6,lookup,iqueryable,C#,Linq,Entity Framework 6,Lookup,Iqueryable,我想使用查询中列表中预加载的查找数据。我需要查询作为IQueryable返回,因为它在网格中使用&正在分页(此处不包括)。我需要从查找中加载标签以避免连接(实际代码中有更多) 我知道我可以做一个ToList()并对其进行后期处理,但我需要IQueryable。代码如下: // Run in intialization other code... var contactLookups = new ContactsDbContext(); List<StatusType> _status
// 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;
}
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);
}
}
}
//在初始化其他代码中运行。。。
var contactLookups=newcontactsDBContext();
列表_statusTypes=contactLookups.statusTypes.ToList();
公共IQueryable GetQuery()
{
var contactsCtx=新的ContactsDbContext();
IQueryable query=contactsCtx.Select(contact=>new
{
Id=contact.Id,
全名=contact.FirstName+“”+contact.LastName,
CompanyName=contact.CompanyName,
Status=\u statusTypes.FirstOrDefault(l=>contact.StatusId==l.Id).Label
});
返回查询;
}
上面的代码抛出了一个错误,因为EF/LINQ无法将内存中的列表形成SQL(原因很明显),除非添加/更改某些内容
我想以某种方式告诉EF在SQL或其他类似的东西之后应用查找。我曾经读过关于使用EF帮助程序代码和表达式执行类似操作的文章,但是我再也找不到这篇文章了
注意,我对查找本身很灵活。唯一不可协商的是“contact.StatusId”(int),但与列表类型一样,“u statusTypes.FirstOrDefault(l=>contact.StatusId==l.Id)”结构的其余部分是打开的
非常感谢您的帮助。您可以将EF的查询包装到自己的IQueryable拦截实现中,在该实现中,您可以在将对象返回到应用程序之前注入内存中查找的值 这听起来可能很复杂,但实际上并不难实现。需要做以下工作:
Status
属性标记为未映射(使用Fluent API的Ignore()
或属性上的[NotMapped]
属性)InterceptingQueryable
(让我们这样命名)实现IOrderedQueryable
,它包装来自EF的IQueryable
对象(由示例中的Select方法返回)InterceptingQueryProvider
实现IQueryProvider
,它反过来包装从EF获得的查询提供程序interceptingemulator
实现IEnumerator
,它将中继到EF返回的枚举器对象此枚举数将在执行MoveNext
之后立即注入Status属性的值(可以很容易地用这种方式填充任何查找属性),以便完全填充Current
返回的对象。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);
}
InterceptingQueryable
中继到EF的查询对象,传递给构造函数InterceptingQueryable.Provider
属性返回InterceptingQueryProvider
InterceptingQueryable.GetEnumerator
方法返回InterceptingEnumerator
InterceptingQueryProvider.CreateQuery
方法从EF查询提供程序获取查询对象,然后将其包装在另一个InterceptingQueryable
实例中返回InterceptingQueryProvider.Execute
调用EF查询提供程序上的Execute
,然后在它获取要进行查找注入的实体时,它以与InterceptingEnumerator
相同的方式注入查找值(提取一个方法以供重用)// 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;
}
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);
}
}
}
然后,我们可以使用拦截器机制,如下所示:
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);
}
查询将转换为:
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对实体进行尽可能多的操作(过滤、排序、分组、投影)来实现,然后将其转换为
IEnumerable
,并使用Linq To对象执行其余操作。您始终可以使用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;
}
您能否读取contact.StatusId并稍后手动填写状态?否,查询将作为方法调用的结果绑定到网格。另外,这种类型的调用只是以上查询为例的众多调用中的一种。非常好,谢谢。如果你能举个例子就好了。我想这和我回忆的很接近。正是我想要的,谢谢你的努力!谢谢,我明天就试试。不确定它是否会破坏分页,但值得一试,因为这是一个简单的解决方案。此外,在“为什么”上,查找表完全加载到第二层缓存中,因此再次加入以检索它们是浪费和性能损失。考虑到我在这个查询中有更多的查询,为了便于理解,这个例子被简化了。我理解你得到了什么,这很好,但是试图用这种方法来警告你会失去什么。如果我的解释不清楚,上面的查询是有效的,但是
AsEnumerable
之后的所有事情都会发生在内存中,包括分页或任何进一步的处理。我尝试了这个方法&在第一次查询结束时,它作为.ToList()有效地运行,这是我一直避免的。谢谢你的努力。@SteveKinyon我没有努力,我想我警告过你两次了。您想要IQueryable
,并且得到了IQueryable
:-)如果您想要一个IQueryable
,对其应用进一步的过滤等,以及在db中发生的所有事情,那么您应该以这种方式对其进行表述。祝你好运