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

Данное руководство устарело. Актуальное руководство: Руководство по Entity Framework Core 7

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

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

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

OwnedAttribute

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

using Microsoft.EntityFrameworkCore;

namespace HelloApp
{
    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; }
		public ApplicationContext()
        {
            Database.EnsureDeleted();
            Database.EnsureCreated();
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=relationsdb;Trusted_Connection=True;");
        }
    }
}

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

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

CREATE TABLE [dbo].[Users] (
    [Id]           INT            IDENTITY (1, 1) NOT NULL,
    [Login]        NVARCHAR (MAX) NULL,
    [Password]     NVARCHAR (MAX) NULL,
    [Profile_Name] NVARCHAR (MAX) NULL,
    [Profile_Age]  INT            NULL,
    CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC)
);

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

public static void Main(string[] args)
{
	// добавление данных
	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 Microsoft.EntityFrameworkCore;

namespace HelloApp
{
    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; }
		public ApplicationContext()
        {
            Database.EnsureDeleted();
            Database.EnsureCreated();
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=relationsdb;Trusted_Connection=True;");
        }
		protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<User>().OwnsOne(u => u.Profile);
        }
    }
}

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

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

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

using Microsoft.EntityFrameworkCore;

namespace HelloApp
{
    public class User
    {
        public 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; }
        public ApplicationContext()
        {
            Database.EnsureDeleted();
            Database.EnsureCreated();
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=relationsdb;Trusted_Connection=True;");
        }
        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());
}

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

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

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 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, который представляет отдельные данные по принципу ключ-значение.

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

public class ApplicationContext : DbContext
{
	public DbSet<User> Users { get; set; }
	public ApplicationContext()
	{
		Database.EnsureDeleted();
		Database.EnsureCreated();
	}
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
		optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=relationsdb;Trusted_Connection=True;");
	}
	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 [dbo].[Users] (
    [Id]                 INT            IDENTITY (1, 1) NOT NULL,
    [Login]              NVARCHAR (MAX) NULL,
    [Password]           NVARCHAR (MAX) NULL,
    [Profile_Name_Key]   NVARCHAR (MAX) NULL,
    [Profile_Name_Value] NVARCHAR (MAX) NULL,
    [Profile_Age_Key]    NVARCHAR (MAX) NULL,
    [Profile_Age_Value]  NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC)
);

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

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