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);