I have a One-to-ZeroOrOne relationship that uses a pattern that is different to the style given in EF Core Relationships documentation. EF Core picks up my pattern, i.e. it knows it is a one-to-one relationship, but fails on an update when there is already an entry.
I have successfully used this one-to-one relationship pattern in EF6 and, to me anyway, it seems like a good pattern as it combines the Primary Key and Foreign Key.
NOTE: This is not urgent as I can swap to your recommended style for a one-to-one relationship, but it would be useful to know if you plan to fix this, as I use this pattern in an example.
If you are seeing an exception, include the full exceptions details (message and stack trace).
The instance of entity type 'PriceOffer' 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.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph(EntityEntryGraphNode node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, Object oldValue, Object newValue)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry entry, INavigation navigation)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at test.UnitTests.DataLayer.TestOneToOneUpdate.TestConnectedUpdateExistingRelationship() in C:\Users\Jon\Documents\Visual Studio 2015\Projects\EfCoreInAction\test\UnitTests\DataLayer\TestOneToOneUpdate.cs:line 84
Include a complete code listing (or project/solution) that we can run to reproduce the issue.
Partial code listings, or multiple fragments of code, will slow down our response or cause us to push the issue back to you to provide code to repoduce the issue.
The two main entity classes are:
c#
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime PublishedOn { get; set; }
public string Publisher { get; set; }
public decimal Price { get; set; }
public string ImageUrl { get; set; }
//----------------------------------------------
//relationships
public PriceOffer Promotion { get; set; }
public ICollection<Review> Reviews { get; set; }
public ICollection<BookAuthor> AuthorsLink { get; set; }
}
````
And the one-to-one entity class, PriceOffer
c#
public class PriceOffer
{
public int BookId { get; set; }
public decimal NewPrice { get; set; }
public string PromotionalText { get; set; }
}
My DbContext contains the information to define the key for `PriceOffer`
```c#
public class EfCoreContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<PriceOffer> PriceOffers { get; set; }
public EfCoreContext( DbContextOptions<EfCoreContext> options)
: base(options) {}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookAuthor>()
.HasKey(x => new {x.BookId, x.AuthorId});
modelBuilder.Entity<PriceOffer>()
.HasKey(x => x.BookId);
}
}
The problem comes when I try to replace/remove a PriceOffer from a Book, i.e. it only fails if there is a PriceOffer on the Book already. Here is the Unit Test that fails. It fails on the line context.SaveChanges().
```c#
[Fact]
public void TestConnectedUpdateExistingRelationship()
{
//SETUP
var inMemDb = new SqliteInMemory();
using (var context = inMemDb.GetContextWithSetup())
{
context.SeedDatabaseFourBooks();
var book = context.Books
.Include(p => p.Promotion)
.First(p => p.Promotion != null);
//ATTEMPT
book.Promotion = new PriceOffer
{
NewPrice = book.Price / 2,
PromotionalText = "Half price today!"
};
context.SaveChanges();
//VERIFY
var bookAgain = context.Books
.Include(p => p.Promotion)
.Single(p => p.BookId == book.BookId);
bookAgain.Promotion.ShouldNotBeNull();
bookAgain.Promotion.PromotionalText.ShouldEqual("Half price today!");
}
}
One extra piece of information. If I set the book.Promotion to null and call SaveChanges then it works, i.e. it deletes the existing `PriceOffer`. See Unit Test below that does not fail
```c#
[Fact]
public void TestDeleteExistingRelationship()
{
//SETUP
var inMemDb = new SqliteInMemory();
using (var context = inMemDb.GetContextWithSetup())
{
context.SeedDatabaseFourBooks();
var book = context.Books
.Include(p => p.Promotion)
.First(p => p.Promotion != null);
//ATTEMPT
book.Promotion = null;
context.SaveChanges();
//VERIFY
var bookAgain = context.Books
.Include(p => p.Promotion)
.Single(p => p.BookId == book.BookId);
bookAgain.Promotion.ShouldBeNull();
context.PriceOffers.Count().ShouldEqual(0);
}
}
EF Core version: "version": "1.1.0"
Database Provider: "Microsoft.EntityFrameworkCore.SqlServer": "1.1.0",
NOTE: It also fails on "Microsoft.EntityFrameworkCore.SqlServer": "1.0.1"/NET Core "version": "1.0.1", so its not a regression.
Operating system: Window 10
IDE: Visual Studio 2015, update 3.
Notes for triage: This is the case where there is an identifying relationship where where the dependent PK is also the FK. The application loads an existing principal and dependent and then attempt to replace the dependent with a new entity--i.e. reference stealing. This requires deletion of the old dependent followed by insertion of the new dependent, where both have the same key.
This worked in the old stack because the new dependent didn't have a key value until it was saved. In the new stack it fails because the state manager assigns key values immediately and the result is an attempt to track two entities with the same key.
We have so far decided not to support this but bringing it to triage as the scenario here is somewhat more realistic than most cases that do this. Note that it would be a significant change to tracking to support this.
Hi @ajcvickers,
Thanks for the detailed explanation. I don't want to put any unessential work into your already heavy workload so I will change my example.
I would say that other people might bump into this as its a valid pattern (and it works in EF6), so if there is any way you reject this as a valid relationship it might save some support issues.
@JonPSmith It is certainly a completely valid relationship. The question is whether or not to allow two entities with the same identity to be tracked at the same time. It weakens the concept of identity such that instead of just being based on the key value it needs to also include something else. EF6 doesn't have this problem because it doesn't give new entities identity until they are saved, but his has its own set of problems.
FWIW, if we ended up leaning towards supporting this in EF Core I believe it should work in a completely different way from EF6:
I don't actually think this would weaken the concept of identity, but I agree with @ajcvickers that our current implementation is simpler and changing it would have a cost.
Hi @divega,
Clearly there is an alternative to the one-to-one PK+FK pattern I am using and I will swap to that. For that reason this issue isn't a priority at all.
My only long-term concern is that the one-to-one PK+FK pattern looks and feels like a proper relationship until you try and change an existing relationship. All of my example code is a) simple and b) has a unit tests, so I caught this problem easily. Someone else might hit this problem and struggle to diagnose it.
I have been thinking about @divega's suggestion and I think it could be implemented without too high cost, both in dev time and in perf/mem usage. It would be nice to not have to do the additional SaveChanges in this case.
I just wanted to show another example I was planning to use which uses the PK+FK pattern in a one-to-many relationship, just to show that the pattern turns up on other places.
The example is of an Order, which has many LineItems. I create the LineItem with the OrderId and a LineNum as a composite key. Obviously, this works in all cases other than update of an LineItem where the composite key is identical to an existing entry in the list that is being deleted.
The two classes are:
```c#
public class Order
{
public int OrderId { get; set; }
public DateTime DateOrderedUtc { get; set; }
public Guid CustomerName { get; set; }
public ICollection
public Order()
{
DateOrderedUtc = DateTime.UtcNow;
}
}
And the LineItem class
```c#
public class LineItem
{
//Composite key using the OrderId and the LineNum
public int OrderId { get; set; }
public byte LineNum { get; set; }
public short NumBooks { get; set; }
public decimal BookPrice { get; set; }
public int BookId { get; set; }
public Book ChosenBook { get; set; }
}
My DbContext is
```c#
public class EfCoreContext : DbContext
{
public DbSet
public DbSet
public DbSet
public DbSet
public EfCoreContext(
DbContextOptions<EfCoreContext> options)
: base(options) {}
protected override void
OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookAuthor>()
.HasKey(x => new {x.BookId, x.AuthorId});
modelBuilder.Entity<LineItem>()
.HasKey(c => new {c.OrderId, c.LineNum});
modelBuilder.Entity<LineItem>()
.HasOne(p => p.ChosenBook)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
}
}
```
I have used this pattern successfully in EF6.x. I quite like this pattern as the composite key ensures that the LineNum in the LineItem is incremented properly.
I have altered my example to get round this issue.
@JonPSmith Can you give more details on what you mean by this, "Obviously, this works in all cases other than update of an LineItem where the composite key is identical to an existing entry in the list that is being deleted." Specifically, can you should the code that is doing the insert/updates/deletes of LineItems?
Hi @ajcvickers ,
Sure, here is the unit test. It fails if the LineItem class uses the PK+FK pattern as shown in the previous post. The failure is the same as with the original PriceOffer PK+FK, i.e.
The instance of entity type 'LineItem' cannot be tracked because another instance of this type with the same key is already being tracked.
... which happens on the second SaveChanges() when I have replaced the LineItems collection.
```c#
///
/// This test is there because of the PK+FK issue - see https://github.com/aspnet/EntityFramework/issues/7340
/// I have changed the LineItem class definition to overcome that issue.
///
[Fact]
public void TestUpdateLineItemInOrder()
{
//SETUP
var options = EfInMemory.CreateNewContextOptions();
using (var context = new EfCoreContext(options))
{
context.SeedDatabaseFourBooks();
var userId = Guid.NewGuid();
var order = new Order
{
CustomerName = userId,
LineItems = new List<LineItem>
{
new LineItem
{
BookId = 1,
LineNum = 0,
BookPrice = 123,
NumBooks = 1
}
}
};
context.Orders.Add(order);
context.SaveChanges();
}
//ATTEMPT
using (var context = new EfCoreContext(options))
{
var order = context.Orders.Include(x => x.LineItems).First();
order.LineItems = new List<LineItem>
{
new LineItem
{
BookId = 1,
LineNum = 0,
BookPrice = 456,
NumBooks = 1
}
};
context.SaveChanges();
}
//VERIFY
using (var context = new EfCoreContext(options))
{
var order = context.Orders.Include(x => x.LineItems).First();
order.LineItems.First().BookPrice.ShouldEqual(456);
}
}
```
Note, if I make the new LineItem have a different composite key, say by setting LineNum to 1, then it doesn't fail, which makes sense because its about a duplicate key in the tracked entity that is to be deleted.
If I change the LineItem to have is own PK, e.g. LineItemId, and relegate the BookId to just being a Foreign Key then this test passes.
I hope this helps. As I say I have worked round this problem so its not holding me up. I just provided it as another example of where this PK+FK pattern can occur.
@JonPSmith In this case, even if EF didn't throw, then the database would throw because the code is attempting to save two entities with the same key. The only way I can see this working is if LineNum was store-generated. It isn't in the code above--is it intended to be?
Also, is the intention of the code to load the LineItems collection and then throw away that collection and replace it with a new one? Replacing collections with entirely new collections like this is something we don't see very often and certainly wouldn't recommend doing. It is something I have a note for to so more testing on, but it has been a low priority.
Hi @ajcvickers,
Let me take your second point first. I think there are situations where you want to replace the whole of a collection, especially when in the disconnected state. I have an example application - try Edit and change the tags. The code to update the Tags can be found in here.
I am proposing to use this method in an example I am writing (talk to Rowan), so if this isn't the accepted way then I would be good to know.
On your first point, if indeed replacing the whole collection is allowed, which does work at the moment (and works in EF6.x), then EF Core would delete the old entry and the new entry would not clash in the database. I included a unit test that shows this with a one to many relationship: one book with many reviews, that I use as an example in my writing.
```c#
[Fact]
public void TestReplaceReviewsLoggedOk()
{
//SETUP
var options =
this.NewMethodUniqueDatabaseSeeded4Books( );
int twoReviewBookId;
using (var context = new EfCoreContext(options))
{
twoReviewBookId = context.Books.ToList().Last().BookId; //Last book has two reviews
}
//ATTEMPT
using (var context = new EfCoreContext(options))
{
var logIt = new LogDbContext(context);
var book = context.Books
.Include(p => p.Reviews)
.Single(p => p.BookId == twoReviewBookId);
book.Reviews = new List<Review>
{
new Review
{
VoterName = "Unit Test",
NumStars = 5,
}
};
context.SaveChanges();
//VERIFY
var bookAgain = context.Books
.Include(p => p.Reviews)
.Single(p => p.BookId == book.BookId);
bookAgain.Reviews.ShouldNotBeNull();
bookAgain.Reviews.Count.ShouldEqual(1);
foreach (var log in logIt.Logs)
{
_output.WriteLine(log);
}
//to get the logs you need to fail see https://github.com/aspnet/Tooling/issues/541
Assert.True(false, "failed the test so that the logs show");
}
}
```
@JonPSmith Okay, thanks for the additional info.
@ajcvickers to bring this to a design meeting
@ajcvickers A related problem that should also be considered is reparenting. In this scenario the principal instance is changed instead of the dependent instance:
book2.Promotion = book1.Promotion;
book1.Promotion = null;
context.SaveChanges();
I am currently working on a chapter covering configuring relationships and I have come across a case where, if the shadow property foreign key is placed in the dependent entity then it becomes both a primary key and a foreign key, which then bumps into the cannot be tracked because another instance of this type with the same key is already being tracked. issue.
The example code I am using is as follows:
Attendee primary entity class
```c#
public class Attendee
{
public int AttendeeId { get; set; }
public string Name { get; set; }
public int TicketId { get; set; }
public Ticket Ticket { get; set; }
public OptionalTrack Optional { get; set; }
public RequiredTrack Required { get; set; }
}
My `RequiredTrack` dependent entity
```c#
public class RequiredTrack
{
public int RequiredTrackId { get; set; }
public TrackNames Track { get; set; }
}
My configuration of the RequiredTrack navigational property (using a extension method called from OnConfiguring method in my application's DbContext
```c#
public static void Configure (this EntityTypeBuilder
{
entity.HasOne(p => p.Required)
.WithOne()
.HasForeignKey
.IsRequired();
}
My unit test is
```c#
[Fact]
public void TestShadowPropertyReplaceOk()
{
//SETUP
using (var context = new Chapter07DbContext(
SqliteInMemory.CreateOptions<Chapter07DbContext>()))
{
{
context.Database.EnsureCreated();
var attendee = new Attendee
{
Name = "Person1",
Ticket = new Ticket { TicketType = Ticket.TicketTypes.VIP },
Required = new RequiredTrack { Track = TrackNames.EfCore }
};
context.Add(attendee);
context.SaveChanges();
//ATTEMPT
attendee.Required = new RequiredTrack {Track = TrackNames.AspNetCore};
context.SaveChanges();
//VERIFY
context.Set<RequiredTrack>().Count().ShouldEqual(1);
}
}
}
The unit test fails on the second SaveChanges, after the Required navigational property is assigned a new RequiredTrack. The failure is the same as in the first post in this issue, i.e.
System.InvalidOperationException : The instance of entity type 'RequiredTrack' 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.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph(EntityEntryGraphNode node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, Object oldValue, Object newValue)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry entry, INavigation navigation)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at test.UnitTests.DataLayer.Ch07_ShadowProperties.TestShadowPropertyReplaceOk() in C:\Users\Jon\Documents\Visual Studio 2017\Projects\EfCoreInAction\Test\UnitTests\DataLayer\Ch07_ShadowProperties.cs:line 65
EF Core version: "version": "1.1.1"
Database Provider: "Microsoft.EntityFrameworkCore.SqlServer": "1.1.1"
Operating system: Window 10
IDE: Visual Studio 2015,7
@JonPSmith What is the shadow property that is a PK?
@ajcvickers,
What happens is that the shadow property is called RequiredTrackId because the navigational property is Required (based on EF Core naming rules). Because I place the FK in the dependent entity RequiredTrack, which has a PK of the same name, RequiredTrackId, then the PK and FK are combined.
Looking at the database the RequiredTrack table has a column called RequiredTrackId that is both PK and FK, which then hits this issue.
@JonPSmith So somehow even though RequiredTrack has a property called RequiredTrackId, this is not being used and a shadow property with the same name is being created instead? Or am I missing something? Can you post what the model looks like using this: https://blog.oneunicorn.com/2016/11/17/ef-core-1-1-looking-at-your-model-in-the-debugger/
@ajcvickers,
Sorry. I don't think I explained precisely enough what the table looks like. Here is the SQL from the EF log of how it builds the Sqlite table RequiredTrack
CREATE TABLE "RequiredTrack" (
"RequiredTrackId" INTEGER NOT NULL CONSTRAINT "PK_RequiredTrack" PRIMARY KEY,
"Track" INTEGER NOT NULL,
CONSTRAINT "FK_RequiredTrack_Attendees_RequiredTrackId" FOREIGN KEY ("RequiredTrackId") REFERENCES "Attendees" ("AttendeeId") ON DELETE CASCADE
);
As you can see it combines the PK and the FK into the column RequiredTrackId. Because it does this then it has the same problem I noted in the first post of this issue, that is the cannot be tracked because another instance of this type with the same key is already being tracked. issue.
If you still need me to post the Debug view I can, but I think this answers your question.
PS. I am not asking for a resolution - I am simply logging that a shadow foreign key property placed in the second part of a one-to-one relationship can bump into issue #7340.
@JonPSmith I _think_ what you are saying is that when you do this:
C#
HasForeignKey<RequiredTrack>( "RequiredTrackId")
you are expecting it to always create a shadow FK property. But that's not what that API does. It instead just says that "RequiredTrackId" should be used as the FK. If there is a CLR property with that name, then that CLR property is used and a shadow property will not be created. If you want to use a shadow property, then you'll need to give it a name that is not a CLR property name--since the whole point of a shadow property is to have a property when one doesn't exist in the CLR type.
Either way, whether or not this is a PK <-> PK relationship is not based on whether the property is shadow or not. In this case, RequiredTrackId is setup as the PK and is then also set as the FK, so the relationship becomes PK <-> PK.
It might be useful to read through the Getting Started documentation for shadow properties here: https://docs.microsoft.com/en-us/ef/core/modeling/shadow-properties
@ajcvickers,
Thanks. My mistake is that I thought I had to follow the by convention rules for shadow properties, which is where I got the name "RequiredTrackId". I have just tried a random name and I see that it uses that name to create the shadow property.
Sorry, I read the section https://docs.microsoft.com/en-us/ef/core/modeling/shadow-properties Conventions and assumed I needed to stick with them. Now I know different. Thanks.
PS. I have updated my original post to say this was a mistake on my part.
Punting this for 2.0
@AndriySvyryd. Great news. Thanks for dealing with that.
Hi @AndriySvyryd, I would like to check this fix out, as my book will go to the publishers before 2.1.0 will be released. Is there a nightly NuGet build I can access?
@JonPSmith Information about the nightly builds are here: https://github.com/aspnet/Home/wiki/NuGet-feeds - allow for a few days for fixes to pass through
Hi @AndriySvyryd .
I couldn't find any NuGet package that looks like it has this fix in it. Am I missing something here?
@JonPSmith The daily builds are on MyGet, not NuGet - see my link above...
tks :+1:
Hi everyone. First of all sorry if I'm missing something really obvious here.
My scenario is as follows:
I have an entity called: Program and another called: Process. The relationship between them is many to many, so i have a third entity to represent that relationship.
public class Program
{
public int Id { get; private set; }
public List<ProgramProcess> Processes { get; private set; }
}
public class Process
{
public int Id { get; private set; }
// ...
}
public class ProgramProcess
{
public int ProgramId { get; private set; }
public Program Program { get; private set; }
public int ProcessId { get; private set; }
public Process Process { get; private set; }
// Constructor
}
public class MyContext : IdentityDbContext<MyUsers>
{
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ProgramProcess>()
.HasKey(x => new {x.ProgramId, x.ProcessId});
// I also configure the realtionship using the FluentApi
}
}
Users can select what processes a program have, so when they edit an existing program I prefer to replace the whole collection of ProgramProcess with the new one. That didn't work in version 1.0, but it does in 2.1.
Recently I've made ProgramProcess inherit from IEquitable to make the two id values represent the identity of the entity, but it stop working, showing the following exception:
System.InvalidOperationException: The instance of entity type 'ProgramProcess' cannot be tracked because another instance with the same key value for {'ProgramId', 'ProcessId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Is it supposed to be that way? is there something wrong with my approach? Or maybe is a bug?
@joalcava "I've made ProgramProcess inherit from IEquitable to make the two id values represent the identity of the entity." EF can only track a single instance for an entity with any given ID.
@ajcvickers Hello! What should I do, if I want to add a few owned instances?
Now using this code 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.'
My domain classes:
public class Parent
{
public int Id { get; set; }
public virtual Child Name { get; set; }
}
public class Child
{
public virtual List<ChildItem> Items { get; set; }
}
public class ChildItem
{
public string Text { get; set; }
public string Language { get; set; }
}
My DbContext:
public class ApplicationContext : DbContext
{
public ApplicationContext()
{
Database.EnsureCreated();
}
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");
}
}
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"}
}
}
};
context.Set<Parent>().Add(parent);
context.SaveChanges();
}
@irmtim What versions of EF Core are you using?
@ajcvickers EF Core version 2.2.3
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.
I just updated to .Net Core 3.0 and this stopped working for my owned types, I cannot replace them with new ones. It was working on 2.2
@joalcava Please create a new issue with a small repro project.
@AndriySvyryd I've created the issue, #18066
Most helpful comment
Hi @divega,
Clearly there is an alternative to the one-to-one PK+FK pattern I am using and I will swap to that. For that reason this issue isn't a priority at all.
My only long-term concern is that the one-to-one PK+FK pattern looks and feels like a proper relationship until you try and change an existing relationship. All of my example code is a) simple and b) has a unit tests, so I caught this problem easily. Someone else might hit this problem and struggle to diagnose it.