实体框架-外键验证

时间:2018-07-31 09:09:08

标签: c# entity-framework entity-framework-6

具有以下TestClass

[Table("xyz")]
public partial class TestClass{
    [Key]
    public int key {get; set;}
    [ForeignKey("key")]
    public virtual ICollection<ExternalClass> externalClasses{get; set;}

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        ... 
    }
}

我该如何设置该类以满足我的以下要求:

  • 我想用Entity Framework单元测试来测试模型
  • 在生产中,externalClasses不应保存(应该已经在数据库中)
  • EF必须确保每个外部类都存在于DB中。如果不是,则抛出异常。

我的第一个想法是将外来对象设置为null,并在validate方法内请求数据库检查外键是否存在。但是这种方法不适用于单元测试,而且在我看来,在模型中包含数据库请求也不是很干净。

有人知道如何使用EF以干净的方式处理该问题吗?

2 个答案:

答案 0 :(得分:1)

  

我认为在模型中包含数据库请求并不是很干净

我倾向于同意,但是为了验证,我倾向于提出一个例外。为什么?

当决定由IValidatableObject完成验证时,应该尽可能唯一地验证 (这意味着Validate方法不是进行涉及其他实体的更复杂验证的最佳位置。

将“简单”验证规则的一部分包含在实体本身中,而将--well的另一部分包含在任何地方,将非常不便。这就是问题所在:它可以在任何地方。因此,在通过另一个代码路径保存实体时可以跳过它。

因此,我倾向于将执行验证的上下文提供给validationContext。这是通过覆盖ValidateEntity调用的上下文SaveChanges方法(当context.Configuration.ValidateOnSaveEnabledtrue时)完成的:

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
    items.Add("context", this);
    return base.ValidateEntity(entityEntry, items);
}

现在可以在Validate方法内获取上下文的句柄了:

var context = (MyContext)validationContext.Items["context"];

...它可用于执行数据库查询。

这应该小心处理。遵守以下三个规则是很好的:

  1. 不要运行查询来更改更改跟踪器的内容,即不要查询完整的实体,而只查询投影。在许多跟踪的实体正等待保存到数据库时执行验证。最好不要更改此跟踪实体集合中的任何内容。
  2. 不要运行繁琐的查询。
  3. 当应用程序例行地在一个工作单元中保存大量实体时,请勿执行此操作。 (在这种情况下,使用IValidatableObject并不是最好的主意。)

有了这个,就可以运行像这样的验证了:

var ids = externalClasses.Select(c => c.ID).ToList();
if (context.ExternalClasses.Any(c => !ids.Contains(c.ID))
{
    yield return new ValidationResult("Some external classes don't exist", 
        new[] { nameof(externalClasses) });
}

这将执行相对轻量级的查询,该查询仅返回布尔值,并且不将新实体附加到变更跟踪器。

答案 1 :(得分:0)

直到现在我已经解决了以下问题:

我按如下方式创建了实体类((通过使用单个异物而不是ICollection简化了示例)

[Table("xyz")]
public partial class TestClass{
    [Key]
    public int key {get; set;}
    [ForeignKey("key")]
    [Required]
    public virtual ExternalClass externalClass{get; set;}



    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        ... 
    }
}

然后在我的ServiceClass中创建新记录的地方:

public void InsertNewRecord(TestClass newClass){
    newClass.externalClass = context.Where(e => e.ID = newClass.externalClass.Id).First();
    context.Add(newClass);
    context.SaveChanges();        
}

如果找不到externalClass(外部),则将newClass.externalClass设置为null,并由于[Required]注释而引发验证错误。这样做还有一个好处,就是EF不会尝试保存异物externalClass,因为它现在识别出该条目仍然存在。