【问题标题】:How to make LINQ to EF and LINQ to ordinary collection return the same values?如何使 LINQ to EF 和 LINQ to 普通集合返回相同的值?
【发布时间】:2020-10-15 12:06:49
【问题描述】:

我使用 LINQ to EF。

我有一个 Item 实体,它有一个 Purchases 集合。

并非所有物品都有购买。我想做一个 Select 查询,当商品没有购买时 - 它为 LastPurchaseQuantity(int?) 和 LastPurchaseDate(DateTime?) 返回 null。

我做了以下 LINQ SELECT 查询。

当我在 EF 的应用程序中运行它时,它会返回我想要的结果。但是当我尝试对其进行单元测试时 - 它返回 LastPurchaseQuantity 和 LastPurchaseDate 的默认值 - 最小日期和 0。

我应该如何优化我的查询,使其在 EF 和单元测试中的工作方式相同?它应该返回值或 null。

我想我不需要 FirstOrDefault,而是 FirstOrNull() 方法。

        var query = _itemsRepository.All()
            .Where(x => x.UserId == userId)
            .Select(x => new ItemOverviewDto()
            {
                Id = x.Id,
                Name = x.Name,
                ReplenishmentPeriod = x.ReplenishmentPeriod,
                NextReplenishmentDate = x.NextReplenishmentDate,

                LastReplenishmentDate = x.Purchases
                                 .OrderByDescending(y => y.ReplenishmentDate)
                                 .Select(m => m.ReplenishmentDate)
                                 .FirstOrDefault(),

                LastReplenishmentQuantity = x.Purchases
                                 .OrderByDescending(y => y.ReplenishmentDate)
                                 .Select(m => m.Quantity)
                                 .FirstOrDefault(),
            });

        return query.ToList();

编辑:对于我的单元测试,我通过了一个假的 IQueryable 集合。

var allItems = BuildItemsCollection();
ItemsRepositoryMock.Setup(x => x.All()).Returns(allItems);

【问题讨论】:

  • 别这样,unittest 不是用来查询数据库的。为什么要在单元测试中从数据库中获取值?
  • 我在单元测试中不使用数据库。我使用 Moq 将假的 IQueryable 集合传递给测试。
  • 好的,然后添加您的单元测试代码并解释您如何模拟存储库。
  • 我添加了单元测试的代码。

标签: c# entity-framework linq


【解决方案1】:

我看到的唯一选择是将类型转换添加到可空类型:

        LastReplenishmentDate = x.Purchases
                         .OrderByDescending(y => y.ReplenishmentDate)
                         .Select(m => (DateTime?) m.ReplenishmentDate)
                         .FirstOrDefault(),

        LastReplenishmentQuantity = x.Purchases
                         .OrderByDescending(y => y.ReplenishmentDate)
                         .Select(m => (int?) m.Quantity)
                         .FirstOrDefault(),

【讨论】:

    【解决方案2】:

    您的 BuildItemsCollection 方法是否设置返回的 Items 包括填充的 Purchases 集合? EF 会将 Linq 表达式转换为 SQL,因此行为可能与查询对象时有所不同。

    在定义具有集合导航属性的实体时,一个建议是始终初始化这些导航属性:

    public class Item
    {
         // Item fields...
    
         public virtual ICollection<Purchase> Purchases { get; set; } = new List<Purchase>();
    }
    

    这只是有助于避免在访问新项目的购买时出现空引用异常。但是,在您的情况下,请检查 BuildItemsCollection 是否正在填充购买:

    private IQueryable<Item> BuildItemsCollection()
    {
        var items = new List<Item> ( new [] 
        {
             new Item 
             {
                 // ....
                 Purchases = new List<Purchase> ( new []   
                 { 
                     new Purchase // Expected purchase..
                     {
                         // ..
                     },
                     new Purchase 
                     {
                         // ..
                     }
                 }
            },
            new Item 
            {
                 // ...
                 Purchases = new List<Purchase> ( new []   
                 { 
                     new Purchase // Expected purchase..
                     {
                         // ..
                     },
                 }
            }
        }
        return items.AsQueryable();
    }
    

    更新:

    在使用内存集与数据库时,以数据库排序方式和您通过字符串执行的任何过滤的方式处理时的另一个注意事项。根据您的数据库排序规则设置,数据库可能在不区分大小写模式下运行(SQL Server 的默认模式),因此查询如下:

    .Where(x => x.Name = "george")
    

    当对一个名称为“George”的表运行 CI 排序规则时,这将返回一条记录。当针对内存中的IQueryable 填充对象运行时,它不会。 (如果/在测试数据过滤器时值得考虑)

    【讨论】:

    • 是的,我已经完全按照您的描述初始化了 Item 实体中的 Purchases 集合。在 BuildItemsCollection() 方法中,我仅将 Purchases 分配给某些项目 - 这是故意的,因为某些项目没有购买是正常的。使用初始化的项目 - 没有问题 - 他们按预期将最后购买日期返回给 Dto。我描述的问题只出现在 Purchases 集合为空的 Items 中。
    猜你喜欢
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多