【问题标题】:IValidatableObject is useless for EF navigation properties?IValidatableObject 对 EF 导航属性没用?
【发布时间】:2012-04-24 14:35:23
【问题描述】:

IValidatableObject.Validate 仅在实现实体 DbEntityEntry.State 不同于“未更改”时调用。并且仅仅改变一个导航属性不会改变状态,所以验证永远不会发生。

为什么微软总是发布半生不熟的测试版?

我什至无法手动检测导航属性的变化:

var changes = context.ChangeTracker.Entries()
    .Where(e => e.State != EntityState.Unchanged)
    .ToArray();

返回一个空数组。

【问题讨论】:

    标签: .net validation ef-code-first entity-framework-4.3


    【解决方案1】:

    这里有几个有趣的点。 EntityFramework 独立于实体的更改跟踪导航属性的更改。 context.ChangeTracker.Entries() 仅返回对实体的更改,而不是对关系的更改。这就是你看不到这些的原因。如果您真的想查看关系以及它们是如何变化的,您可以下拉到 ObjectContext 并执行以下操作:

    var objectContext = ((IObjectContextAdapter) ctx).ObjectContext;
    foreach(var relationshipEntry in objectContext.ObjectStateManager
                                                  .GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Deleted)
                                                  .Where(e => e.IsRelationship))
    {
        EntityKey entityKey1, entityKey2;
    
        if (relationshipEntry.State == EntityState.Added)
        {
            entityKey1 = (EntityKey)relationshipEntry.CurrentValues[0];
            entityKey2 = (EntityKey)relationshipEntry.CurrentValues[1];            
        }
        else
        {
            entityKey1 = (EntityKey)relationshipEntry.OriginalValues[0];
            entityKey2 = (EntityKey)relationshipEntry.OriginalValues[1];                        
        }
    
        var entity1 = objectContext.GetObjectByKey((EntityKey)entityKey1);
        var entity2 = objectContext.GetObjectByKey((EntityKey)entityKey2);
    }
    

    我更感兴趣的场景是当关系发生变化时需要重新验证实体的场景。我假设您没有外键 - 否则您的实体将被标记为已修改,因为更改导航属性会更改外键的值,这反过来会将实体标记为已修改。无论如何,鉴于我有三个有效实体 - 改变关系可能使实体(或模型)无效的情况是什么?另外,请注意,验证本身仅验证给定实体,但从不遵循导航属性来验证相关实体。 最后,如果您真的需要在关系发生变化时验证实体,我认为您有 4 个选项:

    • 尝试添加外键,以便更改关系将更改应将实体标记为已修改的外键(免责声明:我没有尝试过)

    • 覆盖 DbContext.ShouldValidateEntity() 方法以验证所有实体,而不是仅验证修改的实体(这是过滤逻辑发生的地方)。请注意,它可能会对性能产生一些负面影响。以下是您需要添加到从 DbContext 派生的类中的代码:

            protected  override bool ShouldValidateEntity(DbEntityEntry entityEntry)
            {
                return (entityEntry.State & EntityState.Deleted) == 0;
            }
    
    • 覆盖 DbContext.SaveChanges() 以便为所有修改的实体和涉及修改的关系的所有实体调用验证(使用上面的代码,您很可能只对已添加的关系条目感兴趣)

    • 当您修改集合时,手动将实体标记为已修改。请注意,这可能会导致向数据库发送不需要的更新,因此根据您跟踪的实体数量,验证所有未删除的实体可能会便宜得多

    您可以在此处找到有关验证和验证自定义的更多详细信息: http://blogs.msdn.com/b/adonet/archive/2010/12/15/ef-feature-ctp5-validation.aspx http://blogs.msdn.com/b/adonet/archive/2011/05/27/ef-4-1-validation.aspx (是的,它适用于 CTP 和 EF 4.1,但仍然有效)

    【讨论】:

    • 如果我有很多关系怎么办?我有一个业务/对象规则,它说:如果布尔属性 A 为真,则集合 B 不允许为空。这不是对象级验证的一部分吗?我应该在不同的地方这样做吗?
    • 顺便说一句:现在它已经添加了外键,ObjectStateManager 不再返回任何关系条目(实体本身,更准确地说,外键属性现在被标记为已更改,正如您所期望的)
    • 1-* 在这种情况下是一个非常有趣的场景。原因是在集合中添加和删除实体实际上不会对“1”侧的实体(即主体)产生任何影响 - 如果您查看数据库,则相应的表将没有外键相关实体,因此它甚至不知道它们的存在。然而,在相关表中,将添加(或删除)具有指向主体的外键(但不是其他方式)的行。如果没有修改任何值,那么主体是否真的被修改了?
    • 我在您可能要考虑的列表中添加了一个选项。一般来说,我认为在您的情况下,最简单的事情是验证所有实体,而不管状态如何(好的。您不需要验证已删除的实体)-我使用显示的代码 sn-p 更新了响应如何做到这一点。
    • 谢谢。我知道数据库级别的主要“实体”没有变化。但是由于我正在使用 POCO“实体”,因此我的验证针对这些对象。所以我决定重写 ShouldValidateEntity 并且还在上下文中添加了一个属性 ForceValidation:return this.ForceValidation ? ( entityEntry.State & EntityState.Deleted ) == 0 : base.ShouldValidateEntity(entityEntry);;这样,它只在需要时发生,并且所有相关实体都被真正加载(大多数时候 LazyLoading 被禁用)。
    猜你喜欢
    • 2012-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-18
    • 1970-01-01
    • 2014-06-12
    • 2014-01-20
    相关资源
    最近更新 更多