W tym wpisie poznamy sposób na konfigurację relacji jeden do jednego pomiędzy dwiema encjami jeżeli nie przestrzegają one domyślnej konwencji Entity Framework Core. Taka sytuacja może mieć miejsce jeżeli właściwość klucza lub klucza obcego nie jest zgodna z konwencją wspomnianą w poprzednim wpisie (mam tutaj na myśli nazewnictwo poszczególnych właściwości): EF Core - konwencje. W takiej sytuacji możemy posłużyć się atrybutami adnotacji danych lub Fluent API.
Nasz przykład utworzymy na bazie dwóch encji: Person oraz Passport (są oczywiście Państwa, które pozwalają na posiadanie drugiego paszportu ale w ramach przykładu zakładamy oczywiście inny scenariusz…):
public class Person { public int Id { get; set; } public string FullName { get; set; } // referencyjna właściwość nawigacyjna public Passport Passport { get; set; } } public class Passport { public int Id { get; set; } // Numer paszportu to zbiór liter i cyfr public string Number { get; set; } public string City { get; set; } public string Street { get; set; } // klucz obcy odpowiadający poniżej właściwości nawigacyjnej public int PassportNumberOfPersonId { get; set; } // referencyjna właściwość nawigacyjna public Person Person { get; set; } }W przypadku konfiguracji relacji jeden do jednego posłużymy się następującymi metodami udostępnionymi w ramach Fluent API, tj. HasOne, WithOne oraz HasForeignKey:
public class ApplicationDbContext : DbContext { public DbSet<Person> Person { get; set; } public DbSet<Passport> Passport { get; set; } public ApplicationDbContext() { } protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder) { if (!optionBuilder.IsConfigured) { optionBuilder .UseSqlServer(@"Server=PAWEL;database=EFCoreFluentAPIOneToOne;Integrated Security=true;MultipleActiveResultSets=true").EnableSensitiveDataLogging(true); } } protected override void OnModelCreating(ModelBuilder modelBuilder) { // W tym miejscu możemy przygotować konfigurację // wykorzystując Fluent API // Konfiguracja relacji jeden do jednego modelBuilder.Entity<Person>() .HasOne<Passport>(p=>p.Passport) .WithOne(pp=>pp.Person) .HasForeignKey<Passport>(pp=>pp.PassportNumberOfPersonId) } }
Dokonajmy analizy powyższego kodu:
-
W pierwszym kroku dokonujemy rozpoczęcia procesu konfiguracji rozpoczynając od encji Person:
modelBuilder.Entity<Person>()
-
Drugi krok to oznacznie, iż encja Person zawiera jedną właściwość referencyjną typu Passport:
.HasOne<Passport>(p=>p.Passport)
-
W trzecim kroku konfigurujemy drugi koniec relacji, tj. encję Passport. Poniższy zapis mówi, iż zawiera ona właściwość referencjną Person:
.WithOne(pp=>pp.Person)
-
Ostatni krok to identyfikacja pola będącego kluczem obcym:
.HasForeignKey<Passport>(pp=>pp.PassportNumberOfPersonId)
Dokonajmy migracji w celu sprawdzenia jak będzie wyglądał schemat naszej bazy danych:
Wykorzystując Fluent API udało nam się wygenerować poprawny schemat bazy dla relacji jeden do jednego. EF Core utworzył również indeks unikalny na kolumnie klucza obcego PassportNumberOfPersonId w tabeli Passport. Indeks ten zapewnia, że wartość kolumny PassportNumberOfPersonId będzie unikalna w orębie tabeli Passport co jest podstawowym wymaganiem w relacji jeden do jednego.
Podobnie jak w przypadku poprzedniego wpisu nic nie stoi na przeszkodzie, żeby proces konfiguracji rozpocząć od encji Passport:
modelBuilder.Entity<Passport>() .HasOne<Person>(pp => pp.Person) .WithOne(p => p.Passport) .HasForeignKey<Passport>(pp => pp.PassportNumberOfPersonId);