I have Payments and Reviews.
public class Payment {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int PaymentId { get; set; }
[ForeignKey("Review")]
public int? ReviewId { get; set; }
public virtual Review Review { get; set; }
// other properties
}
public class Review {
[Key]
public int ReviewId { get; set; }
[ForeignKey("PeerPayment")]
public int? PeerPaymentId { get; set; }
public virtual PeerPayment PeerPayment { get; set; }
}
I've mapped them as follows:
public ReviewConfiguration() {
// One-to-One optional:optional
HasOptional(s => s.Payment).WithMany().HasForeignKey(x => x.PaymentId);
}
public PaymentConfiguration() {
// One-to-One optional:optional
HasOptional(s => s.Review).WithMany().HasForeignKey(s => s.ReviewId);
}
My database FKs are created as nullable, which is great. However the following test fails:
public async Task ReviewPeerPaymentWorks() {
using (var db = new AppContext()) {
var user1 = db.FindUser("M1");
var user2 = db.FindUser("M2");
var review = new Review() {
FromUserId = user1.UserId,
ToUserId = user2.UserId
};
db.Reviews.Add(review);
var payment = new Payment() {
FromUserId = user1.UserId,
ToUserId = user2.UserId,
ReviewId = review.ReviewId
};
db.Payments.Add(payment);
db.SaveChanges();
review = db.Reviews.First(s => s.ReviewId == review.ReviewId);
payment = db.Payments.First(s => s.PaymentId == payment.PaymentId);
Assert.AreEqual(review.PaymentId, payment.PaymentId); // fails - review.PaymentId is always null
Assert.AreEqual(review.ReviewId, payment.ReviewId); // passes
Assert.AreEqual(review.Payment.PaymentId, payment.Review.PaymentId); // fails - review.Payment is null, payment.Review.PaymentId is null
}
}
What am I doing wrong?
To be clear - I don't want to modify my test, unless someone thinks it's an invalid way to test the relationship for some reason.
One-to-One relation is a bit tricky in the EF. Especially in Code-First.
In the DB-First I would make the UNIQUE constraint on the both FK properties to make sure that this relation appear only once per table (but please make sure that it's not One-to-One-or-Zero because UNIQUE constraint will allow you to have only one NULL FK value). In case of the NULLable FK it can be done via the 3rd relation table with the UNIQUE constraint over all columns in relation table.
Here is the URL which explains how EF maps the One-to-One relationship: http://www.entityframeworktutorial.net/entity-relationships.aspx
Here is the URL which explains how to configure One-to-One relationship: http://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx
Generally they suggest to use the same PK values for both tables:
"So, we need to configure above entities in such a way that EF creates
Students and StudentAddresses table in the DB where it will make
StudentId in Student table as PK and StudentAddressId column in
StudentAddress table as PK and FK both."
I have tried your example and you did two independent One-to-Many relations between Review and Payment. But without the Many navigation property.
Look WithMany():
.HasOptional(s => s.Review).WithMany().HasForeignKey(s => s.ReviewId);
So it's real One-To-Many relation with no MANY (List) navigation properties. Look at the MSSQL diagram:
And the reason why did you get the NULL values - because you have assigned Payment.ReviewId relation and didn't assign the Review.PaymentId relation. But you expected EF did it automatically. Since they are separate relations - EF left one of the as NULL because you didn't assign it
public async Task ReviewPeerPaymentWorks() {
using (var db = new AppContext()) {
var user1 = db.FindUser("M1");
var user2 = db.FindUser("M2");
var review = new Review() {
FromUserId = user1.UserId,
ToUserId = user2.UserId
};
db.Reviews.Add(review);
var payment = new Payment() {
FromUserId = user1.UserId,
ToUserId = user2.UserId,
ReviewId = review.ReviewId
};
db.Payments.Add(payment);
db.SaveChanges();
review.Payment = payment;
db.SaveChanges();
review = db.Reviews.First(s => s.ReviewId == review.ReviewId);
payment = db.Payments.First(s => s.PaymentId == payment.PaymentId);
Assert.AreEqual(review.PaymentId, payment.PaymentId); // fails - review.PaymentId is always null
Assert.AreEqual(review.ReviewId, payment.ReviewId); // passes
Assert.AreEqual(review.Payment.PaymentId, payment.Review.PaymentId); // fails - review.Payment is null, payment.Review.PaymentId is null
}
}
I have faced this issue two times. Each time I have removed those two entities from Edmx and re-added. It solves my issue.
Hopefully it will work with you
Related
The minimal project sources to reproduce the issue is here :
https://wetransfer.com/downloads/8d9325ce7117bb362bf0d61fc7c8571a20220708100401/326add
===================
This error is a classic; In layman's terms it is usually caused by a "bad" insertion when a navigation is not properly taken in account, causing a faulty Ef state somewhere.
Many solutions have been posted along the years but I fail to see how my specific scenario could cause the issue!
My schema is a many-to-many between Groups and Users. The middle entity is named GroupUser.
There's a twist : Each GroupUser has an owned entity containing extra data, DataPayload. This choice was made for versatility -- we wanted that payload to be stored in its own table.
Schema:
public class User {
public Guid Id { get; private set; }
public IList<GroupUser> GroupUsers { get; private set; } = new List<GroupUser>();
public User(Guid id) { Id = id; }
}
public class Group {
public Guid Id { get; private set; }
public Group(Guid id) { Id = id; }
}
public class GroupUser {
public Guid Id { get; private set; }
public Guid GroupId { get; private set; }
public Guid UserId { get; private set; }
// Navigation link
public Group? Group { get; private set; }
public DataPayload? Data { get; private set; }
public GroupUser(Guid groupId, Guid userId, DataPayload data) {
Id = Guid.NewGuid(); //Auto generated
UserId = userId;
GroupId = groupId;
Data = data;
}
// This extra constructor is only there to make EF happy! We do not use it.
public GroupUser(Guid id, Guid groupId, Guid userId) {
Id = id;
UserId = userId;
GroupId = groupId;
}
}
public class DataPayload {
//Note how we did not defined an explicit Id; we let EF do it as part of the "Owned entity" mechanism.
///// <summary>foreign Key to the Owner</summary>
public Guid GroupUserId { get; private set; }
public int DataValue { get; private set; }
public DataPayload(int dataValue) {
DataValue = dataValue;
}
public void SetDataValue(int dataValue) {
DataValue = dataValue;
}
}
To make it all work, we configure the navigations like this :
// --------- Users ---------
builder
.ToTable("Users")
.HasKey(u => u.Id);
// --------- Groups ---------
builder
.ToTable("Groups")
.HasKey(g => g.Id);
// --------- GroupUsers ---------
builder
.ToTable("GroupUsers")
.HasKey(gu => gu.Id);
builder
.HasOne<User>() //No navigation needed
.WithMany(u => u.GroupUsers)
.HasForeignKey(gu => gu.UserId);
builder
.HasOne<Group>(gu => gu.Group) //Here, we did define a navigation
.WithMany()
.HasForeignKey(gu => gu.GroupId);
builder
.OwnsOne(gu => gu.Data,
navBuilder => {
navBuilder.ToTable("PayloadDatas");
navBuilder.Property<Guid>("Id"); //Note: Without this EF would try to use 'int'
navBuilder.HasKey("Id");
//Configure an explicit foreign key to the owner. It will make our life easier in our Unit Tests
navBuilder.WithOwner().HasForeignKey(d => d.GroupUserId);
}
);
//.OnDelete(DeleteBehavior.Cascade) // Not needed (default behaviour for an owned entity)
Now, you know how everything is defined.
Basic setup : works!
var group = new Group(groupId);
await dbContext.Groups.AddAsync(group);
await dbContext.SaveChangesAsync();
var user = new User(userId);
await dbContext.Users.AddAsync(user);
await dbContext.SaveChangesAsync();
Follow-up scenario : fails!
var groupUser = new GroupUser(groupId, userId, new DataPayload(dataValue: 777777));
user.GroupUsers.Add(groupUser);
await dbContext.SaveChangesAsync(); // Crash happens here!!!
Error:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException : The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded.
I suspect that EF gets confused by the addition of two entities at once, where it has to compute some Ids itself : the new GroupUser and the DataPayload it contains. I'm not sure how it's supposed to know that it needs to give an Id to the GroupUser first and then use that Id as the foreign key in PayloadData. But that's just me; it might or might not be related to the issue.
But what do I need to change?
The mistake was in GroupUser's id-less constructor:
Id = Guid.NewGuid();
The code needs to let EF manage the keys when it comes to owned entities such as DataPayload which rely on a foreign key (GroupUserId) that's still in the making at the time of saving.
If you set a key value (Guid.NewGuid()) yourself, then EF gets confused between:
linking the new DataPayload entity to the GroupUser entity where you've shoehorned an Id value,
OR
just expecting an empty value (foreign key) and setting all the keys (both the GroupUser's Id and DataPayload's GroupUserId) itself.
All in all, EF feels like you announced that you were about to let it create 1 entity, but you've pulled the rug under its feet and done it yourself, so it ends up with 0 entity to work with. Hence the error message.
It should have been :
Id = Guid.Empty;
With Guid.Empty, EF clearly identifies that this entity is new and that has to be the same one as the one you told it to create and link to the new PayloadData -- that is, the instance that you've set in GroupUser.Data.
Given these classes:
public class A
{
public int Id {get; set;}
public int? BId {get; set;}
public B B {get; set;}
}
public class B
{
public int Id {get; set;}
public int? AId {get; set;}
public A A {get; set;}
}
Then with Fluent API
modelBuilder.Entity<A>()
.HasOne(a => a.B)
.WithOne(b => b.A)
.HasForeignKey<A>(a => a.BId);
When creating objects and add them to database, things look like following in the corresponding tables:
[A].BId is set
[B].AId = null
When I retrieve data using EF Core:
A.B is set, A.BId is set
B.A is set, but B.AId is null.
What should I do to have B.AId set as well?
These 0..1 : 0..1 relations are usually defined between entities of which none is an obvious principal entity. I like the example of cars and drivers, which is a bit more imaginable than A and B.
The model you're after looks like this:
There are two mutual foreign keys, both of which have a unique index to enforce 1:1 at the database level.
The HasOne - WithOne combi can't be used here, because that always requires a HasForeignKey instruction to tell which entity is principal. This also configures only one field as foreign key. In your example, B.AId is just a regular field. If you don't give it a value, EF won't either.
The mapping of the above model is a bit more cumbersome than HasOne - WithOne:
var carEtb = modelBuilder.Entity<Car>();
var driverEtb = modelBuilder.Entity<Driver>();
carEtb.HasOne(c => c.Driver).WithMany();
carEtb.HasIndex(c => c.DriverID).IsUnique();
driverEtb.HasOne(d => d.Car).WithMany();
driverEtb.HasIndex(c => c.CarID).IsUnique();
So there are two 0..1:n association that are made unique by indexes on the foreign keys.
Which creates the following database model:
CREATE TABLE [Drivers] (
[ID] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
[CarID] int NULL,
CONSTRAINT [PK_Drivers] PRIMARY KEY ([ID])
);
CREATE TABLE [Cars] (
[ID] int NOT NULL IDENTITY,
[Brand] nvarchar(max) NULL,
[Type] nvarchar(max) NULL,
[DriverID] int NULL,
CONSTRAINT [PK_Cars] PRIMARY KEY ([ID]),
CONSTRAINT [FK_Cars_Drivers_DriverID] FOREIGN KEY ([DriverID])
REFERENCES [Drivers] ([ID]) ON DELETE NO ACTION
);
CREATE UNIQUE INDEX [IX_Cars_DriverID] ON [Cars] ([DriverID])
WHERE [DriverID] IS NOT NULL;
CREATE UNIQUE INDEX [IX_Drivers_CarID] ON [Drivers] ([CarID])
WHERE [CarID] IS NOT NULL;
ALTER TABLE [Drivers] ADD CONSTRAINT [FK_Drivers_Cars_CarID] FOREIGN KEY ([CarID])
REFERENCES [Cars] ([ID]) ON DELETE NO ACTION;
It creates two nullable foreign keys both indexed by a unique filtered index. Perfect!
But...
EF doesn't see this as a bidirectional one-on-one relationship. And rightly so. The two FKs are just that, two independent FKs. However, in view of data integrity the relationship should be established by both ends: if a driver claims a car (sets driver.CarID), the car should also be attached to the driver (set car.DriverID), otherwise another driver could be connected to it.
When existing car and drivers are coupled a little helper method could be used, for example in Car:
public void SetDriver(Driver driver)
{
Driver = driver;
driver.Car = this;
}
However, when both a Car and Driver are created and associated in one process, this is clumsy. EF will throw an InvalidOperationException:
Unable to save changes because a circular dependency was detected in the data to be saved: 'Car [Added] <- Car { 'CarID' } Driver [Added] <- Driver { 'DriverID' } Car [Added]'.
Which means: one of the FKs can be be set at once, but the other one can only be set after saving the data. That requires two SaveChanges calls enclosed by a transaction in a pretty imperative piece of code:
using (var db = new MyContext())
{
using (var t = db.Database.BeginTransaction())
{
var jag = new Car { Brand = "Jaguar", Type = "E" };
var peter = new Driver { Name = "Peter Sellers", Car = jag };
db.Drivers.Add(peter);
db.SaveChanges();
jag.Driver = peter;
db.SaveChanges();
t.Commit();
}
}
Alternative: junction table
So now the reason why I go to these lengths explaining all this: in my opinion, 0..1 : 0..1 associations should be modeled by a junction table with unique foreign keys:
By using a junction table -
The association can be established in an atomic operation instead of an error-prone operation of setting two foreign keys.
The entities themselves are independent: they don't have foreign keys they don't really need to fulfill their role.
This model can be implemented by this class model:
public class Car
{
public int ID { get; set; }
public string Brand { get; set; }
public string Type { get; set; }
public CarDriver CarDriver { get; set; }
}
public class Driver
{
public Driver()
{ }
public int ID { get; set; }
public string Name { get; set; }
public CarDriver CarDriver { get; set; }
}
public class CarDriver
{
public int CarID { get; set; }
public Car Car { get; set; }
public int DriverID { get; set; }
public virtual Driver Driver { get; set; }
}
And the mapping:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var carDriverEtb = modelBuilder.Entity<CarDriver>();
carDriverEtb.HasKey(cd => new { cd.CarID, cd.DriverID });
carDriverEtb.HasIndex(cd => cd.CarID).IsUnique();
carDriverEtb.HasIndex(cd => cd.DriverID).IsUnique();
}
Now creating drivers and cars and their associations can easily be done in one SaveChanges call:
using (var db = new MyContext(connectionString))
{
var ford = new Car { Brand = "Ford", Type = "Mustang" };
var jag = new Car { Brand = "Jaguar", Type = "E" };
var kelly = new Driver { Name = "Kelly Clarkson" };
var peter = new Driver { Name = "Peter Sellers" };
db.CarDrivers.Add(new CarDriver { Car = ford, Driver = kelly });
db.CarDrivers.Add(new CarDriver { Car = jag, Driver = peter });
db.SaveChanges();
}
The only drawback is that navigting from Car to Driver vv is a bit les convenient. Well, see for yourself which model suit you best.
In EFCore 3.1 you can do one to zero relation like this:
public class Car
{
public int Id { get; set; }
public int DriverId { get; set; }
public Driver Driver { get; set; }
}
public class Driver
{
public int Id { get; set; }
public Car Driver { get; set; }
}
in your dbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasOne(x => x.Driver)
.WithOne(x => x.Car)
.HasForeignKey<Car>(x => x.DriverId);
}
Then you can include:
driver from car
dbContext.Driver.Inclide(x => x.Car)...
car from driver
dbContext.Car.Inclide(x => x.Driver)...
I need some help understanding the error I'm getting when I try to update a product.
I have read this similar question, and tried the accepted answer (placing a _context.SaveChanges() after each table, before the final saving of the complete product), but I still get the same error as described below.
These are the involved models:
public class Product
{
public int Id { get; set; }
// some more properties
public ICollection<IdentifierForProduct> Identifiers { get; set; }
}
public class IdentifierForProduct
{
public int Id { get; set; }
public int ProductId { get; set; }
public int ProductIdentifierId { get; set; }
public string Value { get; set; } // E.g. "4902505154881"
public ProductIdentifier Identifier { get; set; }
public Product Product { get; set; }
}
public class ProductIdentifier
{
public int Id { get; set; }
public string Label { get; set; } // E.g. "EAN"
public ICollection<IdentifierForProduct> ProductIdentifiers { get; set; }
}
Initially, after form post, the Identifiers are set (VMProduct is the product view model):
List<IdentifierForProduct> Identifiers = new List<IdentifierForProduct>();
if (VMProduct.Identifiers != null)
{
for (var i = 0; i < VMProduct.Identifiers.Count; i++)
{
Identifiers.Add(new IdentifierForProduct
{
ProductId = VMProduct.Id,
ProductIdentifierId = VMProduct.Identifiers[i].Id,
Value = VMProduct.Identifiers[i].Value
});
}
}
Then the product properties are altered according to the changes made in the form:
Product DbM = await GetProduct(VMProduct.Id);
// some more properties are set
DbM.Identifiers = Identifiers;
_context.Update(DbM);
await _context.SaveChangesAsync();
This exception is thrown on await _context.SaveChangesAsync();:
SqlException: The MERGE statement conflicted with the FOREIGN KEY constraint "FK_IdentifiersForProducts_ProductIdentifiers_ProductIdentifierId". The conflict occurred in database "MyStore", table "dbo.ProductIdentifiers", column 'Id'.
The statement has been terminated.
System.Data.SqlClient.SqlCommand+<>c.b__108_0(Task result)
DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch+d__32.MoveNext()
This is the GetProduct() method:
public async Task<Product> GetProduct(int Id)
{
Product DbM = await _context.Products
.Include(ic => ic.InCategories)
.ThenInclude(pc => pc.ProductCategory)
.Include(t => t.Type)
.ThenInclude(i => i.Identifiers) // ProductIdentifiersInTypes
.ThenInclude(i => i.Identifier)
.Include(t => t.Type)
.ThenInclude(p => p.Properties) // ProductPropertiesInTypes
.ThenInclude(p => p.Property)
.ThenInclude(o => o.Options)
.Include(p => p.ProductPropertyOptions)
.Where(p => p.Id == Id)
.SingleOrDefaultAsync();
return DbM;
}
The reason why this error happens is because, your foreign key 'ProductIdentifierId' in 'IdentifierForProduct' probably has value 0 at here:
List<IdentifierForProduct> Identifiers = new List<IdentifierForProduct>();
if (VMProduct.Identifiers != null)
{
for (var i = 0; i < VMProduct.Identifiers.Count; i++)
{
Identifiers.Add(new IdentifierForProduct
{
ProductId = VMProduct.Id,
ProductIdentifierId = VMProduct.Identifiers[i].Id, //here, your id should be 0
Value = VMProduct.Identifiers[i].Value
});
}
}
When entity framework core encounters value 0 for foreign key, it throws this kind of error, because it cannot insert foreign value 0 which is the primary key of some object. Obviously, primary keys cannot be value 0.
I've had to deal with the same exact problem, and the zeros where 'must have' in my scenario. If that's the case for you, you can always disable the foreign key constraint:
Solution in Summary:
Remove auto increment from primary keys and define sequences to generate the Ids for the entities.
When the entities are being saved, before saving the entities assign the IDs generated through the sequence to the parent objects and child objects.
Detailed Answer:
I encountered a similar issue when creating entities with the foreign key constraints where the parent entities are not yet created. Problem could be explained using the following entity relationship diagram.
Here the Author has Books and the Book has chapters. Book has the AuthorId which is a FK to Author and Chapter has FKs to Author and Book. All the Ids in the entities are auto increment integers.
Problem:
When I create Author, add books and chapters and save them, I get the following exception.
List chaptersList1 = new List();
chaptersList1.Add(new Chapter() { ChapterName = "Chapter 1" });
chaptersList1.Add(new Chapter() { ChapterName = "Chapter 2" });
chaptersList1.Add(new Chapter() { ChapterName = "Chapter 3" });
List bookList = new List();
bookList.Add(new Book() { BookName = "Book 1", Chapters = chaptersList1 });
Author author = new Author()
{
AuthorName = "Author 1",
Books = bookList
};
using (var context = BookDbContextPoolHelper.GetConnectionFactory().CreateDbContext())
{
context.Add(author);
context.SaveChanges();
}
Microsoft.Data.SqlClient.SqlException (0x80131904): The MERGE statement conflicted with the FOREIGN KEY constraint "FK_Chapter_Author". The conflict occurred in database "TestWork", table "bookstore.Author", column 'Id'.
The statement has been terminated.
This is basically saying that when the chapter is being saved, it does not know of a Author with the given Id.
Resolution:
Remove the auto increment Ids from Author and Book.
Create Sequences to generate Ids for the Author and Book.
Write an extension method to get the Next value of the sequence for a given sequence.
Override the SaveChanges method of the db context and assign the Ids for Author, Book and Chapter before saving the changes.
private void BeforeSavingChanges()
{
var newAuthors = base.ChangeTracker.Entries().Where(x => x.Entity is Author && x.State == EntityState.Added).ToList();
foreach (var entry in newAuthors)
{
var newAuthor = (Author)entry.Entity;
newAuthor.Id = this.NextValueForSequence(Sequence.AuthorIdSequence);
foreach (var newBook in newAuthor.Books)
{
newBook.Id = this.NextValueForSequence(Sequence.BookIdSequence);
newBook.AuthorId = newAuthor.Id;
foreach (var chapter in newBook.Chapters)
{
chapter.AuthorId = newAuthor.Id;
chapter.BookId = newBook.Id;
}
}
}
}
public override int SaveChanges()
{
BeforeSavingChanges();
return base.SaveChanges();
}
Sample project to demonstrate the problem and solution can be found here. Problem scenario is in the 'master' and the solution is in the 'fix' branch.
I need to insert an entity WITH a related entity inside, both in a single DbSet.Add invocation.
One-to-many between Course and CourseProfesor (CourseProfesor is the entity connecting Courses and Profesors)
Entities:
public class Course
{
public Course() { }
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
...
public virtual ICollection<CourseProfesor> Profesors { get; set; }
}
public class CourseProfesor
{
public CourseProfesor() { }
[Key]
public int ID { get; set; }
[Required, Index, Column(Order = 0)]
public int CourseID { get; set; }
[Required, Index, Column(Order = 1)]
public int ProfesorID { get; set; }
...
[ForeignKey("CourseID")]
public virtual Course Course { get; set; }
[ForeignKey("ProfesorID")]
public virtual Profesor Profesor { get; set; }
}
Mappings:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().HasMany(x => x.Profesors).WithRequired(x => x.Course);
modelBuilder.Entity<CourseProfesor>().HasRequired(x => x.Course).WithMany(x => x.Profesors);
modelBuilder.Entity<CourseProfesor>().HasRequired(x => x.Profesor).WithMany(x => x.Courses);
}
Controller:
public ActionResult Add(Course course, int profesorId = 0)
{
if (profesor > 0)
{
course.Profesors = new List<CourseProfesor>();
course.Profesors.Add(new CourseProfesor() { Course = course, CourseID = 0, ProfesorID = profesorId, From = DateTime.Now, Role = ... });
}
Facade.Create(course);
return Json(new {statusText = "Course Added"});
}
Facade.Create(entity) executes a CreateCommand which will in turn invoke
DbContext.Set(entity.GetType()).Add(entity)
The exception I get:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property value(s) of 'Course.ID' on one end of a relationship do not match the property value(s) of 'CourseProfesor.CourseID' on the other end
how to assign CourseProfesor.CourseID if I don't know the ID of the course yet, since both are new entities?
As you can see in the controller code, I used to worked that out that by setting only the navigation property and EF would auto-populate foreign key accordingly.
This is important: This was working fine on EF5, I got that error after updating to EF6
Any clues why EF6 throws that exception while EF5 didn't? and how to solve it without having to first create the Course and then the CourseProfesor relationship entity?
A couple of things stand-out:
course.Profesors.Add(new CourseProfesor() { Course = course, CourseID = 0, ProfesorID = profesorId, From = DateTime.Now, Role = ... });
When using navigation properties I steer away from defining the FKs fields in the entities, but if you need to define them, you should avoid setting them. Use the navigation properties only. Setting FKs can be misleading because if you were to pass this Course out with it's CourseProfessors and consume it, there would be a ProfessorID set, but no Professor reference available.
Regarding your specific problem the probable issues would be the CourseID = 0 set above, and the two-way mapping between the Course and CourseProfessor.
modelBuilder.Entity<Course>().HasMany(x => x.Profesors).WithRequired(x => x.Course);
//modelBuilder.Entity<CourseProfesor>().HasRequired(x => x.Course).WithMany(x => x.Profesors);
modelBuilder.Entity<CourseProfesor>().HasRequired(x => x.Profesor).WithMany(x => x.Courses);
Try removing that redundant mapping.
Also the definition of the linking table has an ID as a PK, but isn't set up with a generation option. The two FKs are set up Order=0 and Order=1 as well, which looks odd without considering the PK?
Beyond that, I can't say I like that facade pattern. To eliminate the possibility, what happens if you go to the DB Context Courses DBSet?
context.Courses.Add(course);
context.SaveChanges();
None of the similair questions seem to be able to solve this problem. I'm using Entity Framework 5, MVC 4, .NET 4.5 for my web app, designed with VS 2012.
I have 2 classes that are supposed to be in a parent-child relationship.
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
// Other stuff
public int? JoggerId { get; set; }
public virtual Jogger Jogger{ get; set; }
}
and
public class Jogger
{
[Key]
public int JoggerId { get; set; }
[Required, ForeignKey("UserId")]
public int UserId { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
With Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserProfile>()
.HasOptional(x => x.Jogger)
.WithRequired(c => c.UserProfile)
.WillCascadeOnDelete(true);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
A User can be a Jogger but can also not be a Jogger i.e. one User to zero or one Jogger. the relationship looks fine on the EF Powertools edmx view but I cannot get the Foreign key to work with the UserProfile UserId.
Is my Fluent API wrong or is it my models? Please help - I am truly stuck!
Your mapping with Fluent API is OK, but remove the UserProfile.JoggerId and the Jogger.UserId properties from your model and it should work. The reason is that Entity Framework uses the primary key of Jogger as the foreign key to UserProfile, so you don't need to (and can't) have a separate foreign key property. This kind of one-to-one relationship is called "Shared Primary Key Association".
Edit
Keep in mind that the primary key of Jogger (as the dependent of the relationship) is not autogenerated in the database, only the primary key of UserProfile is autogenerated (as the principal of the relationship).
The way how you would insert a UserProfile or a Jogger or both into the database is the following:
If you want to insert a UserProfile without a Jogger just add it to the context:
using (var context = new MyContext())
{
var newUserProfile = new UserProfile();
// no key needs to be supplied, the DB will take care
context.UserProfiles.Add(newUserProfile);
context.SaveChanges();
}
If you want to insert a UserProfile with a Jogger in a single step:
using (var context = new MyContext())
{
var newUserProfile = new UserProfile { Jogger = new Jogger() };
// no key needs to be supplied, neither for UserProfile
// nor for Jogger, the DB will take care
context.UserProfiles.Add(newUserProfile);
context.SaveChanges();
}
If you want to insert a Jogger (and it must be a Jogger for an existing UserProfile):
using (var context = new MyContext())
{
var newJogger = new Jogger { JoggerId = someExistingUserProfileId };
// a valid key needs to be supplied, otherwise you would violate a FK
// constraint in the database
context.Joggers.Add(newJogger);
context.SaveChanges();
}
Edit 2
For your use case where you don't have the UserId directly available but the Name (as authenticated user) instead you must load the key or the UserProfile first from the database:
// we are in an ASP.NET MVC controller action here
using (var context = new MyContext())
{
string userName = User.Identity.Name;
int? userId = context.UserProfiles
.Where(u => u.Name == userName)
.Select(u => (int?)u.UserId)
.SingleOrDefault();
if (userId.HasValue)
{
var newJogger = new Jogger { JoggerId = userId.Value };
context.Joggers.Add(newJogger);
context.SaveChanges();
}
}
Or loading the UserProfile will work as well:
using (var context = new MyContext())
{
string userName = User.Identity.Name;
UserProfile userProfile = context.UserProfiles
.Where(u => u.Name == userName)
.SingleOrDefault();
if (userProfile != null)
{
userProfile.Jogger = new Jogger();
// I believe you don't need to set the JoggerId key now,
// I'm not sure though
context.SaveChanges();
// Change tracking recognizes the new Jogger
// no context.Joggers.Add is required
}
}