I got a problem with the use of EF for creating an update request when it comes to fk. I want to modify a navigation field in an existing entity, without loading it before. So the entity is not pre-loaded in the context, and not proxyied;
For the example, i have a simple 1-* relationship which give me this two entities:
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; }
}
When i modify some scalar properties, i do the following:
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();
}
And EF only modify the Name in the table. (1) No modification are given to the MainLang field, but validation fails if the value is set to null (seems that the fact MainLang field is not nullable in db implies it cant be null when validation is calculated)
Now, i want to modify the Title of a Name. Same as before, i tried:
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();
}
This method failed and throw this error:
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
I tried lot of idea, like:
_context.Entry(nmToModify).Property(a => a.TitleMap).IsModified = true;
but TitleMap is not a property to Ef, so he don't want it; sounds fair.
My main question is: How to solve partial updating when it comes to navigation properties ?
Note: if its possible, i don't want to expose TitleMapId in NameMap.
Bonus question: is there a way to not have to initialise the not null fields, as i show it in (1)
Thanks for your help.