W jednym z poprzednich wpisów nauczyliśmy się tworzenia relacji jeden do wielu korzystając z domyślnych konwencji (EF Core - relacja jeden do wielu). Pojawia się zatem pytanie: Dlaczego mielibyśmy używać konfiguracji przy użyciu Fluent API skoro EF Core posiada wbudowane konwencje do tworzenia tego typu relacji? Odpowiedź jest prosta: takie podejście będzie łatwiejsze z perspektywy przyszłego zarządzania projektem.
Spójrzmy na poniższy przykład dwóch klas: Customer oraz Order. Tworząc relację jeden do wielu będziemy w stanie utworzyć wiele różnych zamówień zrealizowanych przez danego Klienta:
public class Customer { public int Id { get; set; } public string FullName { get; set; } public ICollection<Order> Orders { get; set; } } public class Order { public int Id { get; set; } public string OrderName { get; set; } public int CustomerId { get; set; } public Customer Customer { get; set; } }
Mając w pamięci poprzedni wpis: konfiguracji dokonamy nadpisując metodę OnModelCreating zgodnie z poniższym przykładem:
public class ApplicationDbContext : DbContext { public DbSet<Customer> Customers { get; set; } public DbSet<Order> Orders { get; set; } public ApplicationDbContext() { } protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder) { if (!optionBuilder.IsConfigured) { optionBuilder .UseSqlServer(@"Server=PAWEL;database=EFCoreFluentAPIOneToMany;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 wielu modelBuilder.Entity<Order>() .HasOne<Customer>(c => c.Customer) .WithMany(o => o.Orders) .HasForeignKey(c => c.CustomerId); } }
Zanim przejdziemy przez proces migracji i utworzenie schematu po stronie bazy danych postarajmy się zrozumieć powyższy kod:
-
W pierwszym kroku rozpoczynamy nasz proces konfiguracyjny – możemy rozpocząć od dowolnej klasy, tj. Customer lub Order. W naszy przypadku rozpoczynamy od klasy Order:
modelBuilder.Entity<Order>()
-
Następnie wykorzystując metodę HasOne określamy, że encja Order zawiera właściwość typu Customer o nazwie Customer:
.HasOne<Customer>(c => c.Customer)
-
Kolejny krok to konfiguracja drugiego końca relacji, tj. tabeli naszych Klientów. Dzięki metodzie WithMany określamy, że encja Customer zawiera wiele encji Order:
.WithMany(c => c.Orders)
-
Brakuje nam jeszcze klucza obcego. Ten, zgodnie z konwencjami, może ale nie musi być zdefiniowany – ja preferuje takie podejście, tzn. pełnej definicji, z uwagi na czytelność kodu. Jeżeli posługujemy się pełną defincją wykorzystamy metodę HasForeignKey w celu określenia nazwy klucza obcego:
.HasForeignKey(o => o.CustomerId);
Mam nadzieję, że wszystko jest jasne. Wykorzystajmy teraz dostępne polecenia migracji, tj. add-migration oraz update-database i sprawdźmy czy nasze relacje zostały poprawnie wygenerowane:
Jak doskonale widać została utworzona relacja jeden do wielu. Dany Klient może złożyć wiele różnych zamówień na produkty dostępne w naszym sklepie.
Nieco wcześniej wspomniałem, że konfigurację możemy rozpocząć korzystając z dowolnej strony relacji. Tym razem rozpoczniemy od encji Customer odwracając nieco zależności:
modelBuilder.Entity<Customer>() .HasMany<Order>(o => o.Orders) .WithOne(c => c.Customer) .HasForeignKey(c => c.CustomerId);