Миграции создают дополнительные ключи и индексы.C#

Место общения программистов C#
Ответить
Гость
 Миграции создают дополнительные ключи и индексы.

Сообщение Гость »

Недавно я работал над настройкой базы данных с кодом через EF Core 7.0.4, но сталкиваюсь со странным поведением при запуске миграции -> генерируются дополнительные альтернативные ключи и индексы, а я этого не делаю. возьми. Позвольте мне рассказать вам об этом:
Сначала я работал над сценарием SQL для проектирования и проектирования структуры базы данных - таблиц, ограничений и так далее. Вот макет моего SQL-скрипта:

Код: Выделить всё

CREATE TABLE [Product].[Family]
(
uid uniqueidentifier
CONSTRAINT [DF_Product_Family_UID] DEFAULT (NEWSEQUENTIALID()) ROWGUIDCOL NOT NULL,
code int IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL,
description nvarchar(50) NOT NULL,
created datetime
CONSTRAINT [DF_Product_Family_Created] DEFAULT GETDATE() NOT NULL,
updated datetime
CONSTRAINT [DF_Product_Family_Updated] DEFAULT GETDATE() NOT NULL,
inactive bit
CONSTRAINT [DF_Product_Family_Inactive] DEFAULT (0) NOT NULL,

CONSTRAINT [PK_Product_Family_UID] PRIMARY KEY NONCLUSTERED (uid ASC)
)

CREATE UNIQUE CLUSTERED INDEX [IX_Product_Family_Code]
ON [Product].[Family] (code ASC)
GO

CREATE TABLE [Product].[Category]
(
uid uniqueidentifier
CONSTRAINT [DF_Product_Category_UID] DEFAULT (NEWSEQUENTIALID()) ROWGUIDCOL NOT NULL,
code int IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL,
description nvarchar(50) NOT NULL,
fFamilyCode int NOT NULL,
created datetime
CONSTRAINT [DF_Product_Category_Created] DEFAULT GETDATE() NOT NULL,
updated datetime
CONSTRAINT [DF_Product_Category_Updated] DEFAULT GETDATE() NOT NULL,
inactive bit
CONSTRAINT [DF_Product_Category_Inactive] DEFAULT (0) NOT NULL,

CONSTRAINT [PK_Product_Category_UID] PRIMARY KEY NONCLUSTERED (uid ASC)
)

CREATE UNIQUE CLUSTERED INDEX [IX_Product_Category_Code]
ON [Product].[Category] (code ASC)
GO

ALTER TABLE [Product].[Category]
ADD CONSTRAINT [FK_Category_Product_Family]
FOREIGN KEY (fFamilyCode) REFERENCES [Product].[Family] (code)
Затем я реплицировал эти объекты SQL в модели в решении проекта:

Код: Выделить всё

public partial class Family
{
[Column(Order = 0)]
public Guid Uid { get; set; }
[Column(Order = 1)]
public int Code { get; set; }
[Column(Order = 2)]
public string Description { get; set; } = null!;
[Column(Order = 3)]
public DateTime Created { get; set; }
[Column(Order = 4)]
public DateTime Updated { get; set; }
[Column(Order = 5)]
public bool Inactive { get; set; }

public virtual ICollection Categories { get; } = new List();

public virtual ICollection
 Products { get; } = new List();

public virtual ICollection SubCategories { get; } = new List();
}

public partial class Category
{
[Column(Order = 0)]
public Guid Uid { get; set; }
[Column(Order = 1)]
public int Code { get; set; }
[Column(Order = 2)]
public string Description { get; set; } = null!;
[Column(Order = 3)]
public int FFamilyCode { get; set; }
[Column(Order = 4)]
public DateTime Created { get; set; }
[Column(Order = 5)]
public DateTime Updated { get; set; }
[Column(Order = 6)]
public bool Inactive { get; set; }

public virtual Family FFamilyCodeNavigation { get; set; } = null!;
public virtual ICollection SubCategories { get; } = new List();

public virtual ICollection Products { get; } = new List();
}
И, наконец, соответствующие наборы баз данных:

Код: Выделить всё

modelBuilder.Entity(entity =>
{
entity.ToTable("Family", "Product");

entity.HasKey(e => e.Uid)
.HasName("PK_Product_Family_UID")
.IsClustered(false);

entity.HasIndex(e => e.Code, "IX_Product_Family_Code")
.IsClustered();

entity.Property(e => e.Uid)
.HasDefaultValueSql("(newsequentialid())")
.HasColumnName("uid");

entity.Property(e => e.Code)
.UseIdentityColumn(1, 1)
.HasColumnName("code");

entity.Property(e => e.Description)
.HasMaxLength(50)
.HasColumnName("description");

entity.Property(e => e.Created)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("created");

entity.Property(e => e.Updated)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("updated");

entity.Property(e =>  e.Inactive)
.HasDefaultValue(false)
.HasColumnName("inactive");
});

modelBuilder.Entity(entity =>
{
entity.ToTable("Category", "Product");

entity.HasKey(e => e.Uid)
.HasName("PK_Product_Category_UID")
.IsClustered(false);

entity.HasIndex(e => e.Code, "IX_Product_Category_Code")
.IsUnique()
.IsClustered();

entity.Property(e => e.Uid)
.HasDefaultValueSql("(newsequentialid())")
.HasColumnName("uid");

entity.Property(e => e.Code)
.UseHiLo()
.UseIdentityColumn(1, 1)
.HasColumnName("code");

entity.Property(e => e.Description)
.HasMaxLength(50)
.HasColumnName("description");

entity.Property(e => e.FFamilyCode)
.HasColumnName("fFamilyCode");

entity.Property(e => e.Created)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("created");

entity.Property(e => e.Updated)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("updated");

entity.Property(e => e.Inactive)
.HasDefaultValue(false)
.HasColumnName("inactive");

entity.HasOne(d => d.FFamilyCodeNavigation).WithMany(p => p.Categories)
.HasForeignKey(d => d.FFamilyCode)
.HasPrincipalKey(d => d.Code)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_Category_Product_Family");
});
Теперь, когда я запускаю миграцию для таблицы [Продукт].[Семейство] (и не только), генерируется еще один АК и дополнительный индекс. Кроме того, когда дело доходит до таблицы [Продукт].[Категория], это дополнительный альтернативный ключ, и поскольку он ссылается на таблицу [Продукт].[Семейство], он создает еще два индекса, как показано на этом снимке экрана:
Изображение

Поскольку я не мог сразу сказать, почему это происходит, решил посмотреть реальный сценарий SQL, который EF Core генерирует и запускает на моем сервере. И вот оно:

Код: Выделить всё

CREATE TABLE [Product].[Family]
(
[uid] uniqueidentifier NOT NULL DEFAULT ((newsequentialid())),
[code] int NOT NULL IDENTITY,
[description] nvarchar(50) NOT NULL,
[created] datetime NOT NULL DEFAULT ((getdate())),
[updated] datetime NOT NULL DEFAULT ((getdate())),
[inactive] bit NOT NULL DEFAULT CAST(0 AS bit),
CONSTRAINT [PK_Product_Family_UID] PRIMARY KEY NONCLUSTERED ([uid]),
CONSTRAINT [AK_Family_code] UNIQUE ([code])
);
GO

CREATE TABLE [Product].[Category]
(
[uid] uniqueidentifier NOT NULL DEFAULT ((newsequentialid())),
[code] int NOT NULL IDENTITY,
[description] nvarchar(50) NOT NULL,
[fFamilyCode] int NOT NULL,
[created] datetime NOT NULL DEFAULT ((getdate())),
[updated] datetime NOT NULL DEFAULT ((getdate())),
[inactive] bit NOT NULL DEFAULT CAST(0 AS bit),
CONSTRAINT [PK_Product_Category_UID] PRIMARY KEY NONCLUSTERED ([uid]),
CONSTRAINT [AK_Category_code] UNIQUE ([code]),
CONSTRAINT [FK_Category_Product_Family] FOREIGN KEY ([fFamilyCode]) REFERENCES [Product].[Family] ([code]) ON DELETE NO ACTION
);
GO

CREATE INDEX [IX_Category_fFamilyCode] ON [Product].[Category] ([fFamilyCode]);
GO

CREATE UNIQUE CLUSTERED INDEX [IX_Product_Category_Code] ON [Product].[Category] ([code]);
GO
Теперь у меня есть ответ по поводу создаваемых дополнительных индексов — из-за УНИКАЛЬНОГО ОГРАНИЧЕНИЯ, но я действительно не понимаю, почему EF Core это делает, а также, почему он создает дополнительные альтернативные ключи? Вот как две таблицы будут/должны выглядеть, если я просто запущу сценарий SQL, созданный мной, а не тот, который использует EF Core:
Изображение

Может ли кто-нибудь помочь мне понять и исправить это?
< h2>Изменить ответ, полученный от Ивана
Пожалуйста, позвольте мне сломать ваш ответ:
  • EF Core поддерживает только FK, ссылающиеся на ключ (основной или альтернативный) в основной сущности. Это отличается от реляционной базы данных, которой
    требуется только уникальный ключ в указанной таблице.
Возможно, это так. сумасшедший или наивный, но не должен был подпадать под правила реляционной базы данных?
  • Альтернатива EF Core К ключу предъявляются те же требования, что и к первичному ключу: он не равен нулю, не изменяется после вставки и поддерживается уникальным
    ограничением/индексом. Единственное отличие от первичного ключа заключается в том, что
    альтернативные ключи по умолчанию подразумевают некластеризованный индекс.
True.
  • Всякий раз, когда вы используете HasPrincipalKey API, если указанное поле не является первичным или альтернативным ключом, EF создаст альтернативный ключ для вас
    с обычными именами ограничений/индексов по умолчанию.
Да, я это видел но это нормально, я не против самого ключа.
В любом случае, давайте углубимся в предложенное вами решение
Итак, согласно вашей рекомендации чтобы изменить свойство с .HasIndex() на .HasAlternateKey(), я уже пробовал это раньше. Наборы баз данных теперь выглядят следующим образом:
Семейство

Код: Выделить всё

        modelBuilder.Entity(entity =>
{
entity.ToTable("Family", "Product");

entity.HasKey(e => e.Uid)
.HasName("PK_Product_Family_UID")
.IsClustered(false);

entity.HasAlternateKey(e => e.Code)
.HasName("IX_Product_Family_Code")
.IsClustered();

entity.Property(e => e.Uid)
.HasDefaultValueSql("(newsequentialid())")
.HasColumnName("uid");

entity.Property(e => e.Code)
.UseIdentityColumn(1, 1)
.HasColumnName("code");

entity.Property(e => e.Description)
.HasMaxLength(50)
.HasColumnName("description");

entity.Property(e => e.Created)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("created");

entity.Property(e => e.Updated)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("updated");

entity.Property(e => e.Inactive)
.HasDefaultValue(false)
.HasColumnName("inactive");
});
Категория

Код: Выделить всё

        modelBuilder.Entity(entity =>
{
entity.ToTable("Category", "Product");

entity.HasKey(e => e.Uid)
.HasName("PK_Product_Category_UID")
.IsClustered(false);

entity.HasAlternateKey(e => e.Code)
.HasName("IX_Product_Category_Code")
.IsClustered();

entity.Property(e => e.Uid)
.HasDefaultValueSql("(newsequentialid())")
.HasColumnName("uid");

entity.Property(e => e.Code)
.UseIdentityColumn(1, 1)
.HasColumnName("code");

entity.Property(e => e.Description)
.HasMaxLength(50)
.HasColumnName("description");

entity.Property(e => e.FFamilyCode)
.HasColumnName("fFamilyCode");

entity.Property(e => e.Created)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("created");

entity.Property(e => e.Updated)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("updated");

entity.Property(e => e.Inactive)
.HasDefaultValue(false)
.HasColumnName("inactive");

entity.HasOne(d => d.FFamilyCodeNavigation).WithMany(p => p.Categories)
.HasForeignKey(d => d.FFamilyCode)
.HasPrincipalKey(d => d.Code)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_Category_Product_Family");
});
Сгенерированная миграция

Код: Выделить всё

            migrationBuilder.CreateTable(
name: "Family",
schema: "Product",
columns: table =>  new
{
uid = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "(newsequentialid())"),
code = table.Column(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
description = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false),
created = table.Column(type: "datetime", nullable: false, defaultValueSql: "(getdate())"),
updated = table.Column(type: "datetime", nullable: false, defaultValueSql: "(getdate())"),
inactive = table.Column(type: "bit", nullable: false, defaultValue: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Product_Family_UID", x => x.uid)
.Annotation("SqlServer:Clustered", false);
table.UniqueConstraint("IX_Product_Family_Code", x => x.code)
.Annotation("SqlServer:Clustered", true);
});

migrationBuilder.CreateTable(
name: "Category",
schema: "Product",
columns: table => new
{
uid = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "(newsequentialid())"),
code = table.Column(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
description = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false),
fFamilyCode = table.Column(type: "int", nullable: false),
created = table.Column(type: "datetime", nullable: false, defaultValueSql: "(getdate())"),
updated = table.Column(type: "datetime", nullable: false, defaultValueSql: "(getdate())"),
inactive = table.Column(type: "bit", nullable: false, defaultValue: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Product_Category_UID", x => x.uid)
.Annotation("SqlServer:Clustered", false);
table.UniqueConstraint("IX_Product_Category_Code", x => x.code)
.Annotation("SqlServer:Clustered", true);
table.ForeignKey(
name: "FK_Category_Product_Family",
column: x => x.fFamilyCode,
principalSchema: "Product",
principalTable: "Family",
principalColumn: "code",
onDelete: ReferentialAction.Restrict);
});

migrationBuilder.CreateIndex(
name: "IX_Category_fFamilyCode",
schema: "Product",
table: "Category",
column: "fFamilyCode");

И... наконец, результат миграции DDL-скрипта:

Код: Выделить всё

CREATE TABLE [Product].[Family] (
[uid] uniqueidentifier NOT NULL DEFAULT ((newsequentialid())),
[code] int NOT NULL IDENTITY,
[description] nvarchar(50) NOT NULL,
[created] datetime NOT NULL DEFAULT ((getdate())),
[updated] datetime NOT NULL DEFAULT ((getdate())),
[inactive] bit NOT NULL DEFAULT CAST(0 AS bit),
CONSTRAINT [PK_Product_Family_UID] PRIMARY KEY NONCLUSTERED ([uid]),
CONSTRAINT [IX_Product_Family_Code] UNIQUE CLUSTERED ([code])
);
GO

CREATE TABLE [Product].[Category] (
[uid] uniqueidentifier NOT NULL DEFAULT ((newsequentialid())),
[code] int NOT NULL IDENTITY,
[description] nvarchar(50) NOT NULL,
[fFamilyCode] int NOT NULL,
[created] datetime NOT NULL DEFAULT ((getdate())),
[updated] datetime NOT NULL DEFAULT ((getdate())),
[inactive] bit NOT NULL DEFAULT CAST(0 AS bit),
CONSTRAINT [PK_Product_Category_UID] PRIMARY KEY NONCLUSTERED ([uid]),
CONSTRAINT [IX_Product_Category_Code] UNIQUE CLUSTERED ([code]),
CONSTRAINT [FK_Category_Product_Family] FOREIGN KEY ([fFamilyCode]) REFERENCES [Product].[Family] ([code]) ON DELETE NO ACTION
);
GO

CREATE INDEX [IX_Category_fFamilyCode] ON [Product].[Category] ([fFamilyCode]);
GO
Как видите, УНИКАЛЬНЫЙ КЛАСТЕРНЫЙ ИНДЕКС НА [Продукт].[Категория] в моем случае отсутствует в DDL по сравнению с тем, что вы получили.
Во всяком случае, я запустил команду обновления базы данных. И выглядит это так:
Изображение

Выводы:
  • Да, я избавился от лишнего индекса, созданного для [Продукта].[Семейства] таблица, как она создавалась ранее.
  • Я по-прежнему получаю дополнительный индекс для таблицы [Product].[Category], ориентированной на FK (как показано на скриншот).
  • Что странно, если я щелкну правой кнопкой мыши по таблице [Product].[Category] через SSMS и создам сценарий CREATE TO -> Новое окно запроса , чтобы увидеть сам скрипт, он выглядит так:

Код: Выделить всё

/****** Object:  Table [Product].[Category]    Script Date: 14-03-2024 11:31:02 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [Product].[Category](
[uid] [uniqueidentifier] NOT NULL,
[code] [int] IDENTITY(1,1) NOT NULL,
[description] [nvarchar](50) NOT NULL,
[fFamilyCode] [int] NOT NULL,
[created] [datetime] NOT NULL,
[updated] [datetime] NOT NULL,
[inactive] [bit] NOT NULL,
CONSTRAINT [PK_Product_Category_UID] PRIMARY KEY NONCLUSTERED
(
[uid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
CONSTRAINT [IX_Product_Category_Code] UNIQUE CLUSTERED
(
[code] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [Product].[Category] ADD  DEFAULT (newsequentialid()) FOR [uid]
GO

ALTER TABLE [Product].[Category] ADD  DEFAULT (getdate()) FOR [created]
GO

ALTER TABLE [Product].[Category] ADD  DEFAULT (getdate()) FOR [updated]
GO

ALTER TABLE [Product].[Category] ADD  DEFAULT (CONVERT([bit],(0))) FOR [inactive]
GO

ALTER TABLE [Product].[Category]  WITH CHECK ADD  CONSTRAINT [FK_Category_Product_Family] FOREIGN KEY([fFamilyCode])
REFERENCES [Product].[Family] ([code])
GO

ALTER TABLE [Product].[Category] CHECK CONSTRAINT [FK_Category_Product_Family]
GO
... не существует CONSTRAINT, который бы генерировал неуникальный, некластеризованный индекс IX_Category_fFamilyCode.
  • Я что-то упустил из вашей рекомендации?


Источник: https://stackoverflow.com/questions/781 ... nd-indexes
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»