【问题标题】:linq to entity datediff fails unit test, but not sql datalinq to entity datediff 未通过单元测试,但不是 sql 数据
【发布时间】:2017-12-19 16:29:59
【问题描述】:

我有一个 LINQ to entity 单元测试,需要获取员工在一周内工作的每一天的 timeOut - timeIn 并将它们加在一起。该方法在拉取sql数据时很好,但在单元测试中出现错误:“只能从LINQ to Entities调用此函数。”

这似乎是一件很简单的事情,但是我尝试了很多东西,都无法通过测试。不想添加大量代码只是为了测试,因为那会破坏测试的目的(在几年前的类似问题中提到过)。我想知道是否有任何更新的方法可以在不添加会使方法复杂化的大量代码的情况下使其工作。

方法:

    #region CalculateTimeToPay(payrollWeekEnd)
    public IQueryable<CalculatedHours> CalculateTimeToPay(DateTime payrollWeekStart, DateTime payrollWeekEnd)
    {
        var uow = container.Resolve<WWIncomeTaxDataHandlerUnitOfWork>();

        var employeeHours = (from time in uow.Accounting.Repository.Find<V_Time>()
                             where (payrollWeekEnd >= time.PayTimeIn && time.PayTimeIn >= payrollWeekStart)
                             group time by new { EmployeeID = time.EmployeeID} into empGroup
                             select new CalculatedHours
                             {
                                 EmployeeID = empGroup.Key.EmployeeID,
                                 //TimeToPay = (empGroup.Sum(x => (SqlFunctions.DateDiff("second", x.PayTimeIn, x.PayTimeOut)) / 60.0 / 60.0)) ?? 0
                                 //TimeToPay = empGroup.Sum(x => x.PayTimeOut - x.PayTimeIn) //empGroup.Sum(x => (SqlFunctions.DateDiff("second", x.PayTimeIn, x.PayTimeOut)) / 60.0 / 60.0) ?? 0

                                 TimeToPay = (empGroup.Where(x => x.EmployeeID == empGroup.Key.EmployeeID).Sum(x => (SqlFunctions.DateDiff("second", x.PayTimeIn, x.PayTimeOut))/60.0/60.0)) ?? 0
                             });

        //var employeeHours = (from time in uow.Accounting.Repository.Find<V_Time>()
        //                     where (payrollWeekEnd >= time.PayTimeIn && time.PayTimeIn >= payrollWeekStart)
        //                     group time by new { time.EmployeeID } into empGroup
        //                     select new CalculatedHours
        //                     {
        //                         EmployeeID = empGroup.Key.EmployeeID,
        //                         //TimeToPay = (empGroup.Sum(x => (SqlFunctions.DateDiff("second", x.PayTimeIn, x.PayTimeOut)) / 60.0 / 60.0)) ?? 0
        //                         TimeToPay = (empGroup.Sum(x => (SqlFunctions.DateDiff("second", x.PayTimeIn, x.PayTimeOut)) / 60.0 / 60.0)) ?? 0

        //                         //TimeToPay = (empGroup.Where(x => x.EmployeeID == empGroup.Key.EmployeeID).Sum(x => (SqlFunctions.DateDiff("second", x.PayTimeIn, x.PayTimeOut))/60.0/60.0)) ?? 0
        //                     }); 

        return employeeHours;
    }
    #endregion

测试:

    #region CalculateTimeToPay_IntegerEmployeeIDAndDateTimeWeekOfDate_IQueryableTimeToPay
    [TestMethod]
    public void CalculateTimeToPay_IntegerEmployeeIDAndDateTimeWeekOfDate_IQueryableTimeToPay()
    {
        //Arrange
        var service = new WWIncomeTaxDataHandlerService("ProdSQL");
        DateTime payrollWeekEnd = Convert.ToDateTime("2017-01-15");
        DateTime payrollWeekStart = Convert.ToDateTime("2017-01-09");

        var V_Times = new List<V_Time>()
        {
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-05 08:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-05 16:00:00.000"), EmployeeID = 999 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-05 08:30:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-05 16:00:00.000"), EmployeeID = 777 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-05 08:45:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-05 16:00:00.000"), EmployeeID = 888 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-10 08:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-10 16:00:00.000"), EmployeeID = 999 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-10 08:30:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-10 16:00:00.000"), EmployeeID = 777 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-10 08:45:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-10 16:00:00.000"), EmployeeID = 888 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-11 08:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-11 16:00:00.000"), EmployeeID = 999 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-11 00:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-11 07:00:00.000"), EmployeeID = 777 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-11 08:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-11 16:00:00.000"), EmployeeID = 888 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-12 08:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-12 16:00:00.000"), EmployeeID = 999 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-12 08:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-12 16:44:00.000"), EmployeeID = 777 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-12 08:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-12 16:00:00.000"), EmployeeID = 888 },
            new V_Time { PayTimeIn = Convert.ToDateTime("2017-01-14 08:00:00.000"), PayTimeOut = Convert.ToDateTime("2017-01-14 16:07:00.000"), EmployeeID = 777 }
        };

        var mockRepository = new Mock<IRepository>();
        mockRepository.Setup(x => x.Find<V_Time>()).Returns(V_Times.AsQueryable());
        var builder = BuildContainer();
        builder.Register(x => mockRepository.Object).As<IRepository>();
        var container = builder.Build();

        var itrs = container.Resolve<WWIncomeTaxDataHandler.Domain.WWIncomeTaxDataHandlerService>();

        var expected = new List<CalculatedHours>()
        {
            new CalculatedHours { EmployeeID = 999, TimeToPay = 24 },
            new CalculatedHours { EmployeeID = 777, TimeToPay = 31.35},
            new CalculatedHours { EmployeeID = 888, TimeToPay = 23.25}
        };

        //Act
        var actual = itrs.CalculateTimeToPay(payrollWeekStart, payrollWeekEnd).ToList();

        //Assert
        var compareLogic = new CompareLogic();
        var result = compareLogic.Compare(actual, expected);
        Assert.IsTrue(result.AreEqual, result.DifferencesString);
    }
    #endregion

【问题讨论】:

    标签: c# entity-framework linq unit-testing


    【解决方案1】:

    为了在内存收集中获取不同的日期,像这样使用

    var diffInSeconds = (dateTime1 - dateTime2).TotalSeconds;
    

    您正在使用的功能是使用 sql 数据库而不是内存集合。


    它失败是因为您依赖floatdecimal 的值,我建议您尝试Math.Round(val,2) 之类的方法并检查结果,但是比较两位小数或浮点数总是有问题,更好的放置值将为您提供整数值没有小数部分。

    以下是双重比较的一个示例,这会导致输出值 false 在您的情况下发生同样的事情。

    // Initialize two doubles with apparently identical values
    double double1 = .33333;
    double double2 = 1/3;
    // Compare them for equality
    Console.WriteLine(double1.Equals(double2));    // displays false
    

    你可以在这里查看:Double.Equals Method (Double)

    我正在删除你的这部分代码

    TimeToPay = (empGroup.Where(x => x.EmployeeID == 
     empGroup.Key.EmployeeID).Sum(x => (SqlFunctions.DateDiff("second",
     x.PayTimeIn, x.PayTimeOut))/60.0/60.0)) ?? 0
    

    【讨论】:

    • 不,它失败了,因为规范方法 SqlFunctions.DateDiff 调用仅在 L2E 查询中受支持。
    • @IvanStoev - 我不是说它因为那个功能而失败我说它因为这个计算而失败diffvalue/60.0/60.0)) ?? 0..意味着因为你正在做的分裂
    • 我是说因为这个功能它失败了:)
    • @IvanStoev - SqlFunctions.DateDiff 函数只能与数据库一起使用...您应该将它与内存中的集合数据一起使用..
    【解决方案2】:

    您提到了 LINQ to 实体;因此,我建议在您的测试和代码中尝试 Dbfunctions (我之前遇到过这个问题,并且该开关产生了很大的不同。这是一个相关的问题,它导致了其他相关问题:SqlFunctions vs DbFunctions )

    但是,DbFunctions 没有 DateDiff,因此您必须使用 DiffSeconds

    因此,您的行将更改为:

    TimeToPay = (empGroup.Where(x => x.EmployeeID == empGroup.Key.EmployeeID).Sum(x => (DbFunctions.DiffSeconds(x.PayTimeIn, x.PayTimeOut))/60.0/60.0)) ?? 0
    

    注意:这仅适用于查询提供程序被充分模拟的测试。最好使用Effort.EF6here's a post 关于如何插入)或 EF Core 的内存数据库。你可以去尝试模拟你自己的提供者,但是仅仅为了让这些测试工作,那将是一个糟糕的主意。

    此外,一旦您切换到足够的内存数据上下文,而不是模拟 Find() 方法,您可以将列表保存到数据库中,然后像往常一样查询它。

    【讨论】:

    • 这与 OP 有相同的问题 - “此函数只能从 LINQ to Entities 调用。” 在单元测试中执行时。
    • 这会返回相同的错误。我可能引用不正确吗? TimeToPay = (empGroup.Where(x =&gt; x.EmployeeID == empGroup.Key.EmployeeID).Sum(x =&gt; (System.Data.Entity.DbFunctions.DiffSeconds(x.PayTimeIn, x.PayTimeOut)) / 60.0 / 60.0)) ?? 0
    • 哦...是的,您的参考可能是正确的。我做了一个很大的假设——在我的测试中,我使用Effort.EF6 来模拟我的 DbContext 效果很好。当我尝试以我认为你已经完成的方式模拟存储库时,我得到了完全相同的错误:(回到绘图板。PS,你尝试过 Effort 吗?
    • 我没有。是否可以同时使用可查询和组合?我有大约 15 个使用存储库的其他单元测试/方法。如果它更健壮,我可以更改它们,尽管这是最后一块拼图。所以最好不要哈哈。
    • 不幸的是,您必须更改所有这些测试,因为 List 上的 AsQueryable() 不会返回正确的提供程序(它应该返回 System.Data.Entity.Linq.QueryProvider)。不过从好的方面来说,由于它是拼图的最后一块,它是你最后一次必须做出改变;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-15
    • 1970-01-01
    • 2010-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多