我的 C#.NET LINQ 表达式是否针对延迟执行进行了优化实体框架核心

问题描述

我正在为客户构建一个使用 C#.NET 的 REST API,它将用于从数据库中检索错误日志。该数据库具有三个表:Fault、Message 和 MessageData。表的关系如下:

故障

意味着一个故障可以有多个消息从消息表链接到它,而消息表又可以有一个消息数据从消息数据表链接到它。

我使用 Entity Framework Core 创建了表示这些表的实体模型。我还为每个实体模型创建了 DTO,仅包含与网络传输相关的数据。在我的存储库类中,我使用 LINQ 向数据库写入查询,将结果从我的实体模型映射到我的 DTO。

我的问题是是否可以重写此代码以使其更充分,特别是在延迟执行方面(不想对数据库进行任何不必要的往返):

public async Task<IEnumerable<FaultDTO>> GetFaultsAsync(string? application,DateTime? fromDate,DateTime? toDate,int? count)
        {
            List<FaultDTO> faults;

            fromDate = (fromDate == null) ? DateTime.Today.AddDays(-30) : fromDate;
            toDate = (toDate == null) ? DateTime.Today : toDate;
            count = (count == null) ? 10 : count;

            faults = await _context.Faults.Where(fault => String.IsNullOrWhiteSpace(application) ? fromDate <= fault.InsertedDate && fault.InsertedDate <= toDate : fault.Application.ToLower() == application.ToLower() && fromDate <= fault.InsertedDate && fault.InsertedDate <= toDate).Select(fault => new FaultDTO()
            {
                FaultId = fault.FaultId,InsertedDate = fault.InsertedDate,MachineName = fault.MachineName,ServiceName = fault.ServiceName,Scope = fault.Scope,FaultDescription = fault.FaultDescription,Messages = _context.Messages.Where(msg => fault.FaultId == msg.FaultId).Select(msg => new MessageDTO()
                {
                    MessageId = msg.MessageId,MessageName = msg.MessageName,MessageData = _context.MessageData.Where(msgData => msg.MessageId == msgData.MessageId).Select(msgData => new MessageDataDTO()
                    {
                        MessageData = msgData.MessageData,MessageId = msgData.MessageId
                    }).SingleOrDefault(),FaultId = fault.FaultId,}).ToList()
            }).OrderByDescending(fault => fault.InsertedDate).Take((int)count).ToListAsync<FaultDTO>();

            return faults;
        }

另外,如果有人可以澄清查询是在最后对数据库执行的 ('.ToListAsync();'),还是在此阶段部分执行了 3 次:'.ToList()','.SingleOrDefault( )' 和 '.ToListAsync()?

如前所述,主要关注点是延迟执行。话虽如此,我很乐意收到任何关于在总体性能方面优化我的代码的建议。

解决方法

GetFaultsAsync 将始终获得所有 Faults ...

首先,您将所有故障放在一个列表中,然后丢弃这些信息。如果用户(= 软件,而不是操作员)想知道故障的数量,他们必须进行不同的查询,或者 Count() 所有元素。一种改进是返回一个 ICollection<Fault>,这样人们就可以 Count。如果需要,他们甚至可以添加/删除故障。

当然你可以返回 IList<Fault>,但恕我直言,索引没有意义:

IList<Fault> fetchedFaults = await GGetFaultsAsync(...)
Fault fault4 = fetchedFaults[4];

数字 4 毫无意义。因此,我的建议是返回 ICollection<Fault>IReadonlyCollection<Fault>,如果您不希望人们向获取的数据添加/删除项目。另一方面,如果这意味着人们会将获取的数据复制到新列表中,为什么不让他们更改最初获取的数据?

另一个改进:

var fetchedFaults = await GGetFaultsAsync(...)
var faultsToProcess = fetchedFaults.Take(3);

获取所有 10.000 个错误,然后只使用其中的前 3 个是多么浪费!

在使用 LINQ 时,明智的做法是尽可能长时间地保持返回值 IQueryable / IEnumerable。让查询的用户决定是否要添加其他 LINQ 语句以及何时实现它们:`ToList() / FirstOrDefault() / Count() / 等等。

执行此操作的最简单方法是创建一个将 IQueryable<Fault> 作为输入并返回 IQueryable<FaultDto> 的扩展方法。如果您不熟悉扩展方法,请参阅 Extension methods demystified

public static IQueryable<FaultDto> GetFaults(this IQueryable<Fault> faults,string? application,DateTime? fromDate,DateTime? toDate)
{
    return faults.Where(fault => ...)
        .Select(fault => new FaultDto
        {
            FaultId = fault.FaultId,InsertedDate = fault.InsertedDate,...
        });
}

用法:

string? application = ...
DateTime? fromDate = ...
DateTime? toDate = ...
using (var dbContext = new MyDbContext())
{
    var result = await dbContext.Faults.GetFaults(application,fromDate,toDate)
        .GroupBy(fault => fault.MachineName)
        .Take(10)
        .ToListAsync();
    this.Process(result);
}

优点:

  • 用户可以连接其他 LINQ 语句
  • 您获取的项目不会超过实际使用的数量。
  • 调用者决定是否使用 async/await。
  • 调用者决定他想要使用的错误序列

后者的例子:

var result = await dbContext.Faults
    .Where(fault => fault.MachineName == "SPX100")
    .GetFaults(application,toDate);

缺点:调用者必须创建故障源(DbContext)。

存储库

如果需要,您可以隐藏错误的来源:它可以是使用实体框架的数据库,但也可以是 CSV 文件或字典(用于单元测试?),也可以是 REST 调用在互联网上?

如果你想要这个,创建一个“存储库”类。用户所知道的只是您可以将项目放入存储库,然后再次获取它们,即使在程序重新启动后也是如此。存储库隐藏使用实体框架访问项目。

如果您只需要查询项目,请创建一个具有 IQueryable<...> 属性的存储库。如果要使用此存储库类添加/删除项目,请使用 ICollection<...>IDbSet<...> 属性。但请注意,后一种解决方案限制了更改内部结构的可能性。

class Repository : IDisposable
{
    private readonly MyDbContext dbContext = new MyDbContext(...);

    public IQueryable<Fault> Faults => dbContext.Faults;
    public IQueryable<Message> Messages => dbContext.Messages;
    ...

    // Dispose() disposes the DbContext
}

用法:

using (Repository repository = new Repository()
{
    var result = await repository.Faults.GetFaults(application,toDate)
        .GroupBy(fault => fault.MachineName)
        .Take(10)
        .ToListAsync();
    this.Process(result);
}

使用 Repository 的另一个好处,你可以给不同的用户不同的 Repository:有些用户只想查询项目,有些用户需要添加/删除/更改项目,而只有超级用户需要创建或更改表。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...