有没有一种方法可以用Moq来模拟DbSet.Find方法?

C. *_*son 4 c# generics entity-framework moq entity-framework-6

我目前正在使用扩展方法来将DbSets作为列表进行模拟:

    public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
    {
        var queryable = sourceList.AsQueryable();
        var mockDbSet = new Mock<DbSet<T>>();
        mockDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
        mockDbSet.Setup(x => x.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
        mockDbSet.Setup(x => x.Remove(It.IsAny<T>())).Returns<T>(x => { if (sourceList.Remove(x)) return x; else return null; } );

        return mockDbSet.Object;
    }
Run Code Online (Sandbox Code Playgroud)

但是,我想不出一种模拟Find方法的方法,该方法基于表的主键进行搜索。我可以在每个表的特定级别上执行此操作,因为我可以检查数据库,获取PK,然后仅模拟该字段的Find方法。但是我不能使用通用方法。

我想我也可以添加到EF自动生成的部分类中,以标记具有属性或某些内容的PK是哪个字段。但是我们有100多个表,如果您依靠人们手动维护此代码,那么它将使代码的管理更加困难。

EF6是否提供任何查找主键的方式,还是仅在连接到数据库后才动态知道?

Tim*_*Tim 7

据我所知,这个问题没有“最佳实践”的答案,但我是这样处理的。我在AsDbSet标识主键的方法中添加了一个可选参数,然后Find可以轻松地模拟该方法。

public static DbSet<T> AsDbSet<T>(this List<T> sourceList, Func<T, object> primaryKey = null) where T : class
{
    //all your other stuff still goes here

    if (primaryKey != null)
    {
        mockSet.Setup(set => set.Find(It.IsAny<object[]>())).Returns((object[] input) => sourceList.SingleOrDefault(x => (Guid)primaryKey(x) == (Guid)input.First()));
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

我写这篇文章的前提是假设将单个 guid 用作主键,因为这似乎是您的工作方式,但是如果您需要对复合键等具有更大的灵活性,那么该原则应该很容易适应。

  • 做得好!建议:如果您使用`primaryKey(x).Equals(input.First())` 而不是`(Guid)primaryKey(x) == (Guid)input.First()` 它可能适用于其他类型的primaryKey,例如整数等,而不是特别适用于 Guid。 (3认同)

C. *_*son 5

经过一段时间的思考,我认为我找到了当前可用的“最佳”解决方案。我只有一系列if语句,它们直接检查扩展方法中的类型。然后,我强制转换为需要设置查找行为的类型,并在完成后将其强制转换回泛型。它只是伪通用的,但是我想不到其他更好的了。

        if (typeof(T) == typeof(MyFirstSet))
        {
            mockDbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns<object[]>(x => (sourceList as List<MyFirstSet>).FirstOrDefault(y => y.MyFirstSetKey == (Guid)x[0]) as T);
        }
        else if (typeof(T) == typeof(MySecondSet))
        {
            mockDbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns<object[]>(x => (sourceList as List<MySecondSet>).FirstOrDefault(y => y.MySecondSetKey == (Guid)x[0]) as T);
        }
        ...       
Run Code Online (Sandbox Code Playgroud)