I'm using EFCore 1.1.0 in a netstandard1.6 application.
I have configured my model with a composite key
public class ClassExtension
{
public int Id { get; set; }
public string Name { get; set; }
public int ClassId { get; set; }
public MyClass Class { get; set; }
}
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public List<ClassExtension> Extensions { get; set; }
public Class()
{
Extensions = new List<ClassExtension>();
}
}
Then in my dbcontext OnModelCreating()
modelBuilder.Entity<ClassExtension>()
.HasKey(p => new { p.Id, p.ClassId });
I then get from the database the existing instance of a particular composite key, then create a new instance of ClassExtension in code based on user input and finally assign those values to the entity then save it.
var existingSpec = await db.ClassSpecs.Where(
c => c.ClassId == existingClass.Id && c.Id == input.Id)
.FirstOrDefaultAsync();
var newExtension = new ClassExtension()
{
Id = input.Id,
Name = input.Name
};
db.Entry(existingSpec).CurrentValues.SetValues(newSpec); // Errors on this line
db.Entry(existingSpec).Property(p => p.ClassId).IsModified = false;
await db.SaveChangesAsync()
The application generates the following error when trying to assign the values using SetValues()
{System.InvalidOperationException: The property 'ClassId' on entity type 'ClassExtension'
is part of a key and so cannot be modified or marked as modified.
... removed ...
at MyApplication...
When I do this on a table without a composite key it works just fine, not overwriting the existing foreign key Id with null, just using its existing value.
Is there something I'm missing, or is this just a quirk with using composite keys?
Related
I have following data model
public class SportEvent
{
[Key]
public int Id { get; set; }
public int QualifierLeaderboardId { get; set; }
public Leaderboard QualifierLeaderboard { get; set; }
public int FinalLeaderboardId { get; set; }
public Leaderboard FinalLeaderboard { get; set; }
}
public class Leaderboard
{
[Key]
public int Id { get; set; }
public List<Ranking> Rankings { get; set; }
public int LeaderboardSportEventId { get; set; }
public SportEvent LeaderboardSportEvent { get; set; }
}
One SportEvent has exactly two Leaderboards - one for the qualifier and one for the final event. One Leaderboard has exactly one SportsEvent.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SportEvent>()
.HasOne(s => s.QualifierLeaderboard)
.WithOne()
.HasForeignKey<SportEvent>(s => s.QualifierLeaderboardId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<SportEvent>()
.HasOne(s => s.FinalLeaderboard)
.WithOne()
.HasForeignKey<SportEvent>(s => s.FinalLeaderboardId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Leaderboard>()
.HasOne(l => l.LeaderboardSportEvent)
.WithOne()
.HasForeignKey<Leaderboard>(l => l.LeaderboardSportEventId)
.OnDelete(DeleteBehavior.NoAction);
}
So I configured two relations from SportEvent to Leaderboard and one navigation property on Leaderboard.
SportEvent sportEvent = new SportEvent();
Leaderboard finalLeaderboard = new Leaderboard() { LeaderboardSportEvent = sportEvent};
Leaderboard qualificationLeaderboard = new Leaderboard() { LeaderboardSportEvent = sportEvent };
sportEvent.FinalLeaderboard = finalLeaderboard;
sportEvent.QualifierLeaderboard= qualificationLeaderboard;
try
{
TestingContext context = new TestingContext();
context.SportEvents.Add(sportEvent);
context.SaveChanges();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
When I try to add data and save the changes to the database, following exception occurs.
System.InvalidOperationException: 'The association between entity types 'SportEvent' and 'Leaderboard' has been severed,
but the relationship is either marked as required or is implicitly required because the foreign key is not nullable.
If the dependent/child entity should be deleted when a required relationship is severed, configure the relationship to use cascade deletes.
Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.'
I read already a lot of other posts but was not able to solve it. Many thanks for your help.
If you assign to properties of your SportEvent its related entities, it doesn't mean you don't have to add them to the database. When you save changes, DbContext finds a broken relationship and throws an exception. You should save all entities:
SportEvent sportEvent = new SportEvent();
Leaderboard finalLeaderboard = new Leaderboard() { LeaderboardSportEvent = sportEvent, LeaderboardSportEventId = sportEvent.Id };
Leaderboard qualificationLeaderboard = new Leaderboard() { LeaderboardSportEvent = sportEvent, LeaderboardSportEventId = sportEvent.Id };
sportEvent.FinalLeaderboard = finalLeaderboard;
sportEvent.FinalLeaderboardId = finalLeaderboard.Id;
sportEvent.QualifierLeaderboard = qualificationLeaderboard;
sportEvent.QualifierLeaderboardId = qualificationLeaderboard.Id;
try
{
TestingContext context = new TestingContext();
context.SportEvents.Add(sportEvent);
context.Leaderboards.Add(finalLeaderboard);
context.Leaderboards.Add(qualificationLeaderboard);
context.SaveChanges();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
I've got a domain model with collection of owned types. When I try to add more than one object in the ownedtyped collection? I get an exception:
System.InvalidOperationException: 'The instance of entity type 'ChildItem' cannot be tracked because another
instance with the key value '{NameId: -2147482647, Id: 0}' is already being
tracked. When replacing owned entities modify the properties without changing
the instance or detach the previous owned entity entry first.'
How can it be solved?
UPDATED
My domain classes:
public class Parent
{
public int Id { get; set; }
public Child Name { get; set; }
public Child ShortName { get; set; }
}
public class Child
{
public List<ChildItem> Items { get; set; }
}
public class ChildItem
{
public string Text { get; set; }
public string Language { get; set; }
}
My DbContext:
public class ApplicationContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.OwnsOne(c => c.Name, d =>
{
d.OwnsMany(c => c.Items, a =>
{
a.HasForeignKey("NameId");
a.Property<int>("Id");
a.HasKey("NameId", "Id");
a.ToTable("ParentNameItems");
})
.ToTable("ParentName");
})
.ToTable("Parent");
modelBuilder.Entity<Parent>()
.OwnsOne(c => c.ShortName, d =>
{
d.OwnsMany(c => c.Items, a =>
{
a.HasForeignKey("NameId");
a.Property<int>("Id");
a.HasKey("NameId", "Id");
a.ToTable("ParentShortNameItems");
})
.ToTable("ParentShortName");
})
.ToTable("Parent");
}
}
Usage:
static void Main(string[] args)
{
var context = new ApplicationContext();
var parent = new Parent()
{
Name = new Child()
{
Items = new List<ChildItem>()
{
new ChildItem() { Text = "First value", Language = "en-en"},
new ChildItem() { Text = "Second value", Language = "en-en"}
}
},
ShortName = new Child()
{
Items = new List<ChildItem>()
{
new ChildItem() { Text = "First short value", Language = "en-en"},
new ChildItem() { Text = "Second short value", Language = "en-en"}
}
}
};
context.Set<Parent>().Add(parent);
context.SaveChanges();
}
Well, first of all you classes don't make sense. If anything it should be
public class Parent
{
public int Id { get; set; }
public List<Child> Children { get; set; }
}
a parent should have many children (or none possibly, empty list maybe?). What about the child's name and id, doesn't he have one of each ? also maybe a ParentId maybe something like
public class Child
{
public virtual List<ChildItem> Items { get; set; } //why virtual? you planning to inherit?
public string Name {get; set; }
public int Id {get; set; }
public int ParentId {get; set; }
}
That looks a little better, I should think. The database should have matching tables. and let's be honest EF (Entity Framework) auto create will do 99% of the work for you, so please use it.
Problem is solved. It was in line
a.HasKey("NameId", "Id");
in OnModelCreating method.
I used an example where was written abount configuring Collections of owned types.
After deleting "NameId" field from Key definition
a.HasKey("Id");
everything works fine now.
Entity model:
public class DocumentType : CodeBase
{
[Required]
[MaxLength(100)]
public string Name { get; set; }
public TimeSpan? Productiontime { get; set; }
public bool IsDeliverable { get; set; }
public virtual ICollection<DocumentTypeRetractRelation> DocumentTypes { get; set; }
public virtual ICollection<DocumentTypeRetractRelation> RetractDocumentTypes { get; set; }
}
Relation model:
/// <summary>
/// Relationship between document types showing which documenttypes can
/// retracted when delivering a new document.
/// </summary>
[Table("DocumentTypeRetractRelation")]
public class DocumentTypeRetractRelation
{
public int DocumentTypeId { get; set; }
public virtual DocumentType DocumentType { get; set; }
public int RetractDocumentTypeId { get; set; }
public virtual DocumentType RetractDocumentType { get; set; }
}
Model builder:
modelBuilder.Entity<DocumentTypeRetractRelation>().HasKey(x => new { x.DocumentTypeId, x.RetractDocumentTypeId });
modelBuilder.Entity<DocumentTypeRetractRelation>()
.HasOne(x => x.DocumentType)
.WithMany(x => x.DocumentTypes)
.HasForeignKey(x => x.DocumentTypeId);
modelBuilder.Entity<DocumentTypeRetractRelation>()
.HasOne(x => x.RetractDocumentType)
.WithMany(x => x.RetractDocumentTypes)
.HasForeignKey(x => x.RetractDocumentTypeId);
Update writer:
public async Task<DocumentType> UpdateAsync(DocumentTypeUpdateDto documentTypeUpdateDto)
{
using (IUnitOfWork uow = UowProvider.CreateUnitOfWork<EntityContext>())
{
var documentTypeRepo = uow.GetCustomRepository<IDocumentTypeRepository>();
var existingDocument = await documentTypeRepo.GetAsync(documentTypeUpdateDto.Id);
if (existingDocument == null)
throw new EntityNotFoundException("DocumentType", existingDocument.Id);
foreach (var retractDocumentTypeId in documentTypeUpdateDto.RetractDocumentTypeIds)
{
existingDocument.RetractDocumentTypes.Add(new DocumentTypeRetractRelation()
{
DocumentTypeId = existingDocument.Id,
RetractDocumentTypeId = retractDocumentTypeId
});
}
documentTypeRepo.Update(existingDocument);
await uow.SaveChangesAsync();
return existingDocument;
}
}
When trying to update the existingDocument I get the following error:
The instance of entity type 'DocumentTypeRetractRelation' cannot be
tracked because another instance of this type with the same key is
already being tracked. When adding new entities, for most key types a
unique temporary key value will be created if no key is set (i.e. if
the key property is assigned the default value for its type). If you
are explicitly setting key values for new entities, ensure they do not
collide with existing entities or temporary values generated for other
new entities. When attaching existing entities, ensure that only one
entity instance with a given key value is attached to the context.
The problem is not the self referencing, but applying the many-to-many collection modifications which generate different DocumentTypeRetractRelation objects with the same PK as stated in the exception message.
The correct way currently in EF Core is to make sure RetractDocumentTypes of the existingDocument are loaded (contains the original values), then merge the changes by either using an existing or create new DocumentTypeRetractRelation objects.
Replace the following code
foreach (var retractDocumentTypeId in documentTypeUpdateDto.RetractDocumentTypeIds)
{
existingDocument.RetractDocumentTypes.Add(new DocumentTypeRetractRelation()
{
DocumentTypeId = existingDocument.Id,
RetractDocumentTypeId = retractDocumentTypeId
});
}
with
// existingDocument.RetractDocumentTypes should be loaded (either eager or explicit)
existingDocument.RetractDocumentTypes = (
from retractDocumentTypeId in documentTypeUpdateDto.RetractDocumentTypeIds
join existingRelation in existingDocument.RetractDocumentTypes
on retractDocumentTypeId equals existingRelation.RetractDocumentTypeId
into existingRelations
select existingRelations.FirstOrDefault() ?? new DocumentTypeRetractRelation()
{
DocumentTypeId = existingDocument.Id,
RetractDocumentTypeId = retractDocumentTypeId
}).ToList();
This would handle both added, removed and unchanged relations. You can do similar to DocumentTypes.
Actually looking at your model, the above code should be for DocumentTypes collection (since you a receiving the RetractDocumentTypeIds, which in combination with the document Id form the DocumentTypes collection content). So simply replace the RetractDocumentTypes with DocumentTypes.
Here is my sample code
class Sample
{
public int Id{ get;set; }
public int AssociatedSiteId { get; set; }
[ForeignKey("AssociatedSiteId")]
public virtual SiteA Site { get; set; }
[ForeignKey("AssociatedSiteId")]
public virtual SiteB SiteB { get; set; }
}
I m Inserting the data as
using (var dbcontext = new sampleEntities)
{
var sample = new Sample();
sample.Id=1;
sample.AssociateId = siteInfo.SiteId; // This Id from SiteA Table
dbcontext.Sample.Add(sample);
dbcontext.SaveChanges();
}
where I try to Insert Data into Sample, I m getting the error as:
The INSERT statement conflicted with the FOREIGN KEY constraint.
I m new to EF Can any one please help me?
I am assuming you need some guidance :)
I have a question: Why Site and SiteB are using the same foreign key variable? Aren't them different entities? (SiteA and SiteB)
If you have
Sample.SiteA => MySiteA
Sample.SiteB => MySiteB
they cannot use the same foreignh key. Even if they are the same type, that key is used to locate the foreign entity and you would be overwriting one with the another... Check your code with mine.
Try the following:
class Sample
{
public int Id{ get;set; }
public int AssociatedSiteAId { get; set; } // Optional but sometimes useful if you don't use [ForegnKey("...")]
[ForeignKey("AssociatedSiteAId")]
public virtual SiteA Site { get; set; }
public int AssociatedSiteBId { get; set; } // Optional but sometimes useful if you don't use [ForegnKey("...")]
[ForeignKey("AssociatedSiteBId")]
public virtual SiteB SiteB { get; set; }
}
On your code,
using (db = new DbContext())
{
var site_a = db.SitesA.Find(123); // 1 should be the key of site a
var site_b = db.SitesB.Find(456); // 2 should be the key of site b
Sample sample = new Sample()
{
SiteA = site_a,
SiteB = site_b
};
db.Samples.Add(sample);
db.SaveChanges();
}
The following are the entity classes to make more understanding of relationships:
public class EmployeeCv : UserEntity
{
public byte ProfileImage { get; set; }
public virtual List<Header> Headers { get; set; }
public virtual List<ProjectExperience> ProjectExperiences { get; set; }
public virtual List<Tag> Tags { get; set; } //many to many relationship between employeeCv and tags
[NotMapped]
public List<TagsByTypes> TagsbyTypes
{
get
{
List<TagsByTypes> ListOfTagTypes = new List<TagsByTypes>();
if (Tags != null)
{
var GroupedList = Tags.GroupBy(x => x.TagType.Title).ToList().Select(grp => grp.ToList());
foreach (var currentItem in GroupedList)
{
var TagType = new TagsByTypes()
{
Title = currentItem.FirstOrDefault().TagType.Title,
Tags = currentItem
};
ListOfTagTypes.Add(TagType);
}
return ListOfTagTypes;
}
else
return null;
}
}
}
public class Tag : AuditableEntity<int>
{
public string Title { get; set; }
public virtual List<EmployeeCv> EmployeeCv { get; set; }
public virtual TagType TagType { get; set; }
//To post Id's Not added to the database
[NotMapped]
public int TagTypeId { get; set; }
[NotMapped]
public int EmployeeCv_Id { get; set; }
}
public class TagType : AuditableEntity<int>
{
public string Title { get; set; }
public virtual List<Tag> Tags { get; set; }
}
I am writing a function to add new tag to the employeeCv based on the existing tag type. I have got Unit of work and Repositories setup to add/update/delete records in DB. Here is my implementation:
public void UpdateEmployeeCVWithTag(Tag tag)
{
using (var repository = new UnitOfWork<EmployeeCv>().Repository)
{
var EmployeeCv = repository.GetSingleIncluding(tag.EmployeeCv_Id,
x => x.Headers, x => x.Tags,
x => x.ProjectExperiences,
x => x.ProjectExperiences.Select(p => p.AssociatedProject),
x => x.ProjectExperiences.Select(p => p.ProjectSkills));
//x => x.ProjectExperiences.Select(p => p.ProjectSkillTags.Select(s => s.AssociatedSkill)));
//tag.TagType = EmployeeCv;
var repositoryTagType = new UnitOfWork<TagType>().Repository;
var tagtype = repositoryTagType.GetItemById(tag.TagTypeId);
tag.TagType = tagtype; //even after assignment new tagtype is creating everytime code runs
//repositoryTag.UpdateItem(tagtype);
EmployeeCv.Tags.Add(tag);
//EmployeeCv.ProjectExperiences[projectId - 1].ProjectSkills.Add(tag);
repository.UpdateItem(EmployeeCv);
}
}
This function works correctly except one issue. It is creating a new TagType in the database and ignoring the one that already exist. Below is my updateItem code in the repository classs:
public virtual void UpdateItem(TEntity entityToUpdate)
{
var auditableEntity = entityToUpdate as IAuditableEntity;
if (auditableEntity != null)
{
auditableEntity.UpdatedDate = DateTime.Now;
}
//_context
//Attach(entityToUpdate);
_context.Entry(entityToUpdate).State = EntityState.Modified;
_context.SaveChanges();
}
My guess without seeing the full functionality, is that you are using different context for this.
You should update the foreign key not the entire object so there is no need to add the entire TagType object since the tagTypeId is already set. The foreign key should work as is.
Please look into this link for further information.