├── 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 [![NuGet Package](https://img.shields.io/nuget/v/Toolbelt.EntityFrameworkCore.IndexAttribute.svg)](https://www.nuget.org/packages/Toolbelt.EntityFrameworkCore.IndexAttribute/) [![unit tests](https://github.com/jsakamoto/EntityFrameworkCore.IndexAttribute/actions/workflows/unit-tests.yml/badge.svg?branch=master&event=push)](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 | --------------------------------------------------------------------------------