Недавно я работал над настройкой базы данных с кодом через 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 в модели в решении проекта:
Теперь, когда я запускаю миграцию для таблицы [Продукт].[Семейство] (и не только), генерируется еще один АК и дополнительный индекс. Кроме того, когда дело доходит до таблицы [Продукт].[Категория], это дополнительный альтернативный ключ, и поскольку он ссылается на таблицу [Продукт].[Семейство], он создает еще два индекса, как показано на этом снимке экрана:
Поскольку я не мог сразу сказать, почему это происходит, решил посмотреть реальный сценарий 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(), я уже пробовал это раньше. Наборы баз данных теперь выглядят следующим образом: Семейство
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.
Недавно я работал над настройкой базы данных с кодом через EF Core 7.0.4, но сталкиваюсь со странным поведением при запуске миграции -> генерируются дополнительные альтернативные ключи и индексы, а я этого не делаю. возьми. Позвольте мне рассказать вам об этом: Сначала я работал над сценарием SQL для проектирования и проектирования структуры базы данных - таблиц, ограничений и так далее. Вот макет моего SQL-скрипта: [code]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,
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,
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) [/code] Затем я реплицировал эти объекты SQL в модели в решении проекта: [code]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(); } [/code] И, наконец, соответствующие наборы баз данных: [code]modelBuilder.Entity(entity => { entity.ToTable("Family", "Product");
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"); }); [/code] Теперь, когда я запускаю миграцию для таблицы [Продукт].[Семейство] (и не только), генерируется еще один АК и дополнительный индекс. Кроме того, когда дело доходит до таблицы [Продукт].[Категория], это дополнительный альтернативный ключ, и поскольку он ссылается на таблицу [Продукт].[Семейство], он создает еще два индекса, как показано на этом снимке экрана: [img]https://i.stack.imgur.com/vKXsR.png[/img]
Поскольку я не мог сразу сказать, почему это происходит, решил посмотреть реальный сценарий SQL, который EF Core генерирует и запускает на моем сервере. И вот оно: [code]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 [/code] Теперь у меня есть ответ по поводу создаваемых дополнительных индексов — из-за УНИКАЛЬНОГО ОГРАНИЧЕНИЯ, но я действительно не понимаю, почему EF Core это делает, а также, почему он создает дополнительные альтернативные ключи? Вот как две таблицы будут/должны выглядеть, если я просто запущу сценарий SQL, созданный мной, а не тот, который использует EF Core: [img]https://i.stack.imgur.com/yAbDa.png[/img]
Может ли кто-нибудь помочь мне понять и исправить это? < h2>Изменить ответ, полученный от Ивана Пожалуйста, позвольте мне сломать ваш ответ:
[list] [*]EF Core поддерживает только FK, ссылающиеся на ключ (основной или альтернативный) в основной сущности. Это отличается от реляционной базы данных, которой требуется только уникальный ключ в указанной таблице. [/list]
Возможно, это так. сумасшедший или наивный, но не должен был подпадать под правила реляционной базы данных?
[list] [*]Альтернатива EF Core К ключу предъявляются те же требования, что и к первичному ключу: он не равен нулю, не изменяется после вставки и поддерживается уникальным ограничением/индексом. Единственное отличие от первичного ключа заключается в том, что альтернативные ключи по умолчанию подразумевают некластеризованный индекс. [/list]
True.
[list] [*]Всякий раз, когда вы используете HasPrincipalKey API, если указанное поле не является первичным или альтернативным ключом, EF создаст альтернативный ключ для вас с обычными именами ограничений/индексов по умолчанию. [/list]
Да, я это видел но это нормально, я не против самого ключа. В любом случае, давайте углубимся в предложенное вами решение Итак, согласно вашей рекомендации чтобы изменить свойство с .HasIndex() на .HasAlternateKey(), я уже пробовал это раньше. Наборы баз данных теперь выглядят следующим образом: [b]Семейство[/b] [code] modelBuilder.Entity(entity => { entity.ToTable("Family", "Product");
[/code] И... наконец, результат миграции DDL-скрипта: [code]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 [/code] Как видите, УНИКАЛЬНЫЙ КЛАСТЕРНЫЙ ИНДЕКС НА [Продукт].[Категория] в моем случае отсутствует в DDL по сравнению с тем, что вы получили. Во всяком случае, я запустил команду обновления базы данных. И выглядит это так: [img]https://i.stack.imgur.com/F7XDt.png[/img]
Выводы: [list] [*]Да, я избавился от лишнего индекса, созданного для [Продукта].[Семейства] таблица, как она создавалась ранее. [*]Я по-прежнему получаю дополнительный индекс для таблицы [Product].[Category], ориентированной на FK (как показано на скриншот). [*]Что странно, если я щелкну правой кнопкой мыши по таблице [Product].[Category] через SSMS и создам сценарий CREATE TO -> Новое окно запроса , чтобы увидеть сам скрипт, он выглядит так: [/list] [code]/****** 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 [/code] ... не существует CONSTRAINT, который бы генерировал неуникальный, некластеризованный индекс IX_Category_fFamilyCode. [list] [*]Я что-то упустил из вашей рекомендации? [/list]