EF模型验证与数据库的对比

5

我想使用EF 5模型验证来避免数据库中的重复值,因此我使用了一个像这样的模型类:

[Table("MeasureUnits")]
public class MeasureUnit : IValidatableObject
{
    public int MeasureUnitId { get; set; }

    public string Symbol { get; set; }

    public string Name { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        using (MeasureUnitRepository rep = new MeasureUnitRepository())
        {
            MeasureUnit measureUnit = rep.FindDuplicateBySymbol(this);

            if (measureUnit != null)
                yield return new ValidationResult(
                    "There is another unit with this symbol, you can't duplicate it", 
                    new[] { "Symbol" });
        }
    }

存储库类创建DbContext,实现IDisposable,具有查找重复项的逻辑,并且所有内容都按预期工作。
然而,使用调试器后我发现每次插入或更新时都会执行两次验证,因此存储库(和DbContext)也会被实例化和释放两次。
除此之外,在控制器中还有另一个DbContext,但只是不知道如何在模型类中使用它,除了将DbContext包含在模型的构造函数中,但我感觉这不是正确的解决方案。
是否有更好或“正确”的方法来实现此验证?
提前致谢。

1
根据您想如何处理验证错误,创建数据库中的唯一索引可能更容易且性能更好(但是如果存在重复项,则会收到UpdateException而不是验证异常)。 - Pawel
@Pawel 我会使用索引来加速 FindDuplicateBy...,但我更喜欢在更改发布之前捕获它。然而,现在你提到了它,我意识到我还必须处理它,因为在验证之后但实际提交之前发生重复是有可能的。 (+1) - Miguel Veloso
2个回答

2

当你需要访问数据库时,需要使用DbContext,它有一个可重写的方法叫做ValidateEntity。详见这篇文章:Entity Framework Validation

我在另一个答案中放置了我使用的代码

更多有关我如何在MVC中构建验证结构的信息,请查看此处。

另外,在仓储库中实例化上下文可能会引起问题。仓储库将需要共享上下文。您可以将上下文视为工作单元并将其传递给仓储库的构造函数,或者您可以将上下文封装在自己的工作单元内并将其传递进去。


我不喜欢在DbContext中为所有模型编写重复逻辑(覆盖ValidateEntity),但是看起来我可以使用ValidateEntity的“items”参数来获取“something”(通过ValidationContext将DbContext、Repository或我的UnitOfWork)传递到模型中,因此我不需要实例化DbContext或Repository,而是在每个模型中使用它,这样逻辑就会在其应该存在的地方(依我之见)了。这看起来正确吗? - Miguel Veloso
1
我在另一个问题的评论中放置的链接提供了解决方案,但我认为让POCO依赖于DbContext、Repositories或Units Of Work比在上下文中进行验证更糟糕。归根结底,验证是一个横切关注点。有时它在客户端,有时它在Web服务器上,有时它在数据库中。通常情况下,它在这三个方面都存在。 - Colin
@ Bond 我很遗憾只能检查一个答案,因为它们的组合是正确的,但这就是现实,谢谢你们两个。 - Miguel Veloso

1
你可以使用任何IOC容器,例如UnityNinjectAutofacStructureMap来将仓库注入为依赖项。
这样,您就可以在控制器、验证方法或任何需要使用它的地方访问相同的对象。
其中一些IOC(Ninject肯定是,寻找“请求范围”)容器能够与ASP.NET MVC集成,以便在每个请求中创建依赖项(在该情况下为您的存储库),并在请求结束时处理。
使用Ninject的示例:
您可以创建一个全局可访问的(设计由您决定)ninject内核。
public static class NinjectKernel
{
    public static IKernel Kernel = new StandardKernel();
    static NinjectKernel()
    {
        Kernel.Bind<IMyRepository>().To<MyRepositoryImpl>().InRequestScope();
    }
}

和MVC控制器相关的控制器工厂

public class NinjectControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext,
    Type controllerType)
    {
        return controllerType == null ? null : (IController)NinjectKernel.Kernel.Get(controllerType);
    }
}

您可以在Global.asax中设置控制器工厂,方法如下:
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());

在您的验证方法中以类似于控制器工厂的方式获取存储库。

我在IOC方面并没有做过什么重要的事情,但我知道我必须在模型类构造函数中包含存储库,然后IOC会处理,是这样吗?然而,我并不完全理解,您介意给一些例子吗? - Miguel Veloso
1
是的 - IOC 将负责在您的控制器构造函数中注入一个新对象,并在请求的生命周期内每次需要时返回同一对象。示例取决于您选择使用的 IOC 容器,我将在几分钟内更新我的答案,提供 Ninject 示例。 - Ventsyslav Raikov
如果我没错的话,关键是我需要将仓库(repository)注入到模型(MODEL)类中,而不是控制器(CONTROLLER)类中,是这样吗?还是我完全偏离了目标? - Miguel Veloso
1
你也可以在你的MODEL类中访问它,但通常不会使用IOC容器创建你的模型(虽然这也是可能的)。 - Ventsyslav Raikov
1
@MiguelVeloso - 没错。这样做不是一个好的设计,你最终会得到一个混乱的代码库,考虑到所有需要即时创建模型的框架(如EF和MVC的模型绑定器)。 - Ventsyslav Raikov
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接