实体框架单元测试(或类似测试),以确保在使用DbSet时,始终调用特定的扩展方法



我想强制执行一个要求,即在任何时候与数据库交互。用户,您需要调用特定的扩展方法。以下是一些更具体的信息。

当通过实体框架与我的数据库交互时,我有以下查询(db是DbContext实例):

var user = db.Users
              .FromOrganisation(someId)
              .FirstOrDefault(u => u.Username == someUsername);

在整个应用程序中,这个查询会一直变化(WHERE子句通常会不同,等等)。因此,我想要求您必须始终调用FromOrganisation()扩展方法,以便所有返回的数据首先由Organisation过滤。

这是为了防止任何人看到属于不同组织的数据,但我一直纠结于如何实现它

有没有一个单元测试我可以写来提醒开发人员,用户数据库集是在没有组织过滤的情况下使用的?如果没有,我有没有其他途径可以达到同样的保护水平。

如果它很重要,扩展方法本身看起来是这样的:

public static IQueryable<User> FromOrganisation(this IQueryable<User> u, int organisationId)
{
    return u.Where(x => x.OrganisationId == organisationId);
}

我的最终解决方案

我更改了我的上下文如下:

public DbSet<User> Users { get; set; }

名称:

[Obsolete("MUST use service!")]
public DbSet<User> UsersUnfiltered { get; set; }

public IQueryable<User> Users(int id)
{
    #pragma warning disable 618
    return UsersUnfiltered.Where(x => x.OrganisationId == id);
    #pragma warning restore 618
}

这样做的目的是鼓励您使用Users方法返回按组织筛选的用户列表。这可以像往常一样进行进一步的过滤、连接和查询等。

如果需要,您也可以访问UsersUnfiltered DbSet,但如果使用它,则会生成编译器警告。您可以通过在#pragma warning-disable618指令中访问它来抑制此警告。

有了这个,你就有了代码,可以防止你在没有按组织过滤的情况下使用用户数据,除非你真的想这样做。

感谢@mark_h帮助我找到这个解决方案。

您提出的建议的一个问题是,如果您可以强制使用它,那么每次您想要子查询一组用户时,即使在您从上下文(即)进行了初始查询之后,也必须强制使用它

var allUsersOfOrganisation1 = db.Users.FromOrganisation(1)
// some code...
var someUsersOfOrganisation1 = allUsersOfOrganisation1.FromOrganisation(1).Where(...)

如果强制使用扩展方法,如何区分对包含所有用户的集合的查询和筛选的子集?

您可以通过包装实体模型并仅通过强制执行唯一组织id的方法公开用户来实现您想要的结果;

public class WrappedEntities
{
    private MyEntityFramework Entities = new MyEntityFramework();
    //regular public entities
    public DbSet<Organisation> Organisations { get { return Entities.Organisation; } set { Entities.Organisation = value as DbSet<Organisation>; } }
    //Special case
    public IQueryable<User> Users(int id)
    {
        return Entities.Users.Where(x => x.OrganisationId == organisationId);
    }
    //other wrapped methods...
    public int SaveChanges()
    {
        return Entities.SaveChanges();
    }
}

如果你给这个类一个接口,你也可以为了单元测试的目的模拟它。

最新更新