C# 获取IQueryable LINQ to Entities查询中的字典值

C# 获取IQueryable LINQ to Entities查询中的字典值,c#,.net,entity-framework,linq,linq-to-entities,C#,.net,Entity Framework,Linq,Linq To Entities,我必须在生产应用程序中支持多种语言 有许多实体框架查询从数据库中获取数据作为延迟的IQueryable列表,如下所示: public IQueryable<Request> GetDeferredRequests() { return _dbContext.Set<Request>(); } public partial class Request { public int RequestID { get; set; } public s

我必须在生产应用程序中支持多种语言

有许多实体框架查询从数据库中获取数据作为延迟的IQueryable列表,如下所示:

public IQueryable<Request> GetDeferredRequests()
{
    return _dbContext.Set<Request>();
}   
public partial class Request
{
    public int RequestID { get; set; }

    public string StatusName { get; set; }

    public string RequestType { get; set; }
}
public class RequestDTO
{
    public int RequestID { get; set; }

    public string StatusName { get; set; }

    public string RequestType { get; set; }
}
数据传输对象如下所示:

public IQueryable<Request> GetDeferredRequests()
{
    return _dbContext.Set<Request>();
}   
public partial class Request
{
    public int RequestID { get; set; }

    public string StatusName { get; set; }

    public string RequestType { get; set; }
}
public class RequestDTO
{
    public int RequestID { get; set; }

    public string StatusName { get; set; }

    public string RequestType { get; set; }
}
之后,我将EF POCO实体映射到数据传输对象。为了支持多种语言,我希望通过映射中的数据库值获得资源值,如下所示:

public IQueryable<RequestDTO> MapRequests(IQueryable<Request> requests)
{
      Dictionary<string, string> resoures = new Dictionary<string, string>();

      System.Resources.ResourceSet resources = DatabaseResources.ResourceManager.GetResourceSet(new System.Globalization.CultureInfo("en"), true, true);

      foreach (DictionaryEntry resource in resources)
      {
          resoures.Add(resource.Key.ToString(), resource.Value.ToString());
      }

      return requests.Select(c => new RequestDTO()
      {
          RequestID = c.RequestID,
          StatusName =  resoures.Single(r => r.Key == c.StatusName).Value,
          RequestType = resoures.Single(r => r.Key == c.RequestType).Value
      });
}

不幸的是,通过ToList()将IQueryable转换为IEnumerable不是一个选项,因为我不想将列表移动到内存中

你必须意识到IQueryable和IEnumerable的区别

数不清 实现IEnumerable的对象表示一个序列。它保存了获取序列的第一个元素所需的所有内容,一旦获得了一个元素,就可以获取下一个元素,只要存在一个元素

在最低级别,通过调用GetEnumerator()并重复调用MoveNext()完成此序列的枚举。每次MoveNext返回true时,就有一个元素。可以使用当前属性访问此元素

很少在这个最低级别进行枚举。通常使用foreach或其中一个不返回IEnumerable的LINQ函数进行枚举:ToList()、Count()、Any()、FirstOrDefault()等。在最深层,它们都调用GetEnumerator和MoveNext/Current

易变的 虽然实现IQueryable的对象看起来像IEnumerable,但它并不表示对象本身的序列。它表示创建IEnumerable序列的潜力

为此,IQueryable包含一个表达式和一个提供程序。表达式表示必须查询的数据。提供者知道向谁查询日期(通常是数据库管理系统)以及该DBMS使用什么语言(通常是某种SQL)

串联IQueryable LINQ语句不会执行查询。它只会改变表达式。要执行查询,需要开始枚举

一旦开始使用GetEnumerator枚举IQueryable,表达式将发送给提供者,后者将表达式转换为SQL并在DBMS上执行查询。返回的数据表示为IEnumerable,并调用其GetEnumerator

这和我的问题有什么关系? 问题是,提供程序不知道您的函数
MapRequests
。因此,它无法将其转换为SQL。事实上,即使是几个标准LINQ函数也无法转换为SQL。看

可计算 解决此问题的一种方法是将所选数据移动到本地进程。本地进程知道函数MapRequests并知道如何执行它

可以使用ToList()将数据移动到本地进程。但是,如果在此之后只需要几个元素,如Take(3)或FirstOrDefault(),那么这将是对处理能力的浪费

一个可计算的来营救

您的提供者知道一个可计算的。它会将数据移动到本地进程。一些哑提供者将通过获取所有数据来实现这一点。更智能的提供商将“每页”获取数据。一个页面由查询数据的子集组成,例如,只有50行。如果只使用FirstOrDefault(),这仍然是一种浪费,但至少不会吸引数百万客户

如果您将MapRequests更改为扩展方法,那就太好了。看

但是请注意,这些语句不是由数据库管理系统执行的,而是由本地进程执行的

DBMS可以映射我的请求吗? 但它或许可以。您必须将资源传输到数据库,并使用简单的数据库函数将请求转换为RequestDTO。如果与您必须转换的请求数量相比,有很多资源,那么这样做可能是不明智的。但是,例如,如果您必须使用100个资源转换数千个请求,并且在转换后您将使用另一个表执行
Where
,或
GroupJoin
,那么让DBMS执行转换可能是明智的

似乎每个资源都有一个键和一个值

  • StatusName应具有键等于request.StatusName的资源值
  • RequestType应具有键等于request.RequestType的资源值
因此,让我们将MapRequests重写为IQeryable的扩展方法:

public IQueryable<RequestDTO> ToRequestDto( this IQueryable<Request> requests,
      IEnumerable<KeyValuePair<string, string>> resources)
{
     // TODO: exception if requests == null, resources == null

     return requests.Select(request => new RequestDTO
     {
         RequestId = request.RequestId,

         // from resources, keep only the resource with key equals to StatusName
         // and select the FirstOrDefault value:
         StatusName = resources
                      .Where(resource => resource.Key == request.StatusName)
                      .Select(resource => resource.Value)
                      .FirstOrDefault(),
         // from resources, keep only the resource with key equals to RequestType
         // and select the FirstOrDefault value:
         RequestType = resources
                      .Where(resource => resource.Key == request.RequestType)
                      .Select(resource => resource.Value)
                      .FirstOrDefault(),
     }
public IQueryable to requestdto(此IQueryable请求,
(可数资源)
{
//TODO:请求==null,资源==null时异常
返回请求。选择(request=>newrequestdto
{
RequestId=request.RequestId,
//从resources中,仅保留key等于StatusName的资源
//并选择第一个默认值:
StatusName=资源
.Where(resource=>resource.Key==request.StatusName)
.Select(resource=>resource.Value)
.FirstOrDefault(),
//从资源中,仅保留键等于RequestType的资源
//并选择第一个默认值:
RequestType=资源
.Where(resource=>resource.Key==request.RequestType)
.Select(resource=>resource.Value)
.FirstOrDefault(),
}
用法:

IEnumerable<RequestDto> requestDTOs = GetDeferredRequests()

    // only if you don't want all requests:
    .Where(request => ...)

    // move to local process in a smart way:
    AsEnumerable()

    // Convert to RequestDTO:
    .ToRequestDTO();
IEnumerable<KeyValuePair<string, string> resources = ...
var requestDTOs = GetDeferredRequests()
    .Where(request => ...)
    .ToRequestDTO(resources)

    // do other database processing
    .GroupJoin(myOtherTable, ...)
    .Where(...)
    .Take(3);
IEnumerable…)
.ToRequestDTO(资源)
//进行其他数据库处理
.GroupJoin(myOtherTable,…)
.其中(…)
.采取(3);
现在,完整的语句将由数据库管理系统执行。
大多数数据库管理系统在从序列中选择特定项方面比您的流程优化得多。此外,这看起来更整洁。

您必须了解IQueryable和IEnumerable的区别

数不清 实现的对象