Собственные типы

Последнее обновление: 25.11.2023

Обычно Entity Framework Core сопоставляет каждый класс с отдельной таблицей. Однако бывают случаи, когда определенный класс существует не сам по себе, а несет некоторую дополнительную информацию по отношению к другой главной модели. Данная функциональность предполагает, что у нас есть несколько типов, но один из них является владельцем (owner). Другие же зависимые или собственные типы (owned entity types) являются частью типа-владельца и без него существовать не могут. Подобные зависимые типы не имеют свойство ключа

Для создания связки "главный тип - зависимые типы" можно использовать либо атрибут OwnedAttribute, либо настройки Fluent API.

OwnedAttribute

Атрибут OwnedAttribute позволяет установить зависимый тип. Например:

using Microsoft.EntityFrameworkCore;

public class User
{
    public int Id { get; set; }
    public string? Login { get; set; }
    public string? Password { get; set; }
    public UserProfile? Profile { get; set; }
}
[Owned]
public class UserProfile
{
    public string? Name { get; set; }
    public int Age { get; set; }
}
public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; } = null!;
    public ApplicationContext()
    {
        Database.EnsureDeleted();
        Database.EnsureCreated();
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=helloapp.db");
    }
}

В данном случае есть тип-владелец User и есть владеемый им тип UserProfile. Класс User определяет основные настройки учетной записи - логин и пароль, а UserProfile - всторостепенные данные - имя, возраст и т.д. Чтобы указать, что User владеет типом UserProfile, над UserProfile указывается атрибут OwnedAttribute. Кроме того, UserProfile не содержит ключа.

В итоге при создании базы данных данные обоих типов будут содержаться в одной таблице:

CREATE TABLE "Users" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Users" PRIMARY KEY AUTOINCREMENT,
    "Login" TEXT NULL,
    "Password" TEXT NULL,
    "Profile_Name" TEXT NULL,
    "Profile_Age" INTEGER NULL
)

Использование типов:

// добавление данных
using (ApplicationContext db = new ApplicationContext())
{
    User user1 = new User
    {
        Login = "login1",
        Password = "pass1234",
        Profile = new UserProfile { Age = 23, Name = "Tom" }
    };
    User user2 = new User
    {
        Login = "login2",
        Password = "5678word2",
        Profile = new UserProfile { Age = 27, Name = "Alice" }
    };
    db.Users.AddRange(user1, user2);
    db.SaveChanges();
    // добавление данных
    var users = db.Users.ToList();
    foreach (User u in users)
        Console.WriteLine($"Name: {u.Profile?.Name}  Age: {u.Profile?.Age}  Login: { u.Login} Password: { u.Password} ");
}

Fluent API

Также для настройки связи можно использовать Fluent API, в частности, метод OwnsOne():


using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

using Microsoft.EntityFrameworkCore;

public class User
{
    public int Id { get; set; }
    public string? Login { get; set; }
    public string? Password { get; set; }
    public UserProfile? Profile { get; set; }
}
public class UserProfile
{
    public string? Name { get; set; }
    public int Age { get; set; }
}
public class ApplicationContext : DbContext
{
    public DbSet<User> Users { 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>().OwnsOne(u => u.Profile);
    }
}

В методе OwnsOne() указывается навигационное свойство, которое представляет зависимый тип.

Приватные типы

Вполне возможно, что мы захотим сделать навигационное свойство Profile приватным. В этом случае понадобится дополнительная настройка:

using Microsoft.EntityFrameworkCore;

public class User
{
    private User() { }
    public User(string login, string password, UserProfile profile)
    {
        Login = login; Password = password; Profile = profile;
    }
    public int Id { get; set; }
    public string? Login { get; set; }
    public string? Password { get; set; }
    private UserProfile? Profile { get; set; }
    public override string ToString()
    {
        return $"Name: {Profile?.Name}  Age: {Profile?.Age}  Login: {Login} Password: {Password}";
    }
}
public class UserProfile
{
    public string? Name { get; set; }
    public int Age { get; set; }
}
public class ApplicationContext : DbContext
{
    public DbSet<User> Users { 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>().OwnsOne(typeof(UserProfile), "Profile");
    }
}

В этом случае в метод OwnsOne передается тип навигационного свойства и само имя навигационного свойства. Применение:

using (ApplicationContext db = new ApplicationContext())
{
	User user1 = new User("login1", "pass1234", new UserProfile { Age = 23, Name = "Tom" });
	User user2 = new User("login2","5678word2", new UserProfile { Age = 27, Name = "Alice" });
	db.Users.AddRange(user1, user2);
	db.SaveChanges();

	var users = db.Users.ToList();
	foreach (User user in users)
		Console.WriteLine(user.ToString());
}

Вложенные собственные типы

Одни собственные типы могут иметь другие собственные типы, увеличивая сложность структуры классов. Например, определим следующие классы:

using System.ComponentModel.DataAnnotations;

public class User
{
    public int Id { get; set; }
    public string? Login { get; set; }
    public string? Password { get; set; }
	[Required]
    public UserProfile? Profile { get; set; }
}
public class UserProfile
{
    public Claim? Name { get; set; }
    public Claim? Age { get; set; }
}
public class Claim
{
    public string? Key { get; set; }
    public string? Value { get; set; }
}

Главной сущностью здесь является класс User, который содержит объект класса UserProfile. Класс UserProfile, в свою очередь, также содержит объекты еще одного класса - Claim, который представляет отдельные данные по принципу ключ-значение. Обратите внимание, что свойство Profile является обязательным.

Теперь определим контекст данных:

using Microsoft.EntityFrameworkCore;

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { 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>().OwnsOne(u => u.Profile, p =>
        {
            p.OwnsOne(c => c.Name);
            p.OwnsOne(c => c.Age);
        });
    }
}

С помощью метода OwnsOne() устанавливается включение через свойства собственных типов. И после создания базы данных мы получим следующую таблицу User, которая будет содержать данные, представленные собственными типами.

CREATE TABLE "Users" (
	"Id"	INTEGER NOT NULL,
	"Login"	TEXT,
	"Password"	TEXT,
	"Profile_Name_Key"	TEXT,
	"Profile_Name_Value"	TEXT,
	"Profile_Age_Key"	TEXT,
	"Profile_Age_Value"	TEXT,
	CONSTRAINT "PK_Users" PRIMARY KEY("Id" AUTOINCREMENT)
);

Для работы с собственными типами определим следующий код:

using (ApplicationContext db = new ApplicationContext())
{
	User user1 = new User
	{
		Login = "login1",
        Password = "pass1234",
        Profile = new UserProfile
        {
			Age =new Claim { Key = "Age", Value = "23" },
            Name = new Claim { Key="Name", Value="Tom"}
		}
	};
    User user2 = new User
    {
		Login = "login2",
        Password = "5678word2",
        Profile = new UserProfile
        {
			Age = new Claim { Key = "Age", Value = "27" },
            Name = new Claim { Key = "Name", Value = "Alice" }
		}
	};
	db.Users.AddRange(user1, user2);
	db.SaveChanges();

	var users = db.Users.ToList();
    foreach (User user in users)
		Console.WriteLine($"Name: {user.Profile?.Name?.Value}  Age: {user.Profile?.Age?.Value} Login: {user.Login}  Password: {user.Password}");
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850