EF:使用延迟加载的必需属性时,验证失败

Die*_*hon 69 entity-framework ef-code-first

鉴于这个非常简单的模型:

public class MyContext : BaseContext
{
    public DbSet<Foo> Foos { get; set; }
    public DbSet<Bar> Bars { get; set; }
}

public class Foo
{
    public int Id { get; set; }
    public int Data { get; set; }
    [Required]
    public virtual Bar Bar { get; set; }
}

public class Bar
{
    public int Id { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

以下程序失败:

object id;
using (var context = new MyContext())
{
    var foo = new Foo { Bar = new Bar() };
    context.Foos.Add(foo);
    context.SaveChanges();
    id = foo.Id;
}
using (var context = new MyContext())
{
    var foo = context.Foos.Find(id);
    foo.Data = 2;
    context.SaveChanges(); //Crash here
}
Run Code Online (Sandbox Code Playgroud)

有了DbEntityValidationException.找到的消息EntityValidationErrorsBar字段是必需的..

但是,如果我Bar通过添加以下行强制加载属性SaveChanges:

var bar = foo.Bar;
Run Code Online (Sandbox Code Playgroud)

一切正常.如果我删除该[Required]属性,这也有效.

这真的是预期的行为吗?是否有任何变通方法(除了每次我想要更新实体时加载每个必需的引用)

Xha*_*ent 54

我发现以下帖子有同样问题的答案:

导致此问题的原因是在RC和RTM验证中不再延迟加载任何属性.之所以进行此更改,是因为在保存大量具有延迟加载属性的实体时,验证会逐个获取这些实体,从而可能导致大量意外事务并降低性能.

解决方法是在使用.Include()保存或验证之前显式加载所有经过验证的属性,您可以在此处阅读有关如何执行此操作的更多信息:http: //blogs.msdn.com/b/adonet/archive/2011/01 /31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

我对此的看法是,这是一个非常糟糕的代理实现.虽然不必要地走对象图并重新启动延迟加载的属性自然是有待避免的(但在微软的EF的第一个版本中显然被忽略了),你不必需要去代理包装器来验证它是否存在.再想一想,我不知道为什么你还需要走对象图,当然ORM的变化跟踪器知道哪些对象需要验证.

我不确定为什么会出现问题,但我确信如果我使用说NHibernate,我就不会遇到这个问题.

我的"解决方法" - 我所做的是在EntityTypeConfiguration类中定义关系的必需属性,并删除Required属性.这应该使它工作正常.这意味着您不会验证关系,但它将无法更新.不是理想的结果.

  • 在废话代理实现的协议上升级. (13认同)
  • 谢谢你的回答.这已经成为我在很长一段时间内看到的最愚蠢的错误.怎么有人认为这对ORM来说是可以接受的? (6认同)
  • 我最终编写了一个通用的`LoadAllReferences`方法.我不能对EF感到失望. (5认同)
  • 我很失望学习这个.是不是通过删除虚拟来使所有必需的导航属性非延迟的另一种解决方法? (2认同)
  • @CarlG.如果您将所有引用设置为非延迟,那么您最终会从数据库中检索不确定数量的对象,其中任何数量的对象实际上都需要特定的工作单元.这就是延迟加载的原因. (2认同)
  • 是的,框架确实知道Bar字段没有被更改,因此不需要检查.EF是一个笑话.我希望我没有选择它,现在切换为时已晚,但我永远不会再使用它. (2认同)

小智 44

好的,这是真正的答案=)

先来一点解释

如果你有一个属性(比如你的Bar)注意到FK(ForeignKey),你也可以在你的模型中有相应的FK字段,所以如果我们只需要FK而不是实际,Bar我们不需要它去数据库:

[ForeignKey("BarId")]
public virtual Bar Bar { get; set; }
public int BarId { get; set; }
Run Code Online (Sandbox Code Playgroud)

现在,为了回答你的问题,你能做些什么来让BarRequired是标志BarId为所需的属性,而不是Bar它本身:

[ForeignKey("BarId")]
public virtual Bar Bar { get; set; }
[Required] //this makes the trick
public int BarId { get; set; }
Run Code Online (Sandbox Code Playgroud)

这就像一个魅力=)

  • 外键在POCO中做什么?是不是DB泄漏到模型中? (9认同)
  • 这样做的问题是,在创建新的Foo()时,您需要设置Bar和BarId属性,如果您只设置Bar属性,那么您将无法在BarId上进行所需的验证.此外,BarId需要可为空,以使所需的属性起作用. (2认同)

Gui*_*ume 8

透明的解决方法,以忽略卸载的引用上的错误

在您的DbContext,覆盖ValidateEntity方法中删除未加载的引用上的验证错误.

    private static bool IsReferenceAndNotLoaded(DbEntityEntry entry, string memberName)
    {
        var reference = entry.Member(memberName) as DbReferenceEntry;
        return reference != null && !reference.IsLoaded;
    }

    protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry,
                                                 IDictionary<object, object> items)
    {
        var result = base.ValidateEntity(entityEntry, items);
        if (result.IsValid || entityEntry.State != EntityState.Modified)
        {
            return result;
        }
        return new DbEntityValidationResult(entityEntry,
            result.ValidationErrors
                  .Where(e => !IsReferenceAndNotLoaded(entityEntry, e.PropertyName)));
    }
Run Code Online (Sandbox Code Playgroud)

优点:

  • 使用继承,复杂类型时,透明并且不会崩溃,不需要在模型上进行修改......
  • 仅在验证失败时
  • 没有反思
  • 仅对无效的已卸载引用进行迭代
  • 没有无用的数据加载

  • 在我看来,这是解决这个问题的最佳方案.简单并避免数据库的开销往返. (3认同)

fri*_*ism 5

这是一个半可接受的解决方案:

var errors = this.context.GetValidationErrors();
foreach (DbEntityValidationResult result in errors) {
    Type baseType = result.Entry.Entity.GetType().BaseType;
    foreach (PropertyInfo property in result.Entry.Entity.GetType().GetProperties()) {
        if (baseType.GetProperty(property.Name).GetCustomAttributes(typeof(RequiredAttribute), true).Any()) {
            property.GetValue(result.Entry.Entity, null);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)