Сначала я работал над сценарием 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)
Код: Выделить всё
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

Может ли кто-нибудь помочь мне понять и исправить это?
< h2>Изменить ответ, полученный от Ивана
Пожалуйста, позвольте мне сломать ваш ответ:
- EF Core поддерживает только FK, ссылающиеся на ключ (основной или альтернативный) в основной сущности. Это отличается от реляционной базы данных, которой
требуется только уникальный ключ в указанной таблице.
- Альтернатива EF Core К ключу предъявляются те же требования, что и к первичному ключу: он не равен нулю, не изменяется после вставки и поддерживается уникальным
ограничением/индексом. Единственное отличие от первичного ключа заключается в том, что
альтернативные ключи по умолчанию подразумевают некластеризованный индекс.
- Всякий раз, когда вы используете 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");
Код: Выделить всё
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
Во всяком случае, я запустил команду обновления базы данных. И выглядит это так:

Выводы:
- Да, я избавился от лишнего индекса, созданного для [Продукта].[Семейства] таблица, как она создавалась ранее.
- Я по-прежнему получаю дополнительный индекс для таблицы [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
- Я что-то упустил из вашей рекомендации?
Источник: https://stackoverflow.com/questions/781 ... nd-indexes