Entity Framework 6 code-first relations with Fluent API - c#

I have 3 related tables in an existing database.
Many-to-Many (Many Person Many Group) and a table between - Event, witch ralate these two tables.
public class Person
{
public int PersonID { get; set; }
public string No { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class Event
{
public int EventID { get; set; }
public string EventData { get; set; }
public int PersonID { get; set; }
public int GroupID { get; set; }
public virtual Person Person { get; set; }
public virtual Group Group { get; set; }
}
public class Group
{
public int GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> PersonGroup { get; set; }
}
I ddescribed relations using Fluent API.
First I declared PK's
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID });
modelBuilder.Entity<Event>()
.HasKey(k => k.EventID);
modelBuilder.Entity<Group>()
.HasKey(k => k.GroupID });
Now foreign keys:
Person has many Events
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID })
.HasMany(k => k.Events)
.WithRequired()
.HasForeignKey(f => f.PersonID);
In Event class I have Person (and all its parameters)
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany()
.HasForeignKey(fk => fk.PersonID);
Also I need a Group with all data:
modelBuilder.Entity<Event>()
.HasOptional(s => s.Group)
.WithMany()
.HasForeignKey(fk => fk.GroupID });
At least I need a Person group participating in an Event
modelBuilder.Entity<Group>()
.HasKey(k =>k.GroupID })
.HasMany(k => k.PersonGroup)
.WithOptional()
.HasForeignKey(fk => fk.GroupID);
It seems I have everything I need, but I need one mo collection (Group of persons with their names)
What I get through PersonGroup relation I have all events but also need to get Persons. Could You help?

Edit
I just realize it's not a typical many-to-many, since your Event-Group relation is optional. Is this intended?
Maybe your model should be changed to reflect a more natural event structure:
Group-Person: many-to-many
Event-Group: many-to-many
Event-Person: no direct relation, only via groups; alternative many-to-many
The way your model is currently designed, a single event entry can't be related to more than one group and one person and a person can't be part of a group unless they are associated in the context of an event entry.
Basically, the thing you ask for is not directly available, because you decided to explicitely create the many-to-many table with additional properties.
However, in queries you can always write a select to get the persons collection
db.Groups.Select(g => new {Group = g, PeopleInGroup = g.PersonGroup.Select(ev => ev.Person)})
Few side-notes regarding your model:
Consider removing EventID and instead use modelBuilder.Entity<Event>().HasKey(k => new { k.PersonID, k.GroupID }), like a typical many-to-many table would be designed.
Mention reverse properties in fluent api:
.
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID })
.HasMany(k => k.Events)
.WithRequired(e => e.Person)
.HasForeignKey(f => f.PersonID);
// redundant with the previous configuration
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany(p => p.Events)
.HasForeignKey(fk => fk.PersonID);
// same to be done for groups
In order to have a convenient access to the associated people of a group, you could create a not-mapped property getter which wraps the necessary query:
public class Group
{
public int GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> PersonGroup { get; set; }
[NotMapped]
public IEnumerable<Person> PersonsInGroupEvents
{
return PersonGroup.Select(ev => ev.Person);
}
}
// or fluent api way of NotMapped:
modelBuilder.Entity<Group>()
.Ignore(x => x.PersonsInGroupEvents);

Related

EF Core – Complicated Relationships

I have complicated relationships between these entities:
Country
Airport
Airline
Flight
Country has many Airlines and many Airports.
Airline has one Countries, the same about Airport.
Airline has many Flights,
Airport has many DepartureFlights and ArrivalFlights (both are Flight type).
Flight has one Airline and one DepartureAirport and one ArrivalAirport (both are Airport type).
Country can have no airlines and airports,
Airline can have no Flights,
Airport can have neither DepartureFlights nor ArrivalFlights.
What I am trying to do is when Country is deleted, then all related Airlines and Airports are deleted, also when Airline or DepartureAirport or ArrivalAirport are deleted, all related Flights are deleted also,
but when updating my db after the migration is created I'm getting an error:
Introducing FOREIGN KEY constraint "FK_Flights_Airports_DepartureAirport" on table "Flights" may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
How to implement this behavior and prevent an error?
Here are my models:
Country:
public class Country
{
public int Id { get; set; }
/* other properties */
public virtual ICollection<Airport>? Airports { get; set; }
public virtual ICollection<Airline>? Airlines { get; set; }
}
Airline:
public class Airline
{
public int Id { get; set; }
/* other properties */
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<Flight>? Flights { get; set; }
}
Airport:
public class Airport
{
public int Id { get; set; }
public string Name { get; set; }
/* other properties */
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<Flight>? DepartureFlights { get; set; }
public virtual ICollection<Flight>? ArrivalFlights { get; set; }
}
Flight:
public class Flight
{
public int Id { get; set; }
/* other properties */
public int AirlineId { get; set; }
public int DepartureAirportId { get; set; }
public int ArrivalAirportId { get; set; }
public virtual Airline Airline { get; set; }
public virtual Airport DepartureAirport { get; set; }
public virtual Airport ArrivalAirport { get; set; }
}
After all the DBContext file:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Airline> Airlines { get; set; }
public DbSet<Airport> Airports { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Flight> Flights { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Airline>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airlines)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity<Airport>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airports)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity<Country>(x =>
{
x.HasKey(c => c.Id);
});
modelBuilder.Entity<Flight>(x =>
{
x.HasKey(f => f.Id);
x.HasOne(a => a.Airline)
.WithMany(f => f.Flights)
.HasForeignKey(f => f.AirlineId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
x.HasOne(a => a.DepartureAirport)
.WithMany(f => f.DepartureFlights)
.HasForeignKey(f => f.DepartureAirportId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
x.HasOne(a => a.ArrivalAirport)
.WithMany(f => f.ArrivalFlights)
.HasForeignKey(f => f.ArrivalAirportId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
}
}
How to implement this behavior and prevent an error?
This is officially known issue. Therefore, We have two ways to handle this scenario thus the error:
1. Change one or more of the relationships to not cascade delete.
In this scenario, we could make the Country relationship with Airport, Flight and Airlines optional by giving it a nullable foreign key property: for instance we can do something like:
.IsRequired(false);
Note: You can check our official document for more details.
2. Configure the database without one or more of these cascade deletes,
then ensure all dependent entities are loaded so that EF Core can
perform the cascading behavior.
Considering this appreach we can keep the Airport, Flight and Airlines relationship required and configured for cascade delete, but make this configuration only apply to tracked entities, not the database: So we can do somethng like below:
modelBuilder.Entity<Airline>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airlines)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.ClientCascade);
.IsRequired(false);
});
modelBuilder.Entity<Airport>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airports)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.ClientCascade);
.IsRequired(false);
});
Note: You can apply same for Flight as well. In addition, As you may know OnDelete(DeleteBehavior.ClientCascade); or ClientCascade allows the DBContext to delete entities even if there is a cyclic ref or LOCK on it. Please read the official guideline for more details here
add these lines in "OnModelCreating"
var cascades = modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetForeignKeys())
.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);
foreach (var fk in cascades)
fk.DeleteBehavior = DeleteBehavior.Restrict;

Entity Framework Core: Many To Many relationship with some null values for some records

Let's say I have the following 3 classes - a Company that employs ConstructionWorkers and TruckDrivers. Let's say those people can be employed at many companies (as they work part time) - so it's a many to many relationship.
Some companies will only employ TruckDrivers, others will only employ ConstructionWorkers and others yet will employ both. This means in my CompanyEmployeeMapping table, the record could look like this:
CompanyEmployeeMapping Table:
+-------------+------------------+------------------------+
| CompanyId | TruckDriverId | ConstructionWorkerId |
+-------------+------------------+------------------------+
| 1 | 10 | NULL |
+-------------+------------------+------------------------+
(ie - Company only employs Truck Drivers)
This is how I set up my Class Structure + Fluent API:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CompanyEmployeeMapping> Employees { get; set; }
}
public class ConstructionWorker
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CompanyEmployeeMapping> Companies { get; set; }
}
public class TruckDriver
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CompanyEmployeeMapping> Companies { get; set; }
}
public class CompanyEmployeeMapping
{
public int CompanyId { get; set; }
public int TruckDriverId { get; set; }
public int ConstructionWorkerId { get; set; }
[JsonIgnore]
public Company LinkedCompany { get; set; }
[JsonIgnore]
public TruckDriver LinkedTruckDriver { get; set; }
[JsonIgnore]
public ConstructionWorker LinkedConstructionWorker { get; set; }
}
Fluent API setup:
builder.Entity<CompanyEmployeeMapping>()
.HasKey(x => new { x.CompanyId, x.TruckDriverId, x.ConstructionWorkerId });
builder.Entity<CompanyEmployeeMapping>()
.HasOne(c => c.LinkedCompany)
.WithMany(m => m.LinkedEmployees)
.HasForeignKey(x => x.CompanyId)
.HasPrincipalKey(x => x.Id);
builder.Entity<CompanyEmployeeMapping>()
.HasOne(c => c.LinkedTruckDriver)
.WithMany(m => m.LinkedCompanies)
.HasForeignKey(x => x.TruckDriverId)
.HasPrincipalKey(x => x.Id);
builder.Entity<CompanyEmployeeMapping>()
.HasOne(c => c.LinkedConstructionWorker)
.WithMany(m => m.LinkedCompanies)
.HasForeignKey(x => x.ConstructionWorkerId)
.HasPrincipalKey(x => x.Id);
This seems to work correctly for me - I get an object with companies, containing a list of TruckDrivers and a list of ConstructionWorkers. Each of those also have their corresponding companies.
QUESTION
When I try to add a new company, which only employs TruckDrivers I get an exception:
The value of CompanyEmployeeMapping.ConstructionWorkerId is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
What am I doing wrong here?
It looks like each instance of the mapping entity will only map to either TruckDriver or ConstructionWorker.
Therefore, your TruckDriver and ConstructionWorker ids need to be nullable.
This will then create a new challenge that your composite key cannot contain a nullable property.
I think the way forward is to make those ints nullable and then create a new single property EmployeeMapping Id for the mapping entity key.

EF Core - many to many relationship use / access custom join table

I am trying to implement a many to many relationship.
The Models -
public class User
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<Book> OwnedBooks { get; set; }
}
public class Own
{
public int UserId { get; set; }
public int BookId { get; set; }
public User User { get; set; }
public Book Book { get; set; }
}
public class Book
{
[Key]
public int Id { get; set; }
public int AuthorId { get; set; }
public User Author { get; set; }
public List<User> OwnedBy { get; set; } //Not really needed, but without it I can't create the join table "Own"
[NotMapped]
public int UsersReached; //Get this via the "Own" table
}
The DbContext -
public class TestContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<Own> Own { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlServer("Server=DESKTOP-BT4H8CA;Database=Test;Trusted_Connection=True");
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Book>().HasOne(x => x.Author);
builder.Entity<User>()
.HasMany(x => x.OwnedBooks)
.WithMany(x => x.OwnedBy)
.UsingEntity(x => x.ToTable("Own"));
builder.Entity<Own>()
.HasKey(x => new {x.BookId, x.UserId});
}
}
I am struggling with accessing the join table "Own". I need it to get the amount of each Book that is sold, without completely loading the users. That's why I don't want to use the auto generated one:
Cannot use table 'Own' for entity type 'BookUser (Dictionary<string, object>)' since it is being used for entity type 'Own' and potentially other entity types, but there is no linking relationship. Add a foreign key to 'BookUser (Dictionary<string, object>)' on the primary key properties and pointing to the primary key on another entity typed mapped to 'Own'.
Thanks in advance for your help!
You can actually use the auto-generated joining table and still get the count of each book sold, without completely loading the users.
With your current User and Book models, configure the relationships as -
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Book>()
.HasOne(p => p.Author)
.WithMany()
.HasForeignKey(p => p.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<User>()
.HasMany(p => p.OwnedBooks)
.WithMany(p => p.OwnedBy);
}
Then you can query the books with their count of sales as -
var books = dbCtx.Books
.Select(p => new Book
{
Id = p.Id,
AuthorId = p.AuthorId,
Author = p.Author,
UsersReached = p.OwnedBy.Count // this will not load User entities
})
.ToList();
EDIT:
You can use AutoMapper which can do the projection in .Select() method for you, like -
var dtos = _Mapper.ProjectTo<BookDTO>(dbCtx.Books).ToList();
For that, you'll need to -
create a DTO model with properties you want from the query result, like -
public class BookDTO
{
public int Id { get; set; }
public string Author { get; set; }
public int UsersReached { get; set; }
}
define a map from Book to BookDTO -
CreateMap<Book, BookDTO>()
.ForMember(d => d.Author, opt => opt.MapFrom(s => s.Author.Name))
.ForMember(d => d.UsersReached, opt => opt.MapFrom(s => s.OwnedBy.Count));
You can remove the [NotMapped] property UsersReached from the Book model.

EF Core: Invalid column name 'companyId1'

I have trouble understanding why a EF generated SELECT clause contains the primary key twice, the second one is postfixed with '1'.
exec sp_executesql N'SELECT [entity.WebAdminCompanyUser].[CompanyId], [entity.WebAdminCompanyUser].[AspNetUserId], [entity.WebAdminCompanyUser].[CompanyId1]
FROM [SafeProtect].[WebAdminCompanyUser] AS [entity.WebAdminCompanyUser]
INNER JOIN (
SELECT [entity1].[AspNetUserId]
FROM [SafeProtect].[WebAdminUser] AS [entity1]
WHERE ([entity1].[RowEnabled] = 1) AND EXISTS (
SELECT 1
FROM [SafeProtect].[WebAdminCompanyUser] AS [companyUser1]
WHERE ([companyUser1].[CompanyId] = #__companyId_0) AND ([entity1].[AspNetUserId] = [companyUser1].[AspNetUserId]))
) AS [t0] ON [entity.WebAdminCompanyUser].[AspNetUserId] = [t0].[AspNetUserId]
ORDER BY [t0].[AspNetUserId]',N'#__companyId_0 int',#__companyId_0=1
It fails with Invalid column name 'CompanyId1'.
Following are the entities and the corresponding configurations (fluent API):
WebAdminCompanyUser:
public partial class WebAdminCompanyUser : ITrackable, IMergeable
{
public WebAdminCompanyUser()
{
AdditionalInit();
}
public int CompanyId { get; set; }
public int AspNetUserId { get; set; }
public virtual Company Company { get; set; }
[NotMapped]
public TrackingState TrackingState { get; set; }
[NotMapped]
public ICollection<string> ModifiedProperties { get; set; }
[NotMapped]
public Guid EntityIdentifier { get; set; }
partial void AdditionalInit();
}
}
Configuration:
builder.Entity<WebAdminCompanyUser>(entity =>
{
entity.ToTable(name: "WebAdminCompanyUser", schema: SqlSchema.SafeProtect);
entity.HasKey("CompanyId", "AspNetUserId");
entity
.HasOne(d => d.Company)
.WithMany()
.HasForeignKey(d => d.CompanyId)
.IsRequired();
});
WebAdminUser:
public partial class WebAdminUser : IdentityUser<int>, IAuditInfo, IRowDisableableWithDateTime, ITrackable, IMergeable
{
public WebAdminUser()
{
WebAdminCompanyUser = new HashSet<WebAdminCompanyUser>();
WebAdminUserRole = new HashSet<WebAdminUserRole>();
WebAdminUserClaim = new HashSet<WebAdminUserClaim>();
WebAdminUserLogin = new HashSet<WebAdminUserLogin>();
AdditionalInit();
}
public string DisplayName { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public bool RowEnabled { get; set; }
public DateTime? DisabledOn { get; set; }
public virtual ICollection<WebAdminCompanyUser> WebAdminCompanyUser { get; set; }
public virtual ICollection<WebAdminUserRole> WebAdminUserRole { get; set; }
public virtual ICollection<WebAdminUserClaim> WebAdminUserClaim { get; set; }
public virtual ICollection<WebAdminUserLogin> WebAdminUserLogin { get; set; }
[NotMapped]
public TrackingState TrackingState { get; set; }
[NotMapped]
public ICollection<string> ModifiedProperties { get; set; }
[NotMapped]
public Guid EntityIdentifier { get; set; }
partial void AdditionalInit();
}
Configuration:
builder.Entity<WebAdminUser>(entity =>
{
entity.ToTable(name: "WebAdminUser", schema: SqlSchema.SafeProtect);
entity.Property(e => e.Id).HasColumnName("AspNetUserId");
// authorize multiple user name
entity.HasIndex((p) => new { p.UserName }).IsUnique(false);
entity
.HasMany(user => user.WebAdminUserClaim)
.WithOne()
.HasForeignKey(userClaims => userClaims.UserId)
.IsRequired();
entity
.HasMany(user => user.WebAdminUserLogin)
.WithOne()
.HasForeignKey(userLogin => userLogin.UserId)
.IsRequired();
entity
.HasMany(user => user.WebAdminUserRole)
.WithOne()
.HasForeignKey(userRole => userRole.UserId)
.IsRequired();
entity
.HasMany(user => user.WebAdminCompanyUser)
.WithOne()
.HasForeignKey(companyUser => companyUser.AspNetUserId)
.IsRequired();
});
EF query:
IQueryable<WebAdminUser> query =
from WebAdminUser user WebAdminUserRepository.All()
.Include(user => user.WebAdminUserRole)
.ThenInclude(userRole => userRole.AspNetRole)
.Include(user => user.WebAdminCompanyUser)
where user.WebAdminCompanyUser.Any(companyUser => companyUser.CompanyId == companyId)
select user;
return query.ToList();
Any help appreciated.
This usually happens when you have improperly mapped relationship by leaving some navigation property out of fluent configuration.
Remember that each navigation property (collection or reference) represents a relationship. If you fluently configure relationships and use HasOne / HasMany / WithOne / WithMany w/o passing the navigation property, you are telling EF that the relationship has no navigation property for the corresponding end. But if you actually do have navigation property, EF will map it to a separate relationship with default FK column name. If the default property/column name is already used, EF will append index to it until it gets unique.
In your case, the WebAdminUser class and configuration you've shown are irrelevant. The invalid column name CompanyId1 indicates that the problem is with Company class which you haven't shown, and the WithMany() call here
.HasOne(d => d.Company)
.WithMany() // <--
Most likely your Company class has collection navigation property to WebAdminCompanyUser, something like this (virtual and the name of the property doesn't matter):
public virtual ICollection<WebAdminCompanyUser> CompanyUsers { get; set; }
then you need to change the above .WithMany() call with something like
.WithMany(c => c.CompanyUsers)
and the problem will be solved.

EF Can't update one-to-zero or one relationship fields, but updates the rest

Am trying to make an update on the database with one-to-one relationship models, but only the Student model fields are the only ones working. How can I make the rest update too?
This is my Repository method for updating :
public void Edit(Student student)
{
var existingStudent = _context.Students
.Include(e => e.Education)
.Include(s => s.Siblings)
.Include(p => p.Parents)
.Include(g => g.Guardian)
.Single(s => s.Id == student.Id);
if (existingStudent != null)
{
// do some updating.
_context.Attach(existingStudent);
_context.Entry(existingStudent).CurrentValues.SetValues(student);
_context.Entry(existingStudent).State = EntityState.Modified;
_context.SaveChanges();
}
}
Then, this is my Student model class :
public class Student
{
[Key]
public long Id { get; set; }
[Display(Name="First Name")]
public string FirstName { get; set; }
[Display(Name="Middle Name")]
public string MiddleName { get; set; }
[Display(Name="Last Name")]
public string LastName { get; set; }
[Display(Name="Nationality")]
public string Nationality { get; set; }
[Display(Name="Gender")]
public string Gender { get; set; }
[Display(Name="Religion")]
public string Religion { get; set; }
[Display(Name="Medical Condition")]
public string MedicalCondition { get; set; }
[Display(Name="Deceased")]
public string Deceased { get; set; }
[Display(Name="Home Address")]
public string HomeAddress { get; set; }
[Display(Name="Country Of Residence")]
public string CountryOfResidence { get; set; }
[Display(Name="City")]
public string City { get; set; }
[Display(Name="Date Of Birth")]
public DateTime DateOfBirth { get; set; }
public int Age { get; set; }
public virtual Parents Parents { get; set; }
public virtual Education Education { get; set; }
public virtual Guardian Guardian { get; set; }
public virtual Siblings Siblings { get; set; }
}
Then one of the one-to-one relationship classes is as below :
public class Siblings
{
public long Id { get; set; }
[Display(Name="Number of Brothers")]
public int NumberOfBrothers { get; set; }
[Display(Name="Number of Sisters")]
public int NumberOfSisters { get; set; }
public Student Student { get; set; }
public long? StudentId { get; set; }
}
The rest of the related model classes Parents, Education, Guardian are the same as Siblings.
How can I be able to ensure that the update cuts across all the fields. Thanks.
EDIT
This is what I have in OnModelCreating() :
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Student>()
.HasOne(e => e.Education)
.WithOne(s => s.Student)
;
builder.Entity<Student>()
.HasOne(g => g.Guardian)
.WithOne(s => s.Student);
builder.Entity<Student>()
.HasOne(p => p.Parents)
.WithOne(s => s.Student);
builder.Entity<Student>()
.HasOne(s => s.Siblings)
.WithOne(s => s.Student);
builder.Entity<Education>()
.HasOne(s => s.Student)
.WithOne(e => e.Education);
builder.Entity<Guardian>()
.HasOne(s => s.Student)
.WithOne(g => g.Guardian);
builder.Entity<Parents>()
.HasOne(s => s.Student)
.WithOne(p => p.Parents);
builder.Entity<Siblings>()
.HasOne(s => s.Student)
.WithOne(p => p.Siblings);
}
I suggest you try two ways below:
Modify the navigation property
_context.Entry(existingStudent).CurrentValues.SetValues(student);
_context.Entry(existingStudent).State = EntityState.Modified;
_context.Entry(existingStudent.Siblings).CurrentValues.SetValues(student.Siblings);
_context.Entry(existingStudent.Siblings).State = EntityState.Modified;
Update with _context.Update
var existingStudent = _context.Student
.Include(s => s.Siblings)
.AsNoTracking()
.Single(s => s.Id == 4);
if (existingStudent != null)
{
existingStudent = student;
_context.Update(existingStudent);
_context.SaveChanges();
}
I'm not sure if you have any Fluent configuration, but if you don't,
As seen in this link
You need to configure the relationship using the Fluent API.
Without knowing your complete requirement, I'm going to take a stab in the dark that this is probably what you're looking for.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasOptional(s => s.Siblings) // Mark Siblings property optional in Student entity
.WithRequired(ad => ad.Student); // mark Student property as required in Siblings entity. Cannot save Siblings without Student
}
EDIT
If HasOptional and WithRequired are giving you red squiggles, then you're most likely using EF CORE and not EF 6, unless you have a major problem elsewhere.
Addressing your current problem, in your Fluent configuration (OnModelCreating())
First, what you are looking for is not how to configure one-to-one relationships. Your intending to configure one-to-zero or one relationships.
Second, given the following configuration
builder.Entity<Student>()
.HasOne(s => s.Siblings)
.WithOne(s => s.Student);
This means: Entity<Student> requires one Sibling, and Entity<Sibling> requires one Student So your configuration is not doing what you want it to do.
Last, since EF Core has some pretty smart conventions, it will actually figure out that your nullable foreign key means it's optional, and will create your (0->0..1) relationship for you with out any config.
The Solution, simply remove all fluent config from your OnModelCreating() and let EF do it's job. And don't forget to update-database.

Categories