Генерация значений свойств и столбцов

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

Если при добавлении или обновлении нового объекта у него уже установлено значение для свойства, Entity Framework использует это значение при вставке или обновлении в таблицу. Если для свойства явным образом не установлено значение, то для свойства устанавливается значение по умолчанию (null для nullable-типов, 0 для int, Guid.Empty для Guid и т.д.).

В зависимости от используемого провайдера базы данных, значения для свойств могут генерироваться на стороне клиента с помощью EF, либо же генерироваться уже на стороне базы данных при добавлении. Если значение генерируется базой данных, тогда при добавлении объекта в контекст EF может назначить временное значение. Это временное значение будет заменено значением, сгенерированным базой данных при вызове метода SaveChanges().

Генерация ключей

По умолчанию для свойств первичных ключей, которые представляют типы int или GUID и которые имеют значение по умолчанию, генерируется значение при вставке в базу данных. Для всех остальных свойств значения по умолчанию не генерируется.

Например, пусть у нас имеет следующая модель:

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

public class User
{
    public int Id { get; set; }
    public string? Name { get; set; }
}

То после добавления в базу данных мы сможем получить сгенерированный Id:

using (ApplicationContext db = new ApplicationContext())
{
    User user = new User { Name = "Tom" };
    Console.WriteLine($"Id перед добавлением в контекст {user.Id}");	// Id = 0
    db.Users.Add(user);
    db.SaveChanges();
    Console.WriteLine($"Id после добавления в базу данных {user.Id}");	// Id = 1
}

Атрибут DatabaseGeneratedAttribute

Атрибут DatabaseGeneratedAttribute представляет аннотацию, которая позволяет изменить поведение базы данных при добавлении или изменении.

Например, мы хотим отключить автогенерацию значения при добавлении:

using System.ComponentModel.DataAnnotations.Schema;

public class User
{
	[DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }
    public string? Name { get; set; }
}

И если теперь мы попробуем добавить объект без установленного Id, то EF в качесте временного значения будет использовать значение по умолчанию, то есть Id=0. В итоге при добавление более одного объекта в бд мы получим ошибку:

using (ApplicationContext db = new ApplicationContext())
{
	db.Users.Add(new User { Name = "Tom" });
	db.Users.Add(new User { Name = "Alice" });	// Ошибка
	db.SaveChanges();
	var users = db.Users.ToList();
	foreach (var user in users) 
		Console.WriteLine($"{user.Id} - {user.Name}");
}

В этом случае нам надо будет устанавливать Id:

using (ApplicationContext db = new ApplicationContext())
{
	db.Users.Add(new User { Id = 11, Name = "Tom" });
	db.Users.Add(new User { Id = 23, Name = "Alice" });
	db.SaveChanges();
	var users = db.Users.ToList();
	foreach (var user in users) 
		Console.WriteLine($"{user.Id} - {user.Name}");
}

Если мы хотим, чтобы база данных, наоборот, сама генерировала значение, то в атрибут надо передавать значение DatabaseGeneratedOption.Identity:

public class User
{
	[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
}

Но в данном случае для свойства Id это значение избыточно, так как значение генерируется по умолчанию.

Fluent API

Отключение автогенерации значения для свойства с помощью Fluent API:

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>().Property(b => b.Id).ValueGeneratedNever();
    }
}

public class User
{
    public int Id { get; set; }
    public string? Name { get; set; }
}

Значения по умолчанию

Для свойств, которые не представляют ключи и для которых не устанавливается значения, используются значения по умолчанию. Например, для свойств типа int это значение 0. С помощью метода HasDefaultValue() можно переопределить значение по умолчанию, которое будет применяться после добавления объекта в базу данных:

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>().Property(u => u.Age).HasDefaultValue(18);
    }
}

public class User
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public int Age { get; set; }
}

В этом случае, если мы не укажем значение для свойства Age, то ему будет присвоено значение 18:

using (ApplicationContext db = new ApplicationContext())
{
    User user1 = new User() { Name = "Tom"};
    Console.WriteLine($"Age: {user1.Age}"); // 0
    
	db.Users.Add(user1);
	db.SaveChanges();

    Console.WriteLine($"Age: {user1.Age}"); // 18
}

На уровне базы данных это будет проявляться в установке параметра DEFAULT:

CREATE TABLE "Users" (
	"Id"	INTEGER NOT NULL,
	"Name"	TEXT,
	"Age"	INTEGER NOT NULL DEFAULT 18,
	CONSTRAINT "PK_Users" PRIMARY KEY("Id" AUTOINCREMENT)
);

HasDefaultValueSql

Метод HasDefaultValueSql() также определяет генерацию значения по умолчанию, только само значение устанавливается на основе кода SQL, который передается в этот метод.

Например, пусть в классе пользователя будет свойство CreatedAt, которое представляет дату занесения пользователя в базу данных:

public class User
{
	public int Id { get; set; }
	public string? Name { get; set; }
	public int Age { get; set; }
	public DateTime CreatedAt { get; set; }
}

Для генерации значения этого свойства в базе данных можно вызывать специальные функции, которые применяются в той или иной СУБД. Например, в MS SQL Server/T-SQL это функция GETDATE(), в SQLite это функции DATETIME()/DATE() и т.д. Например:

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>()
            .Property(u => u.CreatedAt)
            .HasDefaultValueSql("DATETIME('now')");
    }
}

В метод HasDefaultValueSql() передается SQL-выражение, которые вызывается при добавлении объекта User в базу данных. Поскольку в данном случае используется база данных SQLite, то в качестве SQL-выражения передается вызов функции DATETIME('now') - "now" здесь указывает, что мы хотим получить текущую дату.

Вычисляемые столбцы

Столбцы могут иметь значение, которое вычисляется на основании остальных столбцов. Например, пусть модель User имеет свойства для хранения имени и фамилии:

public class User
{
	public int Id { get; set; }
	public string? Name { get;}
	public string? FirstName { get; set; }
	public string? LastName { get; set; }
	public int Age { get; set; }
}

А свойство Name должно представлять объединение свойств FirstName и LastName. И через Fluent API с помощью метода HasComputedColumnSql() можно установить в бд SQL-выражение, которое будет устанавливать значение столбца Name на основании столбцов FirstName и LastName:

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>()
                .Property(u => u.Name)
                .HasComputedColumnSql("FirstName || ' ' || LastName");
    }
}

Применение:

using (ApplicationContext db = new ApplicationContext())
{
	User user1 = new User() { FirstName = "Tom", LastName="Smith", Age=36 };
	Console.WriteLine(user1.Name); // до добавления Name имеет значение по умолчанию
	db.Users.Add(user1);
	db.SaveChanges();

	Console.WriteLine(user1.Name); // Tom Smith
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850