You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#/EF Core中Mock AddMilliseconds()或(object)类型转换?

EF Core 7单元测试Mock时类型转换异常问题解决

问题背景

我有一个SQL Server数据库,表结构如下:

StartDate: date (可为null)
EndDate: date (可为null)
PreferredFromMs : bigint (可为null)
PreferredToMs : bigint (可为null)

开始/结束日期时间在SQL Server端计算:日期部分(date类型)加上以毫秒存储的时间部分(bigint类型),二者分开保存。

该查询在EF Core 7应用中运行正常,但单元测试Mock时抛出异常:

System.InvalidCastException: 'Unable to cast object of type 'System.TimeSpan' to type 'System.Int64'

查询代码如下:

var data = query
               .Where(r => routeStart <= ((DateTime)(object)r.EndDate.Value).AddMilliseconds(r.PreferredToMs == null ? 0 : (long)(object)r.PreferredToMs.Value)                     
                           && routeEnd >= ((DateTime)(object)r.StartDate.Value).AddMilliseconds(r.PreferredFromMs == null ? 0 : (long)(object)r.PreferredFromMs.Value))                
               .ToList();

这些(object)类型转换看似不合理,但却是必要的:

  • EndDate是date类型,无时间部分。如果不转换就调用AddMilliseconds(),Linq-to-entities无法正确转换表达式(需要datetime而非date类型)
  • PreferredToMs是bigint类型,无法直接作为TimeSpan使用,否则表达式会被转换为DATEPART(millisecond, [r].[PreferredToMs]) AS bigint),而DATEPART()无法处理bigint,会导致:

Arithmetic overflow error converting expression to data type datetime

我的Mock设置如下:

IQueryable<Data> _fakeData = new List<Data>(1) { _myData }.AsQueryable();
mock.Mock<IMyRepository>().Setup(x => x.Query()).Returns(_fakeData);

请问如何正确处理这些(object)或(long)类型转换?或者是否应该Mock AddMilliseconds()方法?


解决方案

方案1:重构查询逻辑,避免强制类型转换

把日期和时间的拼接逻辑封装成计算属性或数据库标量函数映射,既能让EF Core正确转换,也能让内存Mock数据正常运行。

方式A:实体类添加计算属性(配合EF Core映射)

在Data实体类中添加两个计算属性:

public DateTime? FullStartDate => StartDate.HasValue 
    ? StartDate.Value.AddMilliseconds(PreferredFromMs ?? 0) 
    : null;

public DateTime? FullEndDate => EndDate.HasValue 
    ? EndDate.Value.AddMilliseconds(PreferredToMs ?? 0) 
    : null;

修改查询逻辑:

var data = query
    .Where(r => routeStart <= r.FullEndDate && routeEnd >= r.FullStartDate)
    .ToList();

如果要让EF Core将该属性映射为数据库计算列,需在上下文配置中添加:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Data>()
        .Property(d => d.FullStartDate)
        .HasComputedColumnSql("CAST([StartDate] AS DATETIME) + DATEADD(MILLISECOND, ISNULL([PreferredFromMs], 0), 0)");
    
    modelBuilder.Entity<Data>()
        .Property(d => d.FullEndDate)
        .HasComputedColumnSql("CAST([EndDate] AS DATETIME) + DATEADD(MILLISECOND, ISNULL([PreferredToMs], 0), 0)");
}

这样内存Mock数据会直接使用计算属性逻辑,不会出现类型转换异常。

方式B:映射SQL Server标量函数

创建SQL Server标量函数:

CREATE FUNCTION dbo.GetFullDateTime(@date DATE, @ms BIGINT)
RETURNS DATETIME
AS BEGIN
    RETURN CAST(@date AS DATETIME) + DATEADD(MILLISECOND, ISNULL(@ms, 0), 0)
END

在EF Core中映射该函数:

public static class DbFunctionsExtensions
{
    [DbFunction("GetFullDateTime", "dbo")]
    public static DateTime GetFullDateTime(DateTime date, long? ms)
    {
        return date.AddMilliseconds(ms ?? 0);
    }
}

修改查询:

var data = query
    .Where(r => routeStart <= DbFunctionsExtensions.GetFullDateTime(r.EndDate.Value, r.PreferredToMs)
               && routeEnd >= DbFunctionsExtensions.GetFullDateTime(r.StartDate.Value, r.PreferredFromMs))
    .ToList();

这种方式下,数据库查询会调用SQL函数,内存Mock查询会执行C#函数实现,两边逻辑一致。

方案2:改用EF Core内存数据库代替Mock

直接使用EF Core内存数据库模拟真实查询场景,避免Mock带来的表达式树差异:

var options = new DbContextOptionsBuilder<MyDbContext>()
    .UseInMemoryDatabase(databaseName: "TestDb")
    .Options;

using var context = new MyDbContext(options);
context.Data.Add(_myData);
context.SaveChanges();

var repository = new MyRepository(context);
var data = repository.Query()
    .Where(r => routeStart <= ((DateTime)(object)r.EndDate.Value).AddMilliseconds(r.PreferredToMs == null ? 0 : (long)(object)r.PreferredToMs.Value)
               && routeEnd >= ((DateTime)(object)r.StartDate.Value).AddMilliseconds(r.PreferredFromMs == null ? 0 : (long)(object)r.PreferredFromMs.Value))
    .ToList();

这种方式最贴近真实运行环境,完全规避Mock时的类型转换问题。

方案3:调整Mock数据的类型处理

如果不想重构查询,需确保Mock数据的类型匹配:

  • 确保_myData中的EndDate/StartDate是有效的DateTime(EF Core中date类型映射为DateTime,时间部分默认0)
  • 确保PreferredFromMs/PreferredToMs为long?类型,避免装箱后的类型转换异常

也可以使用EntityFrameworkCore.Testing这类专门的EF Core测试库,它能更好地处理EF Core表达式转换逻辑,减少内存与数据库查询的差异。


内容的提问来源于stack exchange,提问作者T Sl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 16:23:27