Saving navigational data as well when saving the entity - entity-framework

Given the following models (I'm using EF Core):
public class Person {
public Person(){
PhoneNumbers = new HashSet<PhoneNumber>();
}
public int Id {get; set;}
public String Name {get; set;}
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
}
public class PhoneNumber {
public int Id {get; set;}
public String Number {get; set;}
public int PersonId {get; set;}
}
When try to add a new Person with many new PhoneNumbers using the following approach:
Person person = new Person {Name = "Jimmy"};
foreach(var phone in Phone_Number_In_Posted_Form){
person.PhoneNumbers.Add(new PhoneNumber {Number = phone});
}
Db.Entity<Person>(person).State = EntityState.Added;
Db.SaveChanges();
Entity framework does not automatically associate the Id (Auto Increment) generated to the PhoneNumbers (the navigational property) and for that reason the PhoneNumbers is not saved by SQL Server. Although this can be worked around through getting the generated Id and run another loop to associate it manually, I'd like to know if there is any standard solution built into Entity Framework that can work more effectively.

Entity framework does not automatically associate the Id (Auto Increment) generated to the PhoneNumbers (the navigational property) and for that reason the PhoneNumbers is not saved by SQL Server
The reason is different. It's because setting the State property does not cascade (does not apply recursively to the related data), hence is not a replacement of the corresponding DbContext / DbSet methods.
In you your case, instead of setting the State to Added you should call the Add method:
Person person = new Person {Name = "Jimmy"};
foreach(var phone in Phone_Number_In_Posted_Form){
person.PhoneNumbers.Add(new PhoneNumber {Number = phone});
}
Db.Add(person);
Db.SaveChanges();

Related

EF Core, Primary Key is not auto generated for Entity which inherit from ICollection

Here is my Entity:
public class StackImage: ICollection<StackFile>
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
private IList<StackFile> StackFiles { get; set; } = new List<StackFile>();
public StackImage()
{
}
[...] // Implementation of ICollection
}
public class StackFile
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
public string Url { get; set; }
public int Position { get; set; }
public StackFile(){}
}
stackImage.Add(new StackFile(url));
stackImage= await _stackImageRepository.UpdateAsync(stackImage);
await _unitOfWork.SaveChangesAsync();
In this sample after UpdateAsync, the StackImage Id is not generated (stackImage.Id == default) but the StackFile Id is correctly generated (stackImage[0].Id == default)
Did you already noticed this problem? My guess is, EF Core see StackImage as a list and doesn't try to generate a new Guid. How to fix this issue?
EDIT:
From what I can read on the web and by responses I received, It seems not possible to do it. If someone has the solution, please let us know :)
It seems to me that you want to design a database with (at least) two tables. A table with StackImages and a table with StackFiles.
You want to design a one-to-many relation between StackImages and StackFiles: every StackImage has zero or more StackFiles, every StackFile belongs to exactly one StackImage. In a database this is implemented using a foreign key.
Hence, it is not true that a StackImage is a StackFile. However, you can say that a StackImage has some StackFiles.
Following the entity framework code first conventions your classes should be similar to:
class StackImage
{
public Guid Id {get; set;}
...
// every StackImage has zero or more StackFiles (one-to-many):
public virtual ICollection<StackFile> StackFiles {get; set;}
}
class StackFile
{
public Guid Id {get; set;}
...
// every StackFile belongs to exactly one StackImage, using foreign key:
public Guid StackImageId {get; set;}
public virtual StackImage StackImage {get; set;}
}
finally the DbContext:
class MyDbcontext : DbContext
{
public DbSet<StackImage> StackImages {get; set;}
public DbSet<StackFile> StackFiles {get; set;}
}
Note the use of virtual properties to express the relations between the tables. As the foreign key StackImageId is supposed to be a real column, it is not virtual
In entity framework the columns of a table are represented by non-virtual properties,
the virtual properties represent the relations between the tables.
Because I followed the conventions, there is no need for attributes, nor fluent API. Entity framework detects the one-to-many collection and creates the proper tables for you. Only if you want different identifiers for your tables or columns you'll need fluent API or attributes.

Updating a many-to-many relationship neither entirely code nor database first

I seem to be struggling with a combination of naming conventions and understanding.
I have inherited a database and am building a MVC site that I was unable to get the "database-first" workflow to play nicely. In the end I manually constructed my context classes and have been working away happily.
I am now in a situation where I am unable to add an entity with a relationship to several other existing entities (the many-to-many).
My database looks (simplified for this question) like this:
ListItem Option OptionListItems
====== ====== ===============
Id Id ListItem_Id
Name Name Option_Id
My context contains a property that allows me to get all of my ListItems:
public virtual DbSet<ListItem> ListItems { get; set; }
And if I use some LINQ, I do something like the following, and the items are returned and the many-to-many relationship is satisfied and I get a list of Option within my ListItem:
var item = _context.ListItems
.Where(p => p.Id == id)
.Include(p => p.Options)
.SingleOrDefault();
In fact, I had to construct the cross-reference table in the database manually which I did when I tried to run the above query and the exception I got told me I had no object called dbo.OptionListItems. So I assumed we were all good.
Now I need to create a new ListItem and link it to one or more existing Option and I'm at a loss.
Once I've created my new ListItem in isolation, and attempt to call listItem.Options.Add(...) it fails, but I also get the exact same exception if I try to get a reference to a particular Option and try to do option.ListItems.Add(...).
The error is kind of amusing and is the opposite table name to what I have:
{"Invalid object name 'dbo.ListItemOptions'."}
I suspect that it goes against the grain of EF to build a type and a property on my context to directly access the cross reference table like this:
public virtual DbSet<OptionListItem> OptionListItems { get; set; }
But I'm completely baffled by the pattern to create new relationships.
We have this (many-to-many) working declaratively. Pseudocode:
public class ListItem
{
public ListItem()
{
this.RelatedOptions = new HashSet<OptionListItems>();
}
[Key]
public int Id {get; set;}
public string Name {get; set;}
public virtual ICollection<OptionListItems> RelatedOptions {get; set;}
}
public class Option
{
public Ortion()
{
this.RelatedItems = new HashSet<OptionListItems>();
}
[Key]
public int Id {get; set;}
public string Name {get; set;}
public virtual ICollection<OptionListItems> RelatedItems {get; set;}
}
public class OptionListItems
{
[Key]
public int Id {get; set;}
[Column("ListItemId", Order = 1)]
[ForeignKey("ParentListItem")]
public int ListItemId {get; set;}
[Column("OptionId", Order = 2)]
[ForeignKey("ParentOption")]
public int OptionId {get; set;}
public virtual ListItem ParentListItem {get; set;}
public virtual Option ParentOption {get; set;}
}
This should create full relationship declaratively
Credit goes to Steve Greene for pointing me in the right direction.
The table I had was created by convention and worked when I queried WorkItem with .Include(p => p.Options) however the convention seems to break down if you try to do an update. I'm unsure why, but the construction of the mapping table seems to be <Entity1>+<Entity2>+s when querying, but <Entity2>+<Entity1>+s when updating...
The good news, by using fluentAPI, I've created a specific mapping between the entities and forced the cross reference table and both querying and updating works!
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ListItem>()
.HasMany<Option>(s => s.Options)
.WithMany(c => c.ListItems)
.Map(cs =>
{
cs.MapLeftKey("ListItem_Id");
cs.MapRightKey("Option_Id");
cs.ToTable("OptionListItems");
});
}

Entity Framework Code First - Setting Foreign Key / Navigation Property

I like to reference to a specific entry in a database. Is there a data annotation, which I could use
E.g.
public class Address
{
public int CityId {get; set;}
}
public class City
{
public int id {get; set;}
}
So the Address.CityId references the City.id.
And how can I reference to Table Columns via data annotations.
Please refer this link .
https://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application
in that read Topic : The Key Attribute
What you want is called One-To-One entity mapping. You can create navigation property in your Address class as follows:
public class Address
{
[Key]
public int AddressID {get;set;}
public int CityID {get; set;}
[ForeignKey("CityID")]
public City City {get;set;}
}
public class City
{
[Key]
public int CityID {get; set;}
}
Add ForeignKey attribute to your navigation property to create mapping through foreign key. And do not forget to define key attributes in each EF model.
More details on Entity Framework home page: Configure One-to-Zero-or-One Relationship

Automatically update a RelatedProperty when adding a new entity to the collection in a one-to-many relationship

EntityFramework 5.0
Suppose I have the following setup:
public class Book
{
public int ID {get; set;}
public string Title {get; set;}
[InverseProperty("Books")]
[Required]
public Author Author {get; set;}
}
public class Author
{
public int ID {get; set;}
public string Name {get; set;}
public virtual ICollection<Book> Books {get; set;}
}
Then in my code I create a new Book and I do this:
author.Books.Add(newBook);
How can I have the Book pick-up its Author automatically instead of having to write this every time:
newBook.Author = author;
I want the child entity to pick up its parent automatically when added to the parent's collection.
Is this possible? Fluent mapping maybe?
Or do I have to maintain both sides of this bi-directional relationship myself?
My mistake.
This is the default behavior and the book gets its Author out-of-the-box.
Case closed.

Retrieving/Updating Entity Framework POCO objects that already exist in the ObjectContext

I have a project using Entity Framework 4.0 with POCOs (data is stored in SQL DB, lazyloading is enabled) as follows:
public class ParentObject {
public int ID {get; set;}
public virtual List<ChildObject> children {get; set;}
}
public class ChildObject {
public int ID {get; set;}
public int ChildRoleID {get; set;}
public int ParentID {get; set;}
public virtual ParentObject Parent {get; set;}
public virtual ChildRoleObject ChildRole {get; set;}
}
public class ChildRoleObject {
public int ID {get; set;}
public string Name {get; set;}
public virtual List<ChildObject> children {get; set;}
}
I want to create a new ChildObject, assign it a role, then add it to an existing ParentObject. Afterwards, I want to send the new ChildObject to the caller.
The code below works fine until it tries to get the object back from the database. The newChildObjectInstance only has the ChildRoleID set and does not contain a reference to the actual ChildRole object. I try and pull the new instance back out of the database in order to populate the ChildRole property. Unfortunately, in this case, instead of creating a new instance of ChildObject and assigning it to retreivedChildObject, EF finds the existing ChildObject in the context and returns the in-memory instance, with a null ChildRole property.
public ChildObject CreateNewChild(int id, int roleID) {
SomeObjectContext myRepository = new SomeObjectContext();
ParentObject parentObjectInstance = myRepository.GetParentObject(id);
ChildObject newChildObjectInstance = new ChildObject() {
ParentObject = parentObjectInstance,
ParentID = parentObjectInstance.ID,
ChildRoleID = roleID
};
parentObjectInstance.children.Add(newChildObjectInstance);
myRepository.Save();
ChildObject retreivedChildObject = myRepository.GetChildObject(newChildObjectInstance.ID);
string assignedRoleName = retreivedChildObject.ChildRole.Name; //Throws exception, ChildRole is null
return retreivedChildObject;
}
I have tried setting MergeOptions to Overwrite, calling ObjectContext.Refresh() and ObjectContext.DetectChanges() to no avail... I suspect this is related to the proxy objects that EF injects when working with POCOs.
Has anyone run into this issue before? If so, what was the solution?
You should not use "new" to create a a new entity, but rather use ObjectContext.CreateObject(). This way the proxy of your object will be instantiated instead of the entity POCO class itself.
I know it's an old question, but I stumbled upon it and just read about that a second ago.