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.
Related
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?
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.
I'm trying to create a database containing a single self-referenced table "Categories" using Code First approach. Here is the definition of the Category POCO entity:
public class Category
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; private set; }
public string Name { get; set; }
public int? ParentCategoryId { get; private set; }
[ForeignKey("ParentCategoryId")]
public Category ParentCategory { get; set; }
public List<Category> SubCategories { get; set; }
public Category()
{
SubCategories = new List<Category>();
}
}
and here is the definition of the DbContext subclass for the database:
public class CategoryContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public CategoryContext()
: base("name=CategoriesEntities") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Category>().HasMany(cat => cat.SubCategories).WithOptional(cat => cat.ParentCategory).HasForeignKey(cat => cat.ParentCategoryId);
}
}
Now I'm trying to fill the table:
using (var context = new CategoryContext())
{
var child = new Category { Name = "child" };
var parent = new Category { Name = "parent" };
parent.SubCategories.Add(child);
//child.ParentCategory = parent;
context.Categories.Add(child);
//context.Categories.Add(parent);
context.SaveChanges();
}
But the only record I see in a resulting table is the "child" record. But if I change parent.SubCategories.Add(child) line to child.ParentCategory = parent line, everything will work fine and the table will contain both records. Everything will be ok also if I change context.Categories.Add(child) to context.Categories.Add(parent).
So, what am I doing wrong? Why isn't parent record added to the table along with it's child record? How can I achieve the desired behavior without making the substitutions listed above?
Any help will be appreciated.
You are getting this behavior because you are only saying it to add chield
context.Categories.Add(child);
and if you look at your child object it has no association with you parent , but you parent have association with child (one way relationship) so when you do context.Categories.Add(child); EF has no clue about parent
So right way to do is to add parent object only
context.Categories.Add(parent);
Changed code should look something like
using (var context = new CategoryContext())
{
var child = new Category { Name = "child" };
var parent = new Category { Name = "parent" };
parent.SubCategories.Add(child);
context.Categories.Add(parent);
context.SaveChanges();
}
If this had helped you don't forgot to mark it as answer :)
To illustrate my problem, suppose I have a data model where I have a collection of Books, each of which has one or more Drafts, and also a "current" Draft. I'd like my model to look something like this:
class Book
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Draft> Drafts { get; set; }
public virtual Draft CurrentDraft { get; set; }
}
class Draft
{
public int Id { get; set; }
public virtual int BookId { get; set; }
public virtual Book Book { get; set; }
public string Description { get; set; }
}
I have a DbContext that looks like this:
class TestDbContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Draft> Drafts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
base.OnModelCreating(modelBuilder);
}
}
...and a simple program like this:
static void Main(string[] args)
{
Database.SetInitializer<TestDbContext>(new DropCreateDatabaseAlways<TestDbContext>());
using (var db = new TestDbContext())
{
var book = new Book() { Title = "War and Peace", Drafts = new List<Draft>() };
var draft1 = new Draft() { Book = book, Description = "First Draft" };
book.Drafts.Add(draft1);
var draft2 = new Draft() { Book = book, Description = "Second Draft" };
book.Drafts.Add(draft2);
book.CurrentDraft = draft2;
db.Books.Add(book);
db.SaveChanges();
foreach (var b in db.Books)
{
Console.WriteLine("Book {0}: {1}", b.Id, b.Title);
foreach (var d in book.Drafts)
Console.WriteLine("\tDraft ID {0}: {1} from Book ID {2}", d.Id, d.Description, d.BookId);
Console.WriteLine("Current draft has ID {0}", b.CurrentDraft.Id);
}
Console.ReadKey();
}
}
When the DB is created by EF, it looks like this:
[I'm not wild about the extra Book_Id column, but I could live with it if it worked.]
Sadly, when I run the test program, it fails at the SaveChanges call, with an exception:
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.
I've tried messing about with the fluent API in my OnModelCreating, but when I try to configure it that way it gets confused by the fact that there are two FK relationships between Books and Drafts. I'm not familiar enough with the fluent stuff (or EF generally) to know how to do it properly.
Is this even possible in EF code first?
Your inner exception is that EF can't figure out the order to save things to the database to create something properly. If you call save changes in multiple places to force the order your test app will work:
var book = new Book() { Title = "War and Peace", Drafts = new List<Draft>() };
db.Books.Add(book);
db.SaveChanges();
var draft1 = new Draft() { Book = book, Description = "First Draft" };
book.Drafts.Add(draft1);
var draft2 = new Draft() { Book = book, Description = "Second Draft" };
book.Drafts.Add(draft2);
book.CurrentDraft = draft2;
db.SaveChanges();
Regarding the second book_id - you could use this fluent:
modelBuilder.Entity<Book>()
.HasMany(b => b.Drafts)
.WithRequired(d => d.Book)
.HasForeignKey(d => d.BookId);
modelBuilder.Entity<Book>()
.HasOptional(b => b.CurrentDraft)
.WithOptionalDependent()
.Map(m => m.MapKey("CurrentDraftId"));
which produces this database:
If i were you i will add one more property to Draft that will be bool IsCurrent{get;set;}. And
public Draft CurrentDraft { get{return Drafts.SingleOrDefault(c=>c.IsCurrent)} }
In this case there will be one foreign key between books and drafts.
I have the following poco class:
public class Category : IDisplayName
{
private ICollection<Category> children;
private Category parent;
public Category()
{
children = new List<Category>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual Category Parent
{
get { return parent; }
set
{
parent = value;
// if (value != null && parent.Children.Contains(this) == false)
// {
// parent.Children.Add(this);
// }
}
}
public virtual ICollection<Category> Children
{
get { return children; }
set { children = value; }
}
}
This is the Mapping file (I am not sure if this is correct.. but I am out of ideas and there is bugger all documentation out there...)
public class CategoryEntityConfiguration : EntityConfiguration<Category>
{
public CategoryEntityConfiguration()
{
Property(x => x.Name).IsRequired();
HasMany(x => x.Children).WithOptional(x => x.Parent);
HasOptional(x => x.Parent).WithMany(x => x.Children);
}
}
Notice the "Parent" property and how I am not adding them each using the "Children" collection.
var cat_0 = new Category { Name = "Root" };
var cat_1 = new Category { Name = "Property", Parent = cat_0 };
var cat_2 = new Category { Name = "Property Services", Parent = cat_1 };
var cat_3 = new Category { Name = "Housing Association", Parent = cat_2 };
var cat_4 = new Category { Name = "Mortgages & Conveyancing", Parent = cat_2 };
var cat_5 = new Category { Name = "Property Management", Parent = cat_2 };
var cat_6 = new Category { Name = "Property Auctions", Parent = cat_2 };
var cat_7 = new Category { Name = "Landlords Wanted", Parent = cat_2 };
context.Set<Category>().Add(cat_0);
When I save the cat_0 to the database only 1 row is inserted and Entity Framework does not pick up the fact the cat_0 is the parent of a whole bunch of other objects and does not realise that they need to be persisted. I have a workaround which is the commented out code in the "Parent" category property.. but I would rather not have to do this as is does not feel right.
Any help would be much appreciated
Jake
It is possible but you have to use tracking proxies. To do that modify your Category class so that all persisted properties are virtual.
public class Category
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
}
Create context and check that creation of dynamic proxy is allowed. On such context you can use CreateObject method to get your category instance. You will not get instance of type Category but dynamic type inherited from Category. This dynamic proxy is responsible for lazy loading (if enabled) and for change tracking to existing context. If you modify navigation property on the one side it will automatically modify navigation property on the other side.
using (var context = new ObjectContext(connectionString))
{
// This should be default value
context.ContextOptions.ProxyCreationEnabled = true;
var cat0 = context.CreateObject<Category>();
cat0.Name = "A";
var cat1 = context.CreateObject<Category>();
cat1.Name = "B";
cat1.Parent = cat0;
context.CreateObjectSet<Category>().AddObject(cat0);
context.SaveChanges();
}
Edit:
If you don't like approach with tracking proxies (which require existing context) you can reverse the way you create your entities. Instead of setting Parent property on childs you have to fill Childs on parent. In that case it will work.