В принципе название свойства - внешнего ключа необязательно должно следовать выше описанным условностям. Чтобы установить свойство в качестве внешнего ключа, применяется атрибут [ForeignKey]:
using System.ComponentModel.DataAnnotations.Schema; public class Company { public int Id { get; set; } public string? Name { get; set; } // название компании public List<User> Users { get; set; } = new(); } public class User { public int Id { get; set; } public string? Name { get; set; } public int? CompanyInfoKey { get; set; } [ForeignKey("CompanyInfoKey")] public Company? Company { get; set; } }
И пусть будет следующий контекст данных:
using Microsoft.EntityFrameworkCore; public class ApplicationContext : DbContext { public DbSet<User> Users { get; set; } = null!; public DbSet<Company> Companies { get; set; } = null!; public ApplicationContext() { Database.EnsureDeleted(); Database.EnsureCreated(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Data Source=helloapp.db"); } }
В случае БД SQLite будут генерироваться следующие таблицы:
CREATE TABLE "Users" ( "Id" INTEGER NOT NULL, "Name" TEXT, "CompanyInfoKey" INTEGER, CONSTRAINT "PK_Users" PRIMARY KEY("Id" AUTOINCREMENT), CONSTRAINT "FK_Users_Companies_CompanyInfoKey" FOREIGN KEY("CompanyInfoKey") REFERENCES "Companies"("Id") ); CREATE TABLE "Companies" ( "Id" INTEGER NOT NULL, "Name" TEXT, CONSTRAINT "PK_Companies" PRIMARY KEY("Id" AUTOINCREMENT) );
Для настройки отношений между моделями с помощью Fluent API применяются специальные методы: HasOne / HasMany / WithOne / WithMany.
Методы HasOne и HasMany устанавливают навигационное свойство для сущности, для которой производится конфигурация. Далее
могут идти вызовы методов WithOne
и WithMany
, который идентифицируют навигационное свойство на стороне связанной сущности.
Методы HasOne/WithOne применяются для обычного навигационного свойства, представляющего одиночный объект, а методы
HasMany/WithMany
используются для навигационных свойств, представляющих коллекции. Сам же внешний ключ устанавливается с помощью метода
HasForeignKey:
using Microsoft.EntityFrameworkCore; public class ApplicationContext : DbContext { public DbSet<User> Users { get; set; } = null!; public DbSet<Company> Companies { get; set; } = null!; public ApplicationContext() { Database.EnsureDeleted(); Database.EnsureCreated(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Data Source=helloapp.db"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>() .HasOne(u => u.Company) .WithMany(c => c.Users) .HasForeignKey(u => u.CompanyInfoKey); } } public class Company { public int Id { get; set; } public string? Name { get; set; } // название компании public List<User> Users { get; set; } = new(); } public class User { public int Id { get; set; } public string? Name { get; set; } public int? CompanyInfoKey { get; set; } public Company? Company { get; set; } }
В этом случе будут сгенерированы такие же таблицы, как и в предыдущем примере.
Кроме того, с помощью Fluent API мы можем связь внешнего ключа не только с первичными ключами связанных сущностей, но и с другими свойствами. Например:
using Microsoft.EntityFrameworkCore; public class ApplicationContext : DbContext { public DbSet<User> Users { get; set; } = null!; public DbSet<Company> Companies { get; set; } = null!; public ApplicationContext() { Database.EnsureDeleted(); Database.EnsureCreated(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Data Source=helloapp.db"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>() .HasOne(u => u.Company) .WithMany(c => c.Users) .HasForeignKey(u => u.CompanyName) .HasPrincipalKey(c => c.Name); } } public class Company { public int Id { get; set; } public string? Name { get; set; } // название компании public List<User> Users { get; set; } = new(); } public class User { public int Id { get; set; } public string? Name { get; set; } public string? CompanyName { get; set; } public Company? Company { get; set; } }
Метод HasPrincipalKey указывает на свойство связанной сущности, на которую будет ссылаться свойство-внешний ключ CompanyName. Кроме того, для
свойства, указанного в HasPrincipalKey()
, будет создавать альтернативный ключ.
Определение таблицы Users в SQLite будет выглядеть следующим образом:
CREATE TABLE "Users" ( "Id" INTEGER NOT NULL, "Name" TEXT, "CompanyName" TEXT, CONSTRAINT "PK_Users" PRIMARY KEY("Id" AUTOINCREMENT), CONSTRAINT "FK_Users_Companies_CompanyName" FOREIGN KEY("CompanyName") REFERENCES "Companies"("Name") );
В программе при добавлении объектов в БД в этом случае можно установить как навигационное свойство, так и свойство внешнего ключа:
using (ApplicationContext db = new ApplicationContext()) { Company company1 = new Company { Name = "Google" }; Company company2 = new Company { Name = "Microsoft" }; User user1 = new User { Name = "Tom", Company = company1 }; User user2 = new User { Name = "Bob", CompanyName = "Microsoft" }; User user3 = new User { Name = "Sam", CompanyName = company2.Name }; db.Companies.AddRange(company1, company2); db.Users.AddRange(user1, user2, user3); db.SaveChanges(); foreach (var user in db.Users.ToList()) { Console.WriteLine($"{user.Name} работает в {user.Company?.Name}"); } }
Результат работы будет тот же самый.