Проверьте ограничения для Entity Framework Core

  • Михаил
  • 12 мин. на прочтение
  • 187
  • 02 Dec 2022
  • 02 Dec 2022

Многие базы данных поддерживают то, что называется «проверочные ограничения», которые позволяют вам определять произвольную проверку для строк таблицы. Думайте об этом как о скучном уникальном ограничении столбца, но на стероидах — вы можете указать, что каждый клиент в вашей таблице должен быть старше 18 лет или иметь включенный бит «разрешение родителей». Или что имеет смысл. Просто беги с ним.

Entity Framework Core позволяет задавать проверочные ограничения в SQL — это помогает укрепить вашу модель данных и гарантировать, что никакие несогласованные или недействительные данные никогда не попадут в ваши драгоценные таблицы. Однако EF не создает для вас неявно проверочные ограничения, хотя в некоторых случаях это возможно; это связано с тем, что проверочные ограничения влияют на производительность, и они подходят не всем. Этот плагин позволяет вам выбрать некоторые ограничения — просто активируйте его, и они будут автоматически созданы для вас.

Первым шагом является установка пакета nuget EFCore.CheckConstraints . Затем выберите нужные ограничения из списка ниже.

Ограничения проверки

.NET поставляется с некоторыми встроенными атрибутами проверки , которые можно использовать для декларативного применения определенных ограничений к свойствам. Обычно они используются веб-фреймворками, такими как ASP.NET, для проверки данных, предоставленных пользователями, но мы также можем применить их в базе данных:

public class Blog
{
   public int Id { get; set; }
   [Range(1, 5)]
   public int Rating { get; set; }
   [MinLength(4)]
   public string Name { get; set; }
   [StringLength(100, MinimumLength = 1)]
   public string Required { get; set; }
   [Phone]
   public string PhoneNumber { get; set; }
   [CreditCard]
   public string CreditCard { get; set; }
   [EmailAddress]
   public string Email { get; set; }
   [Url]
   public string Address { get; set; }
   [RegularExpression("^A")]
   public string StartsWithA { get; set; }
}
public class MyContext : DbContext
{
   protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
       => optionsBuilder
           .UseNpgsql(...)
           .UseValidationCheckConstraints();
}

В результате в PostgreSQL будет создана следующая таблица:

CREATE TABLE "Blogs" (
   "Id" integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
   "Rating" integer NOT NULL,
   "Name" text NULL,
   "PhoneNumber" text NULL,
   "CreditCard" text NULL,
   "Email" text NULL,
   "Address" text NULL,
   "StartsWithA" text NULL,
   CONSTRAINT "PK_Blogs" PRIMARY KEY ("Id"),
   CONSTRAINT "CK_Blogs_Address_Url" CHECK ("Address" ~ '^(http://|https://|ftp://)'),
   CONSTRAINT "CK_Blogs_CreditCard_CreditCard" CHECK ("CreditCard" ~ '^[\d- ]*$'),
   CONSTRAINT "CK_Blogs_Email_EmailAddress" CHECK ("Email" ~ '^[^@]+@[^@]+$'),
   CONSTRAINT "CK_Blogs_Name_MinLength" CHECK (LENGTH("Name") >= 4),
   CONSTRAINT "CK_Blogs_Required_MinLength" CHECK (LENGTH("Required") >= 1),
   CONSTRAINT "CK_Blogs_PhoneNumber_Phone" CHECK ("PhoneNumber" ~ '^[\d\s+-.()]*\d[\d\s+-.()]*((ext\.|ext|x)\s*\d+)?\s*$'),
   CONSTRAINT "CK_Blogs_Rating_Range" CHECK ("Rating" >= 1 AND "Rating" <= 5),
   CONSTRAINT "CK_Blogs_StartsWithA_RegularExpression" CHECK ("StartsWithA" ~ '^A')
);

Большинство атрибутов используют регулярные выражения базы данных. Для SQL Server требуется некоторая начальная настройка — следуйте этим документам . Обратите внимание, что обработка сложных регулярных выражений требует затрат, поэтому подумайте о производительности, прежде чем включать эти ограничения для приложений с интенсивным выполнением операций записи.

Чтобы отключить генерацию ограничений регулярного выражения из соответствующих атрибутов аннотаций данных, установите UseRegexдля параметра проверки проверки ограничения значение false:


public class MyContext : DbContext
{
   protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
       => optionsBuilder
           .UseSqlServer(...)
           .UseValidationCheckConstraints(options => options.UseRegex(false));
}

Ограничения перечисления

Когда вы сопоставляете перечисление .NET с базой данных, по умолчанию это делается путем сохранения базового int перечисления в обычном столбце int старой базы данных (другая распространенная стратегия — вместо этого отображать строковое представление). Хотя перечисление .NET имеет ограниченный набор значений, которые вы определили, на стороне базы данных ничто не мешает кому-либо вставить любое значение, в том числе выходящее за пределы допустимого диапазона.

Активируйте ограничения проверки перечисления следующим образом:

public class Order
{
   public int Id { get; set; }
   public OrderStatus OrderStatus { get; set; }
}
public enum OrderStatus
{
   Active,
   Completed
}
public class MyContext : DbContext
{
   protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
       => optionsBuilder
           .UseSqlServer(...)
           .UseEnumCheckConstraints();
}

В результате будет создана следующая таблица:

CREATE TABLE [Order] (
   [Id] int NOT NULL IDENTITY,
   [OrderStatus] int NOT NULL,
   CONSTRAINT [PK_Order] PRIMARY KEY ([Id]),
   CONSTRAINT [CK_Order_OrderStatus_Enum_Constraint] CHECK ([OrderStatus] IN (0, 1))
);

Добавленное ограничение CHECK позволяет хранить в столбце только 0 и 1, обеспечивая лучшую целостность данных.

Ограничения дискриминатора

EF Core позволяет сопоставить иерархию типов .NET с одной таблицей базы данных; этот шаблон называется Table-Per-Hierarchy или TPH. При использовании этого шаблона сопоставления в вашу таблицу добавляется столбец дискриминатора , который определяет, какой тип объекта представлен конкретной строкой; при чтении результатов запроса из базы данных EF материализует различные типы .NET в иерархии на основе этого значения. Подробнее о TPH и дискриминаторах можно прочитать в документации EF .

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

public class Parent
{
   // ...
}
public class Sibling1 : Parent
{
   // ...
}
public class Sibling2 : Parent
{
   // ...
}

В результате будет создана следующая таблица:

CREATE TABLE [Parent] (
   [Id] int NOT NULL IDENTITY,
   [Discriminator] nvarchar(max) NOT NULL,
   CONSTRAINT [PK_Parent] PRIMARY KEY ([Id]),
   CONSTRAINT [CK_Parent_Discriminator_Constraint] CHECK ([Discriminator] IN (N'Parent', N'Sibling1', N'Sibling2'))
);

Я хочу их всех!

В любви с ограничениями проверки? Просто укажите UseAllCheckConstraints, чтобы настроить все.

Важная заметка

Этот плагин поддерживается сообществом: он не является официальной частью Entity Framework Core и никак не поддерживается Microsoft.