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

时间:2014-05-16 13:02:54

标签: c# entity-framework

我遇到了使用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只修改表中的名称。 (1)不对MainLang字段进行修改,但如果值设置为null,则验证失败(似乎事实上MainLang字段在db中不可为空,这意味着在计算验证时它不能为null)

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

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)中所示

感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

这部分异常给出了一些问题:

  

来自&#39; TitleMapNameMap&#39;的关系AssociationSet在   &#39;新增&#39;州。给定多重约束,相应的   &#39; NameMap&#39;还必须在“已添加”中添加&#39;状态。

您的关系必需,即NameMap 必须引用TitleMap。如果添加了所需的关系,则相应的从属实体(在这种情况下为NameMap)必须是新的(也在Added状态),因为现有实体已经必须具有关系和关系只能更改。 EF模型更改了关系删除旧关系,在关系管理器中添加新关系。因为状态已删除中没有关系,所以它假定从属实体只能是新的。但是,更改跟踪器中没有新实体导致异常。

现在,如果关系管理器在状态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的关系更改,并将在状态{{1}中创建两个关系条目分别在状态Deleted中。

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

使用外键关联,即通过将FK作为模型中的属性公开,这种更改关系而不知道旧外键更容易。然后改变关系只是修改标量属性。

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

WHERE NameMaps.Id = 2 AND NameMaps.TitleMap_Id = XXX

(基于this answer的构思和代码,其中还包含更多解释和详细信息。)