如何更新导航属性-不修改任何其他字段

本文关键字:修改 任何 字段 其他 属性 导航 何更新 更新 | 更新日期: 2023-09-27 18:03:34

当涉及到fk时,我遇到了使用EF创建更新请求的问题。我想修改现有实体中的导航字段,而不加载它之前。因此,实体没有在上下文中预加载,也没有被代理;

对于这个例子,我有一个简单的1-*关系,它给了我这两个实体:

public partial class NameMap
{
    public NameMap()
    {
        this.SurnameMaps = new HashSet<SurnameMap>();
    }
    public int Id { get; set; }
    public string Name { get; set; }
    public string MainLang { get; set; }
    public virtual ICollection<SurnameMap> SurnameMaps { get; set; }
    public virtual TitleMap TitleMap { get; set; }
}
public partial class TitleMap
{
    public TitleMap()
    {
        this.NameMaps = new HashSet<NameMap>();
    }
    public int Id { get; set; }
    public string Title { get; set; }
    public virtual ICollection<NameMap> NameMaps { get; set; }
}

当我修改一些标量属性时,我做以下操作:

public void PartiallyChangeEntity()
{
    var nmToModify = new NameMap()
    {
        Id = 2, //item i want to change
        Name = "This is a test", //the prop i want to change
        MainLang = String.Empty //must be init and not null - but i come back to this point later (1)
    };
    _context.NameMaps.Attach(nmToModify);
    _context.Entry(nmToModify).Property(a => a.Name).IsModified = true;
    _context.SaveChanges();
}

和EF只修改表中的Name。(1)没有修改MainLang字段,但如果值被设置为null,则验证失败(似乎MainLang字段在db中不可为空的事实意味着在计算验证时它不能为空)

现在,我想修改一个名称的标题。和以前一样,我试过:

public void PartiallyChangeEntityConstraint()
{
    var nmToModify = new NameMap()
    {
        Id = 2,
        MainLang = String.Empty //same as (1)
    };
    _context.NameMaps.Attach(nmToModify);
    var title = new TitleMap {Id = 3}
    _context.NameMaps.Attach(title);
    _context.Entry(title).Collection(a => a.NameMaps).CurrentValue.Add(nmToModify);
    _context.Entry(nmToModify).Reference(a => a.TitleMap).CurrentValue = tm;
    _context.SaveChanges();
}

此方法失败并抛出此错误:

Test Name:  PartiallyChangeEntityConstraint
Test FullName:  EFTests.UnitTest1.PartiallyChangeEntityConstraint
Test Source:    d:'TFS'EFTests'EFTests'UnitTest1.cs : line 100
Test Outcome:   Failed
Test Duration:  0:00:02.0409469
Result Message: 
Test method EFTests.UnitTest1.PartiallyChangeEntityConstraint threw exception: 
System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details. ---> System.Data.Entity.Core.UpdateException: A relationship from the 'TitleMapNameMap' AssociationSet is in the 'Added' state. Given multiplicity constraints, a corresponding 'NameMap' must also in the 'Added' state.
Result StackTrace:  
at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.RelationshipConstraintValidator.ValidateConstraints()
   at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.ProduceCommands()
   at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()
   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.<Update>b__2(UpdateTranslator ut)
   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update[T](T noChangesResult, Func`2 updateFunction)
   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update()
   at System.Data.Entity.Core.Objects.ObjectContext.<SaveChangesToStore>b__35()
   at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
   at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)
   at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass2a.<SaveChangesInternal>b__27()
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
   at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)
   at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options)
   at System.Data.Entity.Internal.InternalContext.SaveChanges()
 --- End of inner exception stack trace ---
    at System.Data.Entity.Internal.InternalContext.SaveChanges()
   at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
   at System.Data.Entity.DbContext.SaveChanges()
   at EFTests.UnitTest1.PartiallyChangeEntityConstraint() in d:'TFS'EFTests'EFTests'UnitTest1.cs:line 103

我尝试了很多想法,比如:

_context.Entry(nmToModify).Property(a => a.TitleMap).IsModified = true;

但是TitleMap不是Ef的属性,所以他不想要它;听起来很公平。

我的主要问题是:当涉及到导航属性时,如何解决部分更新?

注意:如果可能的话,我不想在NameMap中暴露TitleMapId。

附加问题:是否有一种方法可以不初始化非空字段,正如我在(1)

中所示

谢谢你的帮助。

如何更新导航属性-不修改任何其他字段

这部分异常提示了问题可能是什么:

来自'TitleMapNameMap' AssociationSet的关系在"添加"状态。给定多重约束,一个对应的"NameMap"也必须处于"已添加"状态。

您的关系是必需的,即NameMap 必须有对TitleMap的引用。如果所需的关系是添加,则相应的依赖实体(在本例中为NameMap)必须是新的(也处于Added状态),因为现有实体必须已经具有关系,并且该关系只能更改。EF模型通过删除旧关系和在关系管理器中添加新关系来改变关系。因为状态Deleted中没有关系,所以它假定依赖实体只能是新的。但是,变更跟踪器中没有新实体导致异常。

现在,如果关系管理器在状态Deleted中找到与状态Added对应的关系条目,那么问题也可能得到解决。您可以在附加之前为NameMap提供对TitleMap 的引用:

public void PartiallyChangeEntityConstraint()
{
    var nmToModify = new NameMap()
    {
        Id = 2,
        MainLang = String.Empty,
        TitleMap = new TitleMap { Id = XXX }
    };
    _context.NameMaps.Attach(nmToModify);
    var title = new TitleMap { Id = 3 }
    _context.TitleMaps.Attach(title);
    _context.Entry(nmToModify).Reference(a => a.TitleMap).CurrentValue = title;
    // You could also simply use here: nmToModify.TitleMap = title;
    // or did you disable automatic change detection?
    _context.SaveChanges();
}

EF识别将新的title分配给nmToModify实体,作为从外键XXX3的关系更改,并将分别在状态Deleted和状态Added中创建两个关系条目。

现在,最大的问题是:XXX的值是多少?它不能是3,因为那样你会得到一个异常,两个具有相同键的对象被附加,这是被禁止的。这个问题很容易规避(使用3+1或其他东西)。更大的问题是,您显然不能使用一些任意的虚拟值(如-10或其他任何东西)。它必须是当前实际存储在数据库中的NameMap记录2TitleMap的外键。否则,数据库中的UPDATE将无法工作,因为生成的SQL不仅包含NameMap的PK,还包含到TitleMap (WHERE NameMaps.Id = 2 AND NameMaps.TitleMap_Id = XXX)的FK的WHERE子句。如果没有找到要更新的记录(如果XXX不是正确的当前FK,则不会找到),则会得到并发异常。

这种在不知道旧外键的情况下更改关系的方法对于外键关联来说要容易得多,即通过将FK作为模型中的属性公开。因此,更改关系只是修改标量属性。

关于避免未更改的属性的验证异常:您必须禁用全局上下文验证,然后在属性级别手动验证(仅针对您实际想要标记为已修改的属性):

_context.Configuration.ValidateOnSaveEnabled = false;
var entry = ctx.Entry(nmToModify);
var validationErrors = entry.Property(a => a.Name).GetValidationErrors();
if (validationErrors.Count > 0)
    throw new DbEntityValidationException("An entity property is invalid.", 
        new DbEntityValidationResult[] { new DbEntityValidationResult(
            entry, validationErrors) });
entry.Property(a => a.Name).IsModified = true;

(基于此答案的想法和代码,其中还包含更多的解释和细节)