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