├── dist
└── .gitkeep
├── .gitignore
├── .assets
├── nupkg-icon.docx
└── nupkg-icon.png
├── EFCore.IndexAttribute.Test
├── TestProject
│ ├── .config
│ │ └── dotnet-tools.json
│ ├── Program.cs
│ ├── Models
│ │ └── Person.cs
│ ├── Data
│ │ ├── MyDbContextFactory.cs
│ │ └── MyDbContext.cs
│ └── TestProject.csproj
├── Models
│ ├── Enums
│ │ └── SNSProviders.cs
│ ├── PhoneNumber.cs
│ ├── Lines.cs
│ ├── Address.cs
│ ├── Person.cs
│ ├── MyDbContextForSqlServer.cs
│ ├── MyDbContextWithNoConfiguration.cs
│ ├── MyDbContext.cs
│ ├── SNSAccount.cs
│ └── MyDbContextBase.cs
├── MigrationTest.cs
├── IndexAttributeTest_on_SQLite.cs
├── EFCore.IndexAttribute.Test.csproj
└── IndexAttributeTest.cs
├── EFCore.IndexAttribute.Attribute
├── AssemblyInfo.cs
├── Internals
│ └── INameAndOrder.cs
├── RELEASE-NOTES.txt
├── V5
│ ├── PrimaryKeyAttribute.cs
│ └── IndexColumnAttribute.cs
├── PrimaryKeyAttribute.cs
├── EFCore.IndexAttribute.Attribute.csproj
└── IndexAttribute.cs
├── EFCore.IndexAttribute
├── AssemblyInfo.cs
├── IndexAttribute.cs
├── AttributedIndexBuilderOptions.cs
├── RELEASE-NOTES.txt
├── EFCore.IndexAttribute.csproj
└── AttributedIndexBuilderExtension.cs
├── nuget.config
├── EFCore.IndexAttribute.slnx
├── EFCore.IndexAttribute.SqlServer
├── RELEASE-NOTES.txt
├── AttributedIndexBuilderSqlServerExtension.cs
└── EFCore.IndexAttribute.SqlServer.csproj
├── .github
└── workflows
│ └── unit-tests.yml
├── README-Appendix-D.md
├── LICENSE
├── README-Appendix-E.md
├── .editorconfig
└── README.md
/dist/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | .vs/
4 | /dist/
5 | *.user
6 | *.suo
7 |
8 |
--------------------------------------------------------------------------------
/.assets/nupkg-icon.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsakamoto/EntityFrameworkCore.IndexAttribute/HEAD/.assets/nupkg-icon.docx
--------------------------------------------------------------------------------
/.assets/nupkg-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsakamoto/EntityFrameworkCore.IndexAttribute/HEAD/.assets/nupkg-icon.png
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/TestProject/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {}
5 | }
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/TestProject/Program.cs:
--------------------------------------------------------------------------------
1 | namespace TestProject;
2 |
3 | class Program
4 | {
5 | static void Main(string[] args)
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Attribute/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("Toolbelt.EntityFrameworkCore.IndexAttribute")]
4 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer")]
4 |
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/Enums/SNSProviders.cs:
--------------------------------------------------------------------------------
1 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
2 |
3 | public enum SNSProviders
4 | {
5 | Twitter,
6 | Facebook,
7 | LinkedIn,
8 | Instagram,
9 | LINE
10 | }
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Attribute/Internals/INameAndOrder.cs:
--------------------------------------------------------------------------------
1 | namespace Toolbelt.ComponentModel.DataAnnotations.Schema.Internals
2 | {
3 | internal interface INameAndOrder
4 | {
5 | string Name { get; }
6 |
7 | int Order { get; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute/IndexAttribute.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using Toolbelt.ComponentModel.DataAnnotations.Schema;
3 |
4 | #pragma warning disable CS0618 // Type or member is obsolete
5 | [assembly: TypeForwardedTo(typeof(IndexAttribute))]
6 | #pragma warning restore CS0618 // Type or member is obsolete
7 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/PhoneNumber.cs:
--------------------------------------------------------------------------------
1 | using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;
2 |
3 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
4 |
5 | public class PhoneNumber
6 | {
7 | [IndexColumn]
8 | public int CountryCode { get; set; }
9 |
10 | public string CityNumber { get; set; } = "";
11 |
12 | public string Number { get; set; } = "";
13 | }
14 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/Lines.cs:
--------------------------------------------------------------------------------
1 | using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;
2 |
3 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
4 |
5 | public class Lines
6 | {
7 | [IndexColumn("IX_Lines", 1)]
8 | public string Line1 { get; set; } = "";
9 |
10 | [IndexColumn("IX_Lines", 2)]
11 | public string Line2 { get; set; } = "";
12 |
13 | public string Line3 { get; set; } = "";
14 | }
15 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/Address.cs:
--------------------------------------------------------------------------------
1 | using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;
2 |
3 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
4 |
5 | public class Address
6 | {
7 | public Lines Lines { get; set; } = new Lines();
8 |
9 | public string ZipPostCode { get; set; } = "";
10 |
11 | public string TownCity { get; set; } = "";
12 |
13 | [IndexColumn("IX_Country", Includes = new[] { nameof(ZipPostCode), nameof(TownCity) })]
14 | public string Country { get; set; } = "";
15 | }
16 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Attribute/RELEASE-NOTES.txt:
--------------------------------------------------------------------------------
1 | v.5.0.1.3
2 | - Supports EntityFramework Core 10.0
3 | - Update README.md
4 |
5 | v.5.0.1.2
6 | - Supports EntityFramework Core 9.0
7 | - Update README.md
8 |
9 | v.5.0.0
10 | - Add [IndexColumn] attribute. And deprecate [Index] attribute in this package to avoid conflict official [Index] attribute of EFCore v5.
11 |
12 | v.1.2.0
13 | - Add "Includes" index property
14 |
15 | v.1.1.0
16 | - Add "IsClustered" index property
17 | - Add "PrimaryKey" attribute class
18 |
19 | v.1.0.0
20 | - 1st release.
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/TestProject/Models/Person.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;
3 |
4 | namespace TestProject.Models;
5 |
6 | public class Person
7 | {
8 | public int Id { get; set; }
9 |
10 | [IndexColumn(IsUnique = true), Required, StringLength(10)]
11 | public string Name { get; set; }
12 |
13 | [IndexColumn(IsUnique = true, Includes = new[] { nameof(Weight) })]
14 | public int Height { get; set; }
15 |
16 | public int Weight { get; set; }
17 | }
18 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/Person.cs:
--------------------------------------------------------------------------------
1 | using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;
2 |
3 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
4 |
5 | public class Person
6 | {
7 | public int Id { get; set; }
8 |
9 | [IndexColumn(IsUnique = true)]
10 | public string Name { get; set; } = "";
11 |
12 | public int Age { get; set; }
13 |
14 | public Address Address { get; set; } = new Address();
15 |
16 | public PhoneNumber PhoneNumber { get; set; } = new PhoneNumber();
17 |
18 | public PhoneNumber FaxNumber { get; set; } = new PhoneNumber();
19 | }
20 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/MyDbContextForSqlServer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Toolbelt.ComponentModel.DataAnnotations;
3 |
4 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
5 |
6 | public class MyDbContextForSqlServer : MyDbContextBase
7 | {
8 | public MyDbContextForSqlServer(DbContextOptions options) : base(options)
9 | {
10 | }
11 |
12 | protected override void OnModelCreating(ModelBuilder modelBuilder)
13 | {
14 | base.OnModelCreating(modelBuilder);
15 | modelBuilder.BuildIndexesFromAnnotationsForSqlServer();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/MyDbContextWithNoConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Toolbelt.ComponentModel.DataAnnotations;
3 |
4 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
5 |
6 | public class MyDbContextWithNoConfiguration : MyDbContextBase
7 | {
8 | public MyDbContextWithNoConfiguration(DbContextOptions options) : base(options)
9 | {
10 | }
11 |
12 | protected override void OnModelCreating(ModelBuilder modelBuilder)
13 | {
14 | base.OnModelCreating(modelBuilder);
15 | modelBuilder.BuildIndexesFromAnnotations(/* WITH NO CONFIGURATION */);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/TestProject/Data/MyDbContextFactory.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Design;
3 |
4 | namespace TestProject.Data;
5 |
6 | public class MyDbContextFactory : IDesignTimeDbContextFactory
7 | {
8 | public MyDbContext CreateDbContext(string[] args)
9 | {
10 | var options = new DbContextOptionsBuilder()
11 | //.UseSqlite("Data Source=:memory:")
12 | .UseSqlServer(@"Server=(LocalDB)\MSSQLLocalDB;Initial Catalog=test.mdf;")
13 | .Options;
14 | var dbContext = new MyDbContext(options);
15 | return dbContext;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/TestProject/Data/MyDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using TestProject.Models;
3 | using Toolbelt.ComponentModel.DataAnnotations;
4 |
5 | namespace TestProject.Data;
6 |
7 | public class MyDbContext : DbContext
8 | {
9 | public DbSet People { get; set; }
10 |
11 | public MyDbContext(DbContextOptions options) : base(options)
12 | {
13 | }
14 |
15 | protected override void OnModelCreating(ModelBuilder modelBuilder)
16 | {
17 | base.OnModelCreating(modelBuilder);
18 | // modelBuilder.BuildIndexesFromAnnotations();
19 | modelBuilder.BuildIndexesFromAnnotationsForSqlServer();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.slnx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/MyDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Toolbelt.ComponentModel.DataAnnotations;
3 |
4 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
5 |
6 | public class MyDbContext : MyDbContextBase
7 | {
8 | public MyDbContext(DbContextOptions options) : base(options)
9 | {
10 | }
11 |
12 | protected override void OnModelCreating(ModelBuilder modelBuilder)
13 | {
14 | base.OnModelCreating(modelBuilder);
15 | modelBuilder.BuildIndexesFromAnnotations(options =>
16 | {
17 | options.SuppressNotSupportedException.IsClustered = true;
18 | options.SuppressNotSupportedException.Includes = true;
19 | });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/SNSAccount.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations.Schema;
2 | using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;
3 |
4 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
5 |
6 | public class SNSAccount
7 | {
8 | [PrimaryKey(IsClustered = false)]
9 | public int Id { get; set; }
10 |
11 | public int PersonId { get; set; }
12 |
13 | [ForeignKey(nameof(PersonId))]
14 | public virtual Person Person { get; set; } = new Person();
15 |
16 | [IndexColumn]
17 | [IndexColumn("Ix_Provider_and_Account", 1, IsUnique = true, IsClustered = true)]
18 | public SNSProviders Provider { get; set; }
19 |
20 | [IndexColumn("Ix_Provider_and_Account", 2, IsUnique = true, IsClustered = true)]
21 | public string AccountName { get; set; } = "";
22 | }
23 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.SqlServer/RELEASE-NOTES.txt:
--------------------------------------------------------------------------------
1 | v.5.0.1.3
2 | - Supports EntityFramework Core 10.0
3 | - Update README.md
4 |
5 | v.5.0.1.2
6 | - Supports EntityFramework Core 9.0
7 | - Update README.md
8 |
9 | v.5.0.1.1
10 | - Supports EntityFramework Core 7.0, 8.0
11 | - Update README.md
12 |
13 | v.5.0.1
14 | - Supports EntityFramework Core 6.0
15 |
16 | v.5.0.0
17 | - Add [IndexColumn] attribute. And deprecate [Index] attribute in this package to avoid conflict official [Index] attribute of EFCore v5.
18 |
19 | v.3.2.1
20 | - Fix: Migration code which was generated by "Add-Migration" (or "dotnet ef migrations add") could not be compiled.
21 |
22 | v.3.2.0
23 | - Add support for "Includes" property of [Index] attribute
24 |
25 | v.3.1.0
26 | - Supports EntityFramework Core v.3.1.1
27 | - Revert back to .NET Standard 2.0
28 |
29 | v.3.0.0
30 | - BREAKING CHANGE: supports EntityFramework Core v.3.0
31 |
32 | v.1.0.0
33 | - 1st release.
--------------------------------------------------------------------------------
/.github/workflows/unit-tests.yml:
--------------------------------------------------------------------------------
1 | name: unit tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'trial/**'
7 | - master
8 |
9 | env:
10 | MSSQL_SERVER: localhost,1433
11 | MSSQL_USER: sa
12 | MSSQL_PWD: Passw0rd
13 |
14 | jobs:
15 | deploy:
16 | runs-on: ubuntu-latest
17 | steps:
18 | # Checkout the code
19 | - uses: actions/checkout@v5
20 |
21 | # Install .NET SDK
22 | - name: Setup .NET SDK
23 | uses: actions/setup-dotnet@v5
24 | with:
25 | dotnet-version: |
26 | 8.0.x
27 | 9.0.x
28 | 10.0.x
29 |
30 | # Install SQL Server
31 | - name: Setup SQL Server
32 | run: docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=$MSSQL_PWD" -p 1433:1433 --name sql1 --hostname sql1 -d mcr.microsoft.com/mssql/server:2025-latest
33 |
34 | # Perform unit tests
35 | - name: Perform unit tests
36 | run: dotnet test EFCore.IndexAttribute.Test -l "console;verbosity=normal" --nologo
37 |
--------------------------------------------------------------------------------
/README-Appendix-D.md:
--------------------------------------------------------------------------------
1 | # IndexColumnAttribute for EntityFramework Core
2 |
3 | ## Appendix D - Upgrade an existing project
4 |
5 | To upgrade an existing project that uses ver.3 or before to use ver.5 or later of this package:
6 | 1. Please confirm that the version of this package you use is ver.5 or later.
7 |
8 | ```
9 | PM> Update-Package Toolbelt.EntityFrameworkCore.IndexAttribute
10 | ```
11 |
12 | 2. Remove `using Toolbelt.ComponentModel.DataAnnotations.Schema;`, and insert `using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;` instead.
13 |
14 | ```csharp
15 | ...
16 | // 👇 Remove this line...
17 | // using Toolbelt.ComponentModel.DataAnnotations.Schema;
18 |
19 | // 👇 Insert this line, instead.
20 | using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;
21 | ...
22 | ```
23 |
24 | 3. Replace `[Index]` attribute to `[IndexColumn]` attribute.
25 |
26 | ```csharp
27 | ...
28 | public class Foo {
29 | ...
30 | // 👇 Replace [Index] to [IndexColumn]
31 | [IndexColumn]
32 | public int Bar { get; set; }
33 | ...
34 | ```
35 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute/AttributedIndexBuilderOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Toolbelt.ComponentModel.DataAnnotations
2 | {
3 | ///
4 | /// Options for building Indexes from annotations.
5 | ///
6 | public class AttributedIndexBuilderOptions
7 | {
8 | ///
9 | /// Flags that indicates unsupported features.
10 | ///
11 | public class NotSupportedFeaturesFlag
12 | {
13 | ///
14 | /// "IsClustered" porperty of [Index] attribute.
15 | ///
16 | public bool IsClustered { get; set; }
17 |
18 | ///
19 | /// "Includes" porperty of [Index] attribute.
20 | ///
21 | public bool Includes { get; set; }
22 | }
23 |
24 | ///
25 | /// Sets or gets the flags to suppress "NotSupportedException" for each unsupported feature.
26 | ///
27 | public NotSupportedFeaturesFlag SuppressNotSupportedException { get; } = new NotSupportedFeaturesFlag();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute/RELEASE-NOTES.txt:
--------------------------------------------------------------------------------
1 | v.5.0.1.3
2 | - Supports EntityFramework Core 10.0
3 | - Update README.md
4 |
5 | v.5.0.1.2
6 | - Supports EntityFramework Core 9.0
7 | - Update README.md
8 |
9 | v.5.0.1.1
10 | - Supports EntityFramework Core 7.0, 8.0
11 | - Update README.md
12 |
13 | v.5.0.1
14 | - Supports EntityFramework Core 6.0
15 |
16 | v.5.0.0
17 | - Add [IndexColumn] attribute. And deprecate [Index] attribute in this package to avoid conflict official [Index] attribute of EFCore v5.
18 |
19 | v.3.2.0
20 | - Add support for "Includes" property of [Index] attribute
21 |
22 | v.3.1.0
23 | - Supports EntityFramework Core v.3.1.0
24 | - Revert back to .NET Standard 2.0
25 |
26 | v.3.0.0
27 | - BREAKING CHANGE: supports EntityFramework Core v.3.0
28 |
29 | v.2.1.0
30 | - Add support [PrimaryKey] attribute.
31 |
32 | v.2.0.1
33 | - Fix: Doesn't work with owned types on EF Core v.2.1, v.2.2.
34 |
35 | v.2.0.0
36 | - Splitted [Index] attribute class to another package for detach the dependencies of EF Core from [Index] attribute.
37 |
38 | v.1.0.2
39 | - Added support for Owned Entity Types.
40 |
41 | v.1.0.0
42 | - 1st release.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 J.Sakamoto
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Attribute/V5/PrimaryKeyAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace Toolbelt.ComponentModel.DataAnnotations.Schema.V5
2 | {
3 | #pragma warning disable CS0618 // Type or member is obsolete
4 | public class PrimaryKeyAttribute : Toolbelt.ComponentModel.DataAnnotations.Schema.PrimaryKeyAttribute
5 | #pragma warning restore CS0618 // Type or member is obsolete
6 | {
7 | ///
8 | /// Initializes a new PrimaryKeyAttribute instance for a primary key that will be named by convention and has no column order specified.
9 | ///
10 | public PrimaryKeyAttribute() : base("", -1)
11 | {
12 | }
13 |
14 | ///
15 | /// Initializes a new PrimaryKeyAttribute instance for a primary key with the given name and has no column order specified.
16 | ///
17 | /// The primary key name.
18 | public PrimaryKeyAttribute(string name) : base(name, -1)
19 | {
20 | }
21 |
22 | ///
23 | /// Initializes a new PrimaryKeyAttribute instance for a primary key with the given name and column order.
24 | ///
25 | /// The primary key name.
26 | /// A number which will be used to determine column ordering for multi-column primary keys.
27 | public PrimaryKeyAttribute(string name, int order) : base(name, order)
28 | {
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.SqlServer/AttributedIndexBuilderSqlServerExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Microsoft.EntityFrameworkCore;
4 |
5 | namespace Toolbelt.ComponentModel.DataAnnotations
6 | {
7 | public static class AttributedIndexBuilderSqlServerExtension
8 | {
9 | public static void BuildIndexesFromAnnotationsForSqlServer(this ModelBuilder modelBuilder)
10 | {
11 | modelBuilder.BuildIndexesFromAnnotationsForSqlServer(configure: null);
12 | }
13 |
14 | public static void BuildIndexesFromAnnotationsForSqlServer(this ModelBuilder modelBuilder, Action configure)
15 | {
16 | modelBuilder.BuildIndexesFromAnnotations(
17 | postProcessForIndex: (builder, arg) =>
18 | {
19 | builder.IsClustered(arg.IsClustered);
20 | if (arg.Includes != null && arg.Includes.Any())
21 | builder.IncludeProperties(arg.Includes);
22 | },
23 | postProcessForPrimaryKey: (builder, arg) =>
24 | {
25 | builder.IsClustered(arg.IsClustered);
26 | },
27 | configure: options =>
28 | {
29 | configure?.Invoke(options);
30 | options.SuppressNotSupportedException.IsClustered = true;
31 | options.SuppressNotSupportedException.Includes = true;
32 | });
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/Models/MyDbContextBase.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 |
3 | namespace EntityFrameworkCore.IndexAttributeTest.Models;
4 |
5 | public class MyDbContextBase : DbContext
6 | {
7 | public DbSet People { get; set; }
8 |
9 | public DbSet SNSAccounts { get; set; }
10 |
11 | #nullable disable
12 | public MyDbContextBase(DbContextOptions options) : base(options)
13 | {
14 | }
15 | #nullable restore
16 |
17 | protected override void OnModelCreating(ModelBuilder modelBuilder)
18 | {
19 | base.OnModelCreating(modelBuilder);
20 |
21 | var person = modelBuilder.Entity();
22 | person.OwnsOne(p => p.Address, address =>
23 | {
24 | address.OwnsOne(a => a.Lines,
25 | lines =>
26 | {
27 | lines.Property(l => l.Line1).HasColumnName("Line1");
28 | lines.Property(l => l.Line2).HasColumnName("Line2");
29 | lines.Property(l => l.Line3).HasColumnName("Line3");
30 | });
31 | });
32 | person.OwnsOne(p => p.PhoneNumber);
33 | person.OwnsOne(p => p.FaxNumber);
34 |
35 | //modelBuilder.Entity().OwnsOne(typeof(Address), "Address", address =>
36 | //{
37 | // address.OwnsOne(typeof(Lines), "Lines", l => l.HasIndex("Line1", "Line2"));
38 | //});
39 | //modelBuilder.Entity().HasIndex("Name").IsUnique();
40 | //modelBuilder.Entity().HasIndex("Provider");
41 | //modelBuilder.Entity().HasIndex("Provider", "AccountName").HasName("Ix_Provider_and_Account").IsUnique();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Attribute/V5/IndexColumnAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Toolbelt.ComponentModel.DataAnnotations.Schema.V5
4 | {
5 | ///
6 | /// Represents an attribute that is placed on a property to indicate that the database column to which the property is mapped has an index.
7 | ///
8 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
9 | #pragma warning disable CS0618 // Type or member is obsolete
10 | public class IndexColumnAttribute : IndexAttribute
11 | #pragma warning restore CS0618 // Type or member is obsolete
12 | {
13 | ///
14 | /// Initializes a new IndexAttribute instance for an index that will be named by convention and has no column order, uniqueness specified.
15 | ///
16 | public IndexColumnAttribute()
17 | {
18 | }
19 |
20 | ///
21 | /// Initializes a new IndexAttribute instance for an index with the given name and has no column order, uniqueness specified.
22 | ///
23 | /// The index name.
24 | public IndexColumnAttribute(string name) : base(name, -1)
25 | {
26 | }
27 |
28 | ///
29 | /// Initializes a new IndexAttribute instance for an index with the given name and column order, but with no uniqueness specified.
30 | ///
31 | /// The index name.
32 | /// A number which will be used to determine column ordering for multi-column indexes.
33 | public IndexColumnAttribute(string name, int order) : base(name, order)
34 | {
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/README-Appendix-E.md:
--------------------------------------------------------------------------------
1 | # IndexColumnAttribute for EntityFramework Core
2 |
3 | ## Appendix E - If you run into a compile error CS0104...
4 |
5 | If you run into a compile error CS0104 "'Index' is an ambiguous reference between 'Toolbelt.ComponentModel.DataAnnotations.Schema.IndexAttribute' and 'Microsoft.EntityFrameworkCore.IndexAttribute'" in your project that has to use the old version of this package (ver.3.x or before),
6 |
7 | ```csharp
8 | using Microsoft.EntityFrameworkCore;
9 | using Toolbelt.ComponentModel.DataAnnotations.Schema;
10 |
11 | public class Foo {
12 | ...
13 | [Index] // 👈 CS0104 "'Index' is an ambiguous reference...
14 | public int Bar { get; set; }
15 | }
16 | ```
17 |
18 | you can resolve this compile error by the following workaround steps.
19 |
20 | 1. Remove `using namespace` directive.
21 |
22 | ```csharp
23 | // 👇 Remove this line...
24 | using Toolbelt.ComponentModel.DataAnnotations.Schema;
25 | ```
26 |
27 | 2. Insert `using alias = full qualified name` directive to add the alias of `Toolbelt.ComponentModel.DataAnnotations.Schema.IndexAttribute` class.
28 |
29 | ```csharp
30 | // 👇 Insert this line instead to add the alias.
31 | using IndexColumnAttribute =
32 | Toolbelt.ComponentModel.DataAnnotations.Schema.IndexAttribute;
33 | ```
34 |
35 | 3. Replace `[Index]` to `[IndexColumn]`.
36 |
37 | ```csharp
38 | ...
39 | // 👇 Replace [Index] to [IndexColumn]
40 | [IndexColumn]
41 | public int Bar { get; set; }
42 | ...
43 | ```
44 |
45 | Finally, the example code will be as below, and you can compile it as expected.
46 |
47 | ```csharp
48 | using Microsoft.EntityFrameworkCore;
49 | using IndexColumnAttribute =
50 | Toolbelt.ComponentModel.DataAnnotations.Schema.IndexAttribute;
51 |
52 | public class Foo {
53 | ...
54 | [IndexColumn]
55 | public int Bar { get; set; }
56 | }
57 | ```
58 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/MigrationTest.cs:
--------------------------------------------------------------------------------
1 | using Toolbelt;
2 | using Xunit;
3 | using static Toolbelt.Diagnostics.XProcess;
4 |
5 | namespace EFCore.IndexAttribute.Test.MigrationTest;
6 |
7 | public class MigrationTest
8 | {
9 | #if NET8_0
10 | private const string Framework = "net8.0";
11 | private const string EFToolVersion = "8.0.*";
12 | #elif NET9_0
13 | private const string Framework = "net9.0";
14 | private const string EFToolVersion = "9.0.*";
15 | #elif NET10_0
16 | private const string Framework = "net10.0";
17 | private const string EFToolVersion = "10.0.*";
18 | #endif
19 |
20 | [Fact]
21 | public async Task AddMigration_Initial_Test()
22 | {
23 | var testDir = FileIO.FindContainerDirToAncestor("*.csproj");
24 | using var workDir = WorkDirectory.CreateCopyFrom(Path.Combine(testDir, "TestProject"), _ => true);
25 |
26 | // Check the project is fine for build before adding migration.
27 | var build = await Start("dotnet", "build -f " + Framework + " --nologo", workDir).WaitForExitAsync();
28 | build.ExitCode.Is(n => n == 0, message: build.Output);
29 |
30 | var restoreTools = await Start("dotnet", "tool install dotnet-ef --version " + EFToolVersion, workDir).WaitForExitAsync();
31 | #if NET8_0
32 | restoreTools.ExitCode.Is(n => n == 0, message: build.Output);
33 | #endif
34 |
35 | // Add migration code, and...
36 | var migration = await Start("dotnet", "ef migrations add Initial --framework " + Framework, workDir).WaitForExitAsync();
37 | #if NET8_0
38 | migration.ExitCode.Is(n => n == 0, message: build.Output);
39 | #endif
40 |
41 | // Build it, and check it will be succeeded as expectedly.
42 | var rebuild = await Start("dotnet", "build -f " + Framework + " --nologo", workDir).WaitForExitAsync();
43 | rebuild.ExitCode.Is(n => n == 0, message: build.Output);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Attribute/PrimaryKeyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using Toolbelt.ComponentModel.DataAnnotations.Schema.Internals;
4 |
5 | namespace Toolbelt.ComponentModel.DataAnnotations.Schema
6 | {
7 | ///
8 | /// [deprecated] Please use [PrimaryKey] attribute with "using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;".
9 | ///
10 | [Obsolete("Please use [PrimaryKey] attribute with \"using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;\".")]
11 | [EditorBrowsable(EditorBrowsableState.Never)]
12 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
13 | public class PrimaryKeyAttribute : Attribute, INameAndOrder
14 | {
15 | ///
16 | /// Gets the name of this primary key.
17 | ///
18 | public string Name { get; }
19 |
20 | ///
21 | /// Gets a number that determines the column ordering for multi-column indexes. This will be -1 if no column order has been specified.
22 | ///
23 | public int Order { get; }
24 |
25 | ///
26 | /// Gets or sets a value to indicate whether to define a cluster index.
27 | ///
28 | public bool IsClustered { get; set; }
29 |
30 | ///
31 | /// Initializes a new PrimaryKeyAttribute instance for a primary key that will be named by convention and has no column order specified.
32 | ///
33 | public PrimaryKeyAttribute() : this("", -1)
34 | {
35 | }
36 |
37 | ///
38 | /// Initializes a new PrimaryKeyAttribute instance for a primary key with the given name and has no column order specified.
39 | ///
40 | /// The primary key name.
41 | public PrimaryKeyAttribute(string name) : this(name, -1)
42 | {
43 | }
44 |
45 | ///
46 | /// Initializes a new PrimaryKeyAttribute instance for a primary key with the given name and column order.
47 | ///
48 | /// The primary key name.
49 | /// A number which will be used to determine column ordering for multi-column primary keys.
50 | public PrimaryKeyAttribute(string name, int order)
51 | {
52 | this.Name = name;
53 | this.Order = order;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Attribute/EFCore.IndexAttribute.Attribute.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Toolbelt.EntityFrameworkCore.IndexAttribute.Attribute
6 | Toolbelt.ComponentModel.DataAnnotations.Schema
7 | 5.0.1.3
8 | J.Sakamoto
9 | J.Sakamoto
10 | IndexAttribute for EntityFrameworkCore
11 | The `[IndexColumn]` attribute that is the revival of `[Index]` attribute for EF Core. (with extension for model building.)
This package also provides [PrimaryKey] attribute.
12 | Copyright 2017-2025 J.Sakamoto, MIT License
13 |
14 | MIT
15 | https://github.com/jsakamoto/EntityFrameworkCore.IndexAttribute
16 | entity-framework-core entityframeworkcore efcore index attribute
17 | true
18 | ..\dist\
19 | nupkg-icon.png
20 | README.md
21 |
22 |
23 |
24 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
25 | $(NoWarn);1591
26 | (Please write the package release notes in "RELEASE-NOTES.txt".)
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | @(ReleaseNoteLines, '%0a')
40 | $([System.Text.RegularExpressions.Regex]::Match($(PackageReleaseNotes), "^(v\.[\d\.]+.+?)v\.[\d\.]+", System.Text.RegularExpressions.RegexOptions.Singleline).Groups[1].Value)
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/TestProject/TestProject.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0;net9.0;net10.0
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 | all
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | runtime; build; native; contentfiles; analyzers; buildtransitive
33 | all
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | runtime; build; native; contentfiles; analyzers; buildtransitive
44 | all
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/IndexAttributeTest_on_SQLite.cs:
--------------------------------------------------------------------------------
1 | using EntityFrameworkCore.IndexAttributeTest.Models;
2 | using Microsoft.EntityFrameworkCore;
3 | using Xunit;
4 |
5 | namespace EntityFrameworkCore.IndexAttributeTest;
6 |
7 | public class IndexAttributeTest_on_SQLite
8 | {
9 | [Fact(DisplayName = "CreateDb with Indexes on SQLite")]
10 | public void CreateDb_with_Indexes_Test()
11 | {
12 | var option = new DbContextOptionsBuilder()
13 | .UseSqlite("Data Source=:memory:")
14 | .Options;
15 | using var db = new MyDbContext(option);
16 | db.Database.OpenConnection();
17 |
18 | // Create database.
19 | db.Database.EnsureCreated();
20 |
21 | // Validate database indexes.
22 | var conn = db.Database.GetDbConnection();
23 | using var cmd = conn.CreateCommand();
24 | cmd.CommandText = @"
25 | SELECT * FROM sqlite_master
26 | WHERE type = 'index'
27 | ORDER BY tbl_name, name, sql";
28 | var reader = cmd.ExecuteReader();
29 | var dump = new List();
30 | try { while (reader.Read()) dump.Add(reader[4]?.ToString() ?? throw new NullReferenceException("reader[4] returns null.")); }
31 | finally { reader.Close(); }
32 | dump.Is(
33 | @"CREATE INDEX ""IX_Country"" ON ""People"" (""Address_Country"")",
34 | @"CREATE INDEX ""IX_Lines"" ON ""People"" (""Line1"", ""Line2"")",
35 | @"CREATE INDEX ""IX_People_FaxNumber_CountryCode"" ON ""People"" (""FaxNumber_CountryCode"")",
36 | @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"")",
37 | @"CREATE INDEX ""IX_People_PhoneNumber_CountryCode"" ON ""People"" (""PhoneNumber_CountryCode"")",
38 | @"CREATE INDEX ""IX_SNSAccounts_PersonId"" ON ""SNSAccounts"" (""PersonId"")",
39 | @"CREATE INDEX ""IX_SNSAccounts_Provider"" ON ""SNSAccounts"" (""Provider"")",
40 | @"CREATE UNIQUE INDEX ""Ix_Provider_and_Account"" ON ""SNSAccounts"" (""Provider"", ""AccountName"")");
41 | }
42 |
43 | [Fact(DisplayName = "IsClustered=true with no configuration causes NotSupportedException")]
44 | public void IsCLusteredTrue_With_NoConfiguration_Causes_NotSupportedException_Test()
45 | {
46 | var option = new DbContextOptionsBuilder()
47 | .UseSqlite("Data Source=:memory:")
48 | .Options;
49 | using var db = new MyDbContextWithNoConfiguration(option);
50 | var e = Assert.ThrowsAny(() =>
51 | {
52 | db.Database.OpenConnection();
53 | });
54 | e.InnerException.IsInstanceOf();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Attribute/IndexAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using Toolbelt.ComponentModel.DataAnnotations.Schema.Internals;
4 |
5 | namespace Toolbelt.ComponentModel.DataAnnotations.Schema
6 | {
7 | ///
8 | /// [deprecated] Please use [IndexColumn] attribute with "using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;".
9 | ///
10 | [Obsolete("Please use [IndexColumn] attribute with \"using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;\".")]
11 | [EditorBrowsable(EditorBrowsableState.Never)]
12 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
13 | public class IndexAttribute : Attribute, INameAndOrder
14 | {
15 | ///
16 | /// Gets the index name.
17 | ///
18 | public string Name { get; }
19 |
20 | ///
21 | /// Gets a number that determines the column ordering for multi-column indexes. This will be -1 if no column order has been specified.
22 | ///
23 | public int Order { get; }
24 |
25 | ///
26 | /// Gets or sets a value to indicate whether to define a unique index.
27 | ///
28 | public bool IsUnique { get; set; }
29 |
30 | ///
31 | /// Gets or sets a value to indicate whether to define a cluster index.
32 | ///
33 | public bool IsClustered { get; set; }
34 |
35 | ///
36 | /// Gets or sets an array of name of properties for inclusion into this index.
37 | ///
38 | public string[] Includes { get; set; } = new string[] { };
39 |
40 | ///
41 | /// Initializes a new IndexAttribute instance for an index that will be named by convention and has no column order, uniqueness specified.
42 | ///
43 | public IndexAttribute() : this("", -1)
44 | {
45 | }
46 |
47 | ///
48 | /// Initializes a new IndexAttribute instance for an index with the given name and has no column order, uniqueness specified.
49 | ///
50 | /// The index name.
51 | public IndexAttribute(string name) : this(name, -1)
52 | {
53 | }
54 |
55 | ///
56 | /// Initializes a new IndexAttribute instance for an index with the given name and column order, but with no uniqueness specified.
57 | ///
58 | /// The index name.
59 | /// A number which will be used to determine column ordering for multi-column indexes.
60 | public IndexAttribute(string name, int order)
61 | {
62 | this.Name = name;
63 | this.Order = order;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute/EFCore.IndexAttribute.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;netstandard2.1
5 | Toolbelt.EntityFrameworkCore.IndexAttribute
6 | Toolbelt.ComponentModel.DataAnnotations.Schema
7 | Toolbelt.EntityFrameworkCore.IndexAttribute
8 | 5.0.1.3
9 | J.Sakamoto
10 | J.Sakamoto
11 | IndexAttribute and model builder extension for EntityFrameworkCore
12 |
13 | The `[IndexColumn]` attribute that is the revival of `[Index]` attribute for EF Core. (with extension for model building.)
14 | This package also provides [PrimaryKey] attribute.
15 |
16 | Copyright 2017-2025 J.Sakamoto, MIT License
17 |
18 | https://github.com/jsakamoto/EntityFrameworkCore.IndexAttribute
19 | (Please write the package release notes in "RELEASE-NOTES.txt".)
20 | entity-framework-core entityframeworkcore efcore index attribute
21 | MIT
22 | true
23 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
24 | $(NoWarn);1591
25 | ..\dist\
26 | nupkg-icon.png
27 | README.md
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | @(ReleaseNoteLines, '%0a')
46 | $([System.Text.RegularExpressions.Regex]::Match($(PackageReleaseNotes), "^(v\.[\d\.]+.+?)v\.[\d\.]+", System.Text.RegularExpressions.RegexOptions.Singleline).Groups[1].Value)
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.SqlServer/EFCore.IndexAttribute.SqlServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;netstandard2.1
5 | Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer
6 | Toolbelt.ComponentModel.DataAnnotations.Schema
7 | Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer
8 | 5.0.1.3
9 | J.Sakamoto
10 | J.Sakamoto
11 | IndexAttribute and model builder extension for EntityFrameworkCore
12 | The `[IndexColumn]` attribute that is the revival of `[Index]` attribute for EF Core. (with extension for model building.)
This package also provides [PrimaryKey] attribute.
13 | Copyright 2019-2025 J.Sakamoto, MIT License
14 |
15 | MIT
16 | https://github.com/jsakamoto/EntityFrameworkCore.IndexAttribute
17 | (Please write the package release notes in "RELEASE-NOTES.txt".)
18 | entity-framework-core entityframeworkcore efcore index attribute
19 | true
20 | ..\dist\
21 | nupkg-icon.png
22 | README.md
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
43 | $(NoWarn);1591
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | @(ReleaseNoteLines, '%0a')
52 | $([System.Text.RegularExpressions.Regex]::Match($(PackageReleaseNotes), "^(v\.[\d\.]+.+?)v\.[\d\.]+", System.Text.RegularExpressions.RegexOptions.Singleline).Groups[1].Value)
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/EFCore.IndexAttribute.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net9.0;net10.0
5 | enable
6 | $(WarningsAsErrors);nullable
7 | false
8 | Toolbelt.EntityFrameworkCore.Metadata.Builders.Test
9 | enable
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 | all
21 |
22 |
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 | all
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/EFCore.IndexAttribute.Test/IndexAttributeTest.cs:
--------------------------------------------------------------------------------
1 | using EntityFrameworkCore.IndexAttributeTest.Models;
2 | using Microsoft.Data.SqlClient;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.Extensions.Logging;
5 | using Xunit;
6 |
7 | namespace EntityFrameworkCore.IndexAttributeTest;
8 |
9 | public class IndexAttributeTest
10 | {
11 | private static MyDbContextBase CreateMyDbContext(bool enableSqlServerFeature)
12 | {
13 | var server = Environment.GetEnvironmentVariable("MSSQL_SERVER");
14 | if (string.IsNullOrEmpty(server)) server = "(localdb)\\mssqllocaldb";
15 | var user = Environment.GetEnvironmentVariable("MSSQL_USER");
16 | var pwd = Environment.GetEnvironmentVariable("MSSQL_PWD");
17 | var credential = (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(pwd)) ? $"User={user};Password={pwd}" : "Integrated Security=True";
18 | var connStrBase = $"Server={server};{credential};TrustServerCertificate=True;";
19 |
20 | var dbName = Guid.NewGuid().ToString("N");
21 |
22 | using (var connToMaster = new SqlConnection(connStrBase + "Database=master;"))
23 | using (var cmd = new SqlCommand($"CREATE DATABASE [{dbName}]", connToMaster))
24 | {
25 | connToMaster.Open();
26 | cmd.ExecuteNonQuery();
27 | }
28 |
29 | var loggerFactory = LoggerFactory.Create(builder =>
30 | {
31 | builder
32 | .AddDebug()
33 | .AddFilter(
34 | category: DbLoggerCategory.Database.Command.Name,
35 | level: LogLevel.Information);
36 | });
37 |
38 | var connStr = connStrBase + $"Database={dbName};";
39 | var option = new DbContextOptionsBuilder()
40 | .UseSqlServer(connStr)
41 | .UseLoggerFactory(loggerFactory)
42 | .Options;
43 |
44 | return enableSqlServerFeature ?
45 | new MyDbContextForSqlServer(option) as MyDbContextBase :
46 | new MyDbContext(option);
47 | }
48 |
49 | [Fact(DisplayName = "CreateDb with Indexes")]
50 | public void CreateDb_with_Indexes_Test()
51 | {
52 | var dump = this.CreateDbAndDumpIndexes(enableSqlServerFeature: false);
53 | dump.Is(
54 | $"People|IX_Country|Address_Country|False|NONNULLABLE|NONCLUSTERED|False",
55 | $"People|IX_Lines|Line1|False|NONNULLABLE|NONCLUSTERED|False",
56 | $"People|IX_Lines|Line2|False|NONNULLABLE|NONCLUSTERED|False",
57 | $"People|IX_People_FaxNumber_CountryCode|FaxNumber_CountryCode|False|NONNULLABLE|NONCLUSTERED|False",
58 | $"People|IX_People_Name|Name|True|NONNULLABLE|NONCLUSTERED|False",
59 | $"People|IX_People_PhoneNumber_CountryCode|PhoneNumber_CountryCode|False|NONNULLABLE|NONCLUSTERED|False",
60 | $"SNSAccounts|Ix_Provider_and_Account|Provider|True|NONNULLABLE|NONCLUSTERED|False",
61 | $"SNSAccounts|Ix_Provider_and_Account|AccountName|True|NONNULLABLE|NONCLUSTERED|False",
62 | $"SNSAccounts|IX_SNSAccounts_PersonId|PersonId|False|NONNULLABLE|NONCLUSTERED|False",
63 | $"SNSAccounts|IX_SNSAccounts_Provider|Provider|False|NONNULLABLE|NONCLUSTERED|False"
64 | );
65 | }
66 |
67 | [Fact(DisplayName = "CreateDb with Indexes for SQL Server")]
68 | public void CreateDb_with_IndexesForSqlServer_Test()
69 | {
70 | var dump = this.CreateDbAndDumpIndexes(enableSqlServerFeature: true);
71 | dump.Is(
72 | $"People|IX_Country|Address_Country|False|NONNULLABLE|NONCLUSTERED|False",
73 | $"People|IX_Country|Address_ZipPostCode|False|NONNULLABLE|NONCLUSTERED|True",
74 | $"People|IX_Country|Address_TownCity|False|NONNULLABLE|NONCLUSTERED|True",
75 | $"People|IX_Lines|Line1|False|NONNULLABLE|NONCLUSTERED|False",
76 | $"People|IX_Lines|Line2|False|NONNULLABLE|NONCLUSTERED|False",
77 | $"People|IX_People_FaxNumber_CountryCode|FaxNumber_CountryCode|False|NONNULLABLE|NONCLUSTERED|False",
78 | $"People|IX_People_Name|Name|True|NONNULLABLE|NONCLUSTERED|False",
79 | $"People|IX_People_PhoneNumber_CountryCode|PhoneNumber_CountryCode|False|NONNULLABLE|NONCLUSTERED|False",
80 | $"SNSAccounts|Ix_Provider_and_Account|Provider|True|NONNULLABLE|CLUSTERED|False",
81 | $"SNSAccounts|Ix_Provider_and_Account|AccountName|True|NONNULLABLE|CLUSTERED|False",
82 | $"SNSAccounts|IX_SNSAccounts_PersonId|PersonId|False|NONNULLABLE|NONCLUSTERED|False",
83 | $"SNSAccounts|IX_SNSAccounts_Provider|Provider|False|NONNULLABLE|NONCLUSTERED|False"
84 | );
85 | }
86 |
87 | private IEnumerable CreateDbAndDumpIndexes(bool enableSqlServerFeature)
88 | {
89 | using var db = CreateMyDbContext(enableSqlServerFeature);
90 |
91 | // Create database.
92 | db.Database.OpenConnection();
93 | db.Database.EnsureCreated();
94 |
95 | try
96 | {
97 | // Validate database indexes.
98 | if (!(db.Database.GetDbConnection() is SqlConnection conn)) throw new NullReferenceException("db.Database.GetDbConnection() returns not SqlConnection.");
99 | using var cmd = conn.CreateCommand();
100 | cmd.CommandText = @"
101 | SELECT [Table] = t.name, [Index] = ind.name, [Column] = col.name, [IsUnique] = ind.is_unique, [Nullable] = col.is_nullable, [Type] = ind.type_desc, [IsInclude] = ic.is_included_column
102 | FROM sys.indexes ind
103 | INNER JOIN sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id
104 | INNER JOIN sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id
105 | INNER JOIN sys.tables t ON ind.object_id = t.object_id
106 | WHERE ind.is_primary_key = 0 AND t.is_ms_shipped = 0
107 | ORDER BY t.name, ind.name, ind.index_id, ic.is_included_column, ic.key_ordinal;";
108 | var dump = new List();
109 | var r = cmd.ExecuteReader();
110 | try { while (r.Read()) dump.Add($"{r["Table"]}|{r["Index"]}|{r["Column"]}|{r["IsUnique"]}|{((bool)r["Nullable"] ? "NULLABLE" : "NONNULLABLE")}|{r["Type"]}|{r["IsInclude"]}"); }
111 | finally { r.Close(); }
112 | return dump;
113 | }
114 | finally { db.Database.EnsureDeleted(); }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.csproj]
2 | indent_style = space
3 | indent_size = 2
4 | [*.{cs,vb}]
5 | #### Naming styles ####
6 |
7 | # Naming rules
8 |
9 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
10 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
11 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
12 |
13 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
14 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
15 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
16 |
17 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
18 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
19 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
20 |
21 | # Symbol specifications
22 |
23 | dotnet_naming_symbols.interface.applicable_kinds = interface
24 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
25 | dotnet_naming_symbols.interface.required_modifiers =
26 |
27 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
28 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
29 | dotnet_naming_symbols.types.required_modifiers =
30 |
31 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
32 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
33 | dotnet_naming_symbols.non_field_members.required_modifiers =
34 |
35 | # Naming styles
36 |
37 | dotnet_naming_style.begins_with_i.required_prefix = I
38 | dotnet_naming_style.begins_with_i.required_suffix =
39 | dotnet_naming_style.begins_with_i.word_separator =
40 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
41 |
42 | dotnet_naming_style.pascal_case.required_prefix =
43 | dotnet_naming_style.pascal_case.required_suffix =
44 | dotnet_naming_style.pascal_case.word_separator =
45 | dotnet_naming_style.pascal_case.capitalization = pascal_case
46 |
47 | dotnet_naming_style.pascal_case.required_prefix =
48 | dotnet_naming_style.pascal_case.required_suffix =
49 | dotnet_naming_style.pascal_case.word_separator =
50 | dotnet_naming_style.pascal_case.capitalization = pascal_case
51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
52 | tab_width = 4
53 | indent_size = 4
54 | end_of_line = crlf
55 | dotnet_style_coalesce_expression = true:suggestion
56 | dotnet_style_null_propagation = true:suggestion
57 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
58 | dotnet_style_prefer_auto_properties = true:silent
59 | dotnet_style_object_initializer = true:suggestion
60 | dotnet_style_collection_initializer = true:suggestion
61 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
62 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
63 | dotnet_style_prefer_conditional_expression_over_return = true:silent
64 | dotnet_style_explicit_tuple_names = true:suggestion
65 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
66 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
67 | dotnet_style_prefer_compound_assignment = true:suggestion
68 | dotnet_style_prefer_simplified_interpolation = true:suggestion
69 | dotnet_style_namespace_match_folder = true:suggestion
70 | dotnet_style_readonly_field = true:suggestion
71 | dotnet_style_qualification_for_field = true:warning
72 | dotnet_style_qualification_for_property = true:warning
73 | dotnet_style_qualification_for_method = true:warning
74 | dotnet_style_qualification_for_event = true:warning
75 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent
76 | dotnet_style_predefined_type_for_member_access = true:silent
77 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
78 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent
79 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
80 | dotnet_code_quality_unused_parameters = all:suggestion
81 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
82 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
83 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
84 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
85 |
86 | [*.cs]
87 | csharp_indent_labels = one_less_than_current
88 | csharp_space_around_binary_operators = before_and_after
89 | csharp_using_directive_placement = outside_namespace:silent
90 | csharp_prefer_simple_using_statement = true:suggestion
91 | csharp_prefer_braces = true:silent
92 | csharp_style_namespace_declarations = file_scoped:suggestion
93 | csharp_style_prefer_method_group_conversion = true:silent
94 | csharp_style_expression_bodied_methods = false:silent
95 | csharp_style_expression_bodied_constructors = false:silent
96 | csharp_style_expression_bodied_operators = false:silent
97 | csharp_style_expression_bodied_properties = true:silent
98 | csharp_style_expression_bodied_indexers = true:silent
99 | csharp_style_expression_bodied_accessors = true:silent
100 | csharp_style_expression_bodied_lambdas = true:silent
101 | csharp_style_expression_bodied_local_functions = false:silent
102 | csharp_style_throw_expression = true:suggestion
103 | csharp_style_prefer_null_check_over_type_check = true:suggestion
104 | csharp_prefer_simple_default_expression = true:suggestion
105 | csharp_style_prefer_local_over_anonymous_function = true:suggestion
106 | csharp_style_prefer_index_operator = true:suggestion
107 | csharp_style_prefer_range_operator = true:suggestion
108 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
109 | csharp_style_prefer_tuple_swap = true:suggestion
110 | csharp_style_inlined_variable_declaration = true:suggestion
111 | csharp_style_deconstructed_variable_declaration = true:suggestion
112 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
113 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent
114 | csharp_prefer_static_local_function = true:suggestion
115 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
116 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
117 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
118 | csharp_style_conditional_delegate_call = true:suggestion
119 | csharp_style_prefer_parameter_null_checking = true:suggestion
120 | csharp_style_prefer_switch_expression = true:suggestion
121 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
122 | csharp_style_prefer_pattern_matching = true:silent
123 | csharp_style_prefer_not_pattern = true:suggestion
124 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
125 | csharp_style_prefer_extended_property_pattern = true:suggestion
126 | csharp_style_var_for_built_in_types = true:warning
127 | csharp_style_var_when_type_is_apparent = true:warning
128 | csharp_style_var_elsewhere = true:warning
--------------------------------------------------------------------------------
/EFCore.IndexAttribute/AttributedIndexBuilderExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
5 | using Toolbelt.ComponentModel.DataAnnotations.Schema;
6 | using Toolbelt.ComponentModel.DataAnnotations.Schema.Internals;
7 | using Toolbelt.EntityFrameworkCore.Metadata.Builders;
8 |
9 | namespace Toolbelt.ComponentModel.DataAnnotations
10 | {
11 | public static class AttributedIndexBuilderExtension
12 | {
13 | internal class IndexBuilderArgument
14 | {
15 | public string[] PropertyNames { get; }
16 |
17 | public string IndexName { get; }
18 |
19 | public bool IsUnique { get; }
20 |
21 | public bool IsClustered { get; }
22 |
23 | public string[] Includes { get; }
24 |
25 | #pragma warning disable CS0618 // Type or member is obsolete
26 | public IndexBuilderArgument(IndexAttribute indexAttr, params string[] propertyNames)
27 | #pragma warning restore CS0618 // Type or member is obsolete
28 | {
29 | this.PropertyNames = propertyNames;
30 | this.IndexName = indexAttr.Name;
31 | this.IsUnique = indexAttr.IsUnique;
32 | this.IsClustered = indexAttr.IsClustered;
33 | this.Includes = indexAttr.Includes ?? new string[0];
34 | }
35 |
36 | #pragma warning disable CS0618 // Type or member is obsolete
37 | public IndexBuilderArgument(PrimaryKeyAttribute primaryKeyAttr, params string[] propertyNames)
38 | #pragma warning restore CS0618 // Type or member is obsolete
39 | {
40 | this.PropertyNames = propertyNames;
41 | this.IndexName = primaryKeyAttr.Name;
42 | this.IsClustered = primaryKeyAttr.IsClustered;
43 | }
44 | }
45 |
46 | public static void BuildIndexesFromAnnotations(this ModelBuilder modelBuilder)
47 | {
48 | modelBuilder.BuildIndexesFromAnnotations(
49 | postProcessForIndex: null,
50 | postProcessForPrimaryKey: null,
51 | configure: null);
52 | }
53 |
54 | public static void BuildIndexesFromAnnotations(this ModelBuilder modelBuilder, Action configure)
55 | {
56 | modelBuilder.BuildIndexesFromAnnotations(
57 | postProcessForIndex: null,
58 | postProcessForPrimaryKey: null,
59 | configure: configure);
60 | }
61 |
62 | internal static void BuildIndexesFromAnnotations(
63 | this ModelBuilder modelBuilder,
64 | Action postProcessForIndex,
65 | Action postProcessForPrimaryKey,
66 | Action configure
67 | )
68 | {
69 | var options = new AttributedIndexBuilderOptions();
70 | configure?.Invoke(options);
71 |
72 | #pragma warning disable CS0618 // Type or member is obsolete
73 | AnnotationBasedModelBuilder.Build(
74 | modelBuilder,
75 | (props) => CreateBuilderArguments(props, (attr, propNames) => new IndexBuilderArgument(attr, propNames)),
76 | (b1, b2, arg) => BuildIndex(options, b1, b2, arg, postProcessForIndex));
77 | AnnotationBasedModelBuilder.Build(
78 | modelBuilder,
79 | (props) => CreateBuilderArguments(props, (attr, propNames) => new IndexBuilderArgument(attr, propNames)),
80 | (b1, b2, arg) => BuildPrimaryKey(b1, b2, arg, postProcessForPrimaryKey));
81 | #pragma warning restore CS0618 // Type or member is obsolete
82 | }
83 |
84 | private static IndexBuilderArgument[] CreateBuilderArguments(
85 | AnnotatedProperty[] annotatedProperties,
86 | Func createBuilderArgInstance
87 | )
88 | where TAttr : Attribute, INameAndOrder
89 | {
90 | var unnamedIndexArgs = annotatedProperties
91 | .Where(prop => prop.Attribute.Name == "")
92 | .Select(prop => createBuilderArgInstance(prop.Attribute, new[] { prop.Name }));
93 |
94 | var namedIndexArgs = annotatedProperties
95 | .Where(prop => prop.Attribute.Name != "")
96 | .GroupBy(prop => prop.Attribute.Name)
97 | .Select(g => createBuilderArgInstance(
98 | g.First().Attribute,
99 | g.OrderBy(item => item.Attribute.Order).Select(item => item.Name).ToArray())
100 | );
101 |
102 | var indexBuilderArgs = unnamedIndexArgs.Concat(namedIndexArgs).ToArray();
103 | return indexBuilderArgs;
104 | }
105 |
106 | private static void BuildIndex(
107 | AttributedIndexBuilderOptions options,
108 | EntityTypeBuilder builder1,
109 | OwnedNavigationBuilder builder2,
110 | IndexBuilderArgument builderArg,
111 | Action postProcess)
112 | {
113 | if (!options.SuppressNotSupportedException.IsClustered && builderArg.IsClustered)
114 | throw new NotSupportedException(
115 | "\"IsClustered=true\" of [Index] attribute is not supported.\n" +
116 | "If you want to use \"IsClustered=true\", you need to call \"BuildIndexesFromAnnotationsForSqlServer()\" (in the Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer package) instead of \"BuildIndexesFromAnnotations()\", for a SQL Server connection.\n" +
117 | "You can also suppress this exception by calling like \"BuildIndexesFromAnnotations(options => options.SupressUnsupportedException.IsClustered = true)\"");
118 |
119 | if (!options.SuppressNotSupportedException.Includes && (builderArg.Includes ?? new string[0]).Any())
120 | throw new NotSupportedException(
121 | "\"Includes\" of [Index] attribute is not supported.\n" +
122 | "If you want to use \"Includes\", you need to call \"BuildIndexesFromAnnotationsForSqlServer()\" (in the Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer package) instead of \"BuildIndexesFromAnnotations()\", for a SQL Server connection.\n" +
123 | "You can also suppress this exception by calling like \"BuildIndexesFromAnnotations(options => options.SupressUnsupportedException.Includes = true)\"");
124 |
125 | var indexBuilder = builder1?.HasIndex(builderArg.PropertyNames) ?? builder2.HasIndex(builderArg.PropertyNames);
126 | indexBuilder.IsUnique(builderArg.IsUnique);
127 | if (builderArg.IndexName != "")
128 | {
129 | indexBuilder.HasName(builderArg.IndexName);
130 | }
131 | postProcess?.Invoke(indexBuilder, builderArg);
132 | }
133 |
134 | private static void BuildPrimaryKey(EntityTypeBuilder builder1, OwnedNavigationBuilder builder2, IndexBuilderArgument builderArg, Action postProcess)
135 | {
136 | if (builder1 == null) throw new NotSupportedException("Annotate primary key to owned entity isn't supported. If you want to do it, you have to implement it by Fluent API in DbContext.OnModelCreating() with EF Core v.2.2 or after.");
137 |
138 | var keyBuilder = builder1.HasKey(builderArg.PropertyNames);
139 | if (builderArg.IndexName != "")
140 | {
141 | keyBuilder.HasName(builderArg.IndexName);
142 | }
143 | postProcess?.Invoke(keyBuilder, builderArg);
144 | }
145 | }
146 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IndexColumnAttribute for EntityFramework Core [](https://www.nuget.org/packages/Toolbelt.EntityFrameworkCore.IndexAttribute/) [](https://github.com/jsakamoto/EntityFrameworkCore.IndexAttribute/actions/workflows/unit-tests.yml)
2 |
3 | ## What's this?
4 |
5 | The `[IndexColumn]` attribute that is the revival of `[Index]` attribute for EF Core. (with extension for model building.)
6 |
7 | ### Attention
8 |
9 | EF Core also includes the `[Index]` attribute officially, since ver.5.0.
10 |
11 | However, I'm going to continue improving and maintaining these libraries, because these libraries still have advantages as below.
12 |
13 | - You can still create indexes by data annotations even if you have to use **a lower version of EF Core**.
14 | - You can create indexes with **"included columns"** for SQL Server.
15 | - You can create a **clustered index** (This means you can also create a non-clustered primary key index).
16 |
17 | ## How to use?
18 |
19 | 1. Add [`Toolbelt.EntityFrameworkCore.IndexAttribute`](https://www.nuget.org/packages/Toolbelt.EntityFrameworkCore.IndexAttribute/) package to your project.
20 |
21 | ```shell
22 | > dotnet add package Toolbelt.EntityFrameworkCore.IndexAttribute
23 | ```
24 |
25 | ### Supported Versions
26 |
27 | The version of EF Core | Version of this package
28 | -----------------------------|-------------------------
29 | v.3.1, 5, 6, 7, 8, 9, and 10 | **v.5.0.1 or later (Recommended)**, v.5.0, v.3.2, v.3.1
30 | v.3.0 | **v.5.0.1 or later (Recommended)**, v.5.0, v.3.2, v.3.1, v.3.0
31 | v.2.0, 2.1, 2.2 | v.2.0.x
32 |
33 | If you want to use `IsClustered=true` and/or `Includes` index features on a SQL Server, you have to add [`Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer`](https://www.nuget.org/packages/Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer/) package to your project, instead.
34 |
35 | ```shell
36 | > dotnet add package Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer
37 | ```
38 |
39 | 2. Annotate your model with `[IndexColumn]` attribute that lives in `Toolbelt.ComponentModel.DataAnnotations.Schema.V5` namespace.
40 |
41 | _**Notice**_ - The attribute name is **`[IndexColumn]`**, is not `[Index]` (the attribute name `[Index]` is used by EFocre v.5.0).
42 |
43 | ```csharp
44 | using Toolbelt.ComponentModel.DataAnnotations.Schema.V5;
45 |
46 | public class Person
47 | {
48 | public int Id { get; set; }
49 |
50 | [IndexColumn] // <- Here!
51 | public string Name { get; set; }
52 | }
53 | ```
54 |
55 | 3. **[Important]** Override `OnModelCreating()` method of your DbContext class, and call `BuildIndexesFromAnnotations()` extension method which lives in `Toolbelt.ComponentModel.DataAnnotations` namespace.
56 |
57 | ```csharp
58 | using Microsoft.EntityFrameworkCore;
59 | using Toolbelt.ComponentModel.DataAnnotations;
60 |
61 | public class MyDbContext : DbContext
62 | {
63 | ...
64 | // Override "OnModelCreating", ...
65 | protected override void OnModelCreatin(ModelBuilder modelBuilder)
66 | {
67 | base.OnModelCreating(modelBuilder);
68 |
69 | // .. and invoke"BuildIndexesFromAnnotations"!
70 | modelBuilder.BuildIndexesFromAnnotations();
71 | }
72 | }
73 | ```
74 |
75 | If you use SQL Server and `IsClustered=true` and/or `Includes = new[]{"Foo", "Bar"}` features, you need to call `BuildIndexesFromAnnotationsForSqlServer()` extension method instead of `BuildIndexesFromAnnotations()` extension method.
76 |
77 | ```csharp
78 | ...
79 | // Override "OnModelCreating", ...
80 | protected override void OnModelCreatingModelBuilder modelBuilder)
81 | {
82 | base.OnModelCreating(modelBuilder);
83 |
84 | // Invoke uildIndexesFromAnnotationsForSqlServer"
85 | // instead of "BuildIndexesFromAnnotations".
86 | modelBuilder.BuildIndexesFromAnnotationsForSqlServer;
87 | }
88 | ```
89 |
90 | That's all!
91 |
92 | `BuildIndexesFromAnnotations()` (or, `BuildIndexesFromAnnotationsForSqlServer()`) extension method scans the DbContext with .NET Reflection technology, and detects `[IndexColumn]` attributes, then build models related to indexing.
93 |
94 | After doing that, the database which created by EF Core, contains indexes that are specified by `[IndexColumn]` attributes.
95 |
96 | ## Appendix A - Suppress "NotSupportedException"
97 |
98 | You will run into "NotSupportedException" when you call `BuildIndexesFromAnnotations()` with the model which is annotated with the `[IndexColumn]` attribute that's "IsClustered" property is true, or "Includes" property is not empty.
99 |
100 | If you have to call `BuildIndexesFromAnnotations()` in this case (for example, share the model for different Database products), you can suppress this behavior with configuration, like this.
101 |
102 | ```csharp
103 | ...
104 | protected override void OnModelCreating(ModelBuilder modelBuilder)
105 | {
106 | base.OnModelCreating(modelBuilder);
107 |
108 | // Suppress "NotSupportedException" for "IsClustered" and "Includes" feature.
109 | modelBuilder.BuildIndexesFromAnnotations(options => {
110 | options.SuppressNotSupportedException.IsClustered = true;
111 | options.SuppressNotSupportedException.Includes = true;
112 | });
113 | }
114 | }
115 | ```
116 |
117 | ## Appendix B - Notice for using "IsClustered=true"
118 |
119 | If you annotate the model with "IsClustered=true" index simply like this,
120 |
121 | ```csharp
122 | public class Employee {
123 | public int Id { get; set; }
124 |
125 | [IndexColumn(IsClustered = true)]
126 | public string EmployeeCode { get; set; }
127 | }
128 | ```
129 |
130 | You will run into 'System.Data.SqlClient.SqlException' like this.
131 |
132 | ```
133 | System.Data.SqlClient.SqlException :
134 | Cannot create more than one clustered index on table '(table name)'.
135 | Drop the existing clustered index '(index name)' before creating another.
136 | ```
137 |
138 | In this case, you need to annotate a primary key property with `[PrimaryKey(IsClustered = false)]` attribute explicitly for suppress auto generated primary key to be clustered index.
139 |
140 | ```csharp
141 | public class Employee {
142 | [PrimaryKey(IsClustered = false)] // <- Add this line!
143 | public int Id { get; set; }
144 |
145 | [IndexColumn(IsClustered = true)]
146 | public string EmployeeCode { get; set; }
147 | }
148 | ```
149 |
150 | ## Appendix C - If you want to use only "IndexAttribute" without any dependencies...
151 |
152 | If you want to use only "IndexColumnAttribute" class without any dependencies, you can use [Toolbelt.EntityFrameworkCore.IndexAttribute.Attribute](https://j.mp/3kfJgTm) NuGet package.
153 |
154 | ## Appendix D - Upgrade an existing project
155 |
156 | For more detail on this topic, please visit [this link.](https://j.mp/2HlmNFJ)
157 |
158 | ## Appendix E - If you run into a compile error CS0104...
159 |
160 | For more detail on this topic, please visit [this link.](https://j.mp/3476B3X)
161 |
162 | ## For More Detail...
163 |
164 | This library is designed to have the same syntax as EF 6.x `[Index]` annotation.
165 |
166 | Please visit document site of EF 6.x and `[Index]` attribute for EF 6.x.
167 |
168 | - [MSDN Document - Entity Framework Code First Data Annotations](https://j.mp/37hHBZI)
169 | - [MSDN Document - IndexAttribute class](https://j.mp/2HeIAzp)
170 |
171 | ## Limitations
172 |
173 | `[IndexColumn]` attribute with `IsClustered=true` can apply only not owned entity types.
174 |
175 | ## Release Notes
176 |
177 | - [Toolbelt.EntityFrameworkCore.IndexAttribute.Attibute](https://j.mp/3lSWUfw)
178 | - [Toolbelt.EntityFrameworkCore.IndexAttribute](https://j.mp/359Hg90)
179 | - [Toolbelt.EntityFrameworkCore.IndexAttribute.SqlServer](https://j.mp/3dBWDuu)
180 |
181 | ## License
182 |
183 | [MIT License](https://j.mp/3476mWB)
184 |
185 |
--------------------------------------------------------------------------------