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

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

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

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

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

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

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

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

public class User
{
    public int Id { get; set; }
    public string Name { 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=helloappdb;Trusted_Connection=True;");
    }
}

То после добавления в базу данных мы сможем получить сгенерированный 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 = 3
}

Атрибут 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

Отключение автогенерации значения для свойства:

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; }
	public ApplicationContext()
	{
		Database.EnsureDeleted();
		Database.EnsureCreated();
	}
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().Property(b => b.Id).ValueGeneratedNever();
    }
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
    }
}

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

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

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

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; }
	public ApplicationContext()
	{
		Database.EnsureDeleted();
		Database.EnsureCreated();
	}
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().Property(u => u.Age).HasDefaultValue(18);
    }
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
    }
}

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 [dbo].[Users] (
    [Id]   INT            IDENTITY (1, 1) NOT NULL,
    [Age]  INT            DEFAULT (18) NOT NULL,
    [Name] NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC)
);

HasDefaultValueSql

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

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

using System;

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

Для генерации значения этого свойства в базе данных можно вызывать функцию GETDATE():

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=helloappdb;Trusted_Connection=True;");
	}

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<User>()
			.Property(u => u.CreatedAt)
			.HasDefaultValueSql("GETDATE()");
	}
}

В метод HasDefaultValueSql() передается SQL-выражение, которые вызывается при добавлении объекта User в базу данных.

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

Столбцы могут иметь значение, которое вычисляется на основании остальных столбцов. Например, пусть модель 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:

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=helloappdb;Trusted_Connection=True;");
	}

	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