194

I'm trying to build an EF Entity with Code First, and an EntityTypeConfiguration using fluent API. creating primary keys is easy but not so with a Unique Constraint. I was seeing old posts that suggested executing native SQL commands for this, but that seem to defeat the purpose. is this possible with EF6?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
kob490
  • 3,177
  • 4
  • 25
  • 40

6 Answers6

296

On EF6.2, you can use HasIndex() to add indexes for migration through fluent API.

https://github.com/aspnet/EntityFramework6/issues/274

Example

modelBuilder
    .Entity<User>()
    .HasIndex(u => u.Email)
        .IsUnique();

On EF6.1 onwards, you can use IndexAnnotation() to add indexes for migration in your fluent API.

http://msdn.microsoft.com/en-us/data/jj591617.aspx#PropertyIndex

You must add reference to:

using System.Data.Entity.Infrastructure.Annotations;

Basic Example

Here is a simple usage, adding an index on the User.FirstName property

modelBuilder 
    .Entity<User>() 
    .Property(t => t.FirstName) 
    .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));

Practical Example:

Here is a more realistic example. It adds a unique index on multiple properties: User.FirstName and User.LastName, with an index name "IX_FirstNameLastName"

modelBuilder 
    .Entity<User>() 
    .Property(t => t.FirstName) 
    .IsRequired()
    .HasMaxLength(60)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName, 
        new IndexAnnotation(
            new IndexAttribute("IX_FirstNameLastName", 1) { IsUnique = true }));

modelBuilder 
    .Entity<User>() 
    .Property(t => t.LastName) 
    .IsRequired()
    .HasMaxLength(60)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName, 
        new IndexAnnotation(
            new IndexAttribute("IX_FirstNameLastName", 2) { IsUnique = true }));
Niels R.
  • 7,260
  • 5
  • 32
  • 44
Yorro
  • 11,445
  • 4
  • 37
  • 47
  • 4
    This is required to name column annotation as "Index"! I wrote another name and it didn't work! I spent hours before I try rename it to original "Index" as in your post and understood that this important. :( There must be a constant for it in the framework to not hard code the string. – Alexander Vasilyev Jul 10 '14 at 07:09
  • useful link for the problem! http://stackoverflow.com/questions/18889218/unique-key-constraints-for-multiple-columns-in-entity-framework – Bassam Alugili Jul 15 '14 at 09:23
  • 10
    @AlexanderVasilyev The constant is defined as `IndexAnnotation.AnnotationName` – Nathan Jul 25 '14 at 21:21
  • 3
    @Nathan Thank you! That's it! The example in this post must be corrected using this constant. – Alexander Vasilyev Aug 07 '14 at 13:45
  • It's also pretty easy to create your own extension methods so you can write things like `Property(x => x.FirstName).Unique("IX_FirstName")` or `new[] { Property(x => x.FirstName), Property(x => x.LastName) }.Unique("IX_FirstNameLastName")` – Phil Mar 26 '15 at 16:36
  • 2
    Can't seem to find it in EF7 - DNX – Shimmy Weitzhandler Jul 21 '15 at 02:52
  • Yeah, and how do you add unique constraints on navigation properties? Lets say if one of the `User` properties has a type of `Role`? how do you make a unique constraint off that? – Tanuki Jul 28 '15 at 12:56
  • That's a lot of typing just to an Unique constraint. Jeez – wickd Dec 21 '15 at 11:17
  • 2
    I believe that IsUnique need to be set to true when creating IndexAttribute in the first example. Like this: `new IndexAttribute() { IsUnique = true }`. Otherwise it creates just regular (non-unique) index. – jakubka Feb 05 '16 at 12:50
  • How would you get this to work with nullable fields? – Eitan K Mar 18 '16 at 13:39
  • 1
    I have tried this and can compile however when I run Update-Database, the index is not added. Anyone knows why and how to solve? – bman Aug 02 '16 at 04:11
  • @bman, check your migration scripts. I tried this on a `string` variable not realizing I needed to set a `HasMaxLength` but after I fixed this, running the migration again did not re-scaffold properly. You should have something like this: `AlterColumn("dbo.GiftCards", "CardNumber", c => c.String(nullable: false, maxLength: 50)); CreateIndex("dbo.GiftCards", "CardNumber", unique: true);` – justanotherdev Nov 24 '16 at 22:16
  • @Yorro, my ConsoleApplication needed `using System.ComponentModel.DataAnnotations.Schema;`. I can't run my application right now to see if it's right, but if this is OK, better add it below the other `using` statment for newbies like me ^^' – Henryk Budzinski Mar 07 '18 at 22:05
137

As an addition to Yorro's answer, it can also be done by using attributes.

Sample for int type unique key combination:

[Index("IX_UniqueKeyInt", IsUnique = true, Order = 1)]
public int UniqueKeyIntPart1 { get; set; }

[Index("IX_UniqueKeyInt", IsUnique = true, Order = 2)]
public int UniqueKeyIntPart2 { get; set; }

If the data type is string, then MaxLength attribute must be added:

[Index("IX_UniqueKeyString", IsUnique = true, Order = 1)]
[MaxLength(50)]
public string UniqueKeyStringPart1 { get; set; }

[Index("IX_UniqueKeyString", IsUnique = true, Order = 2)]
[MaxLength(50)]
public string UniqueKeyStringPart2 { get; set; }

If there is a domain/storage model separation concern, using Metadatatype attribute/class can be an option: https://msdn.microsoft.com/en-us/library/ff664465%28v=pandp.50%29.aspx?f=255&MSPPError=-2147217396


A quick console app example:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace EFIndexTest
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new AppDbContext())
            {
                var newUser = new User { UniqueKeyIntPart1 = 1, UniqueKeyIntPart2 = 1, UniqueKeyStringPart1 = "A", UniqueKeyStringPart2 = "A" };
                context.UserSet.Add(newUser);
                context.SaveChanges();
            }
        }
    }

    [MetadataType(typeof(UserMetadata))]
    public class User
    {
        public int Id { get; set; }
        public int UniqueKeyIntPart1 { get; set; }
        public int UniqueKeyIntPart2 { get; set; }
        public string UniqueKeyStringPart1 { get; set; }
        public string UniqueKeyStringPart2 { get; set; }
    }

    public class UserMetadata
    {
        [Index("IX_UniqueKeyInt", IsUnique = true, Order = 1)]
        public int UniqueKeyIntPart1 { get; set; }

        [Index("IX_UniqueKeyInt", IsUnique = true, Order = 2)]
        public int UniqueKeyIntPart2 { get; set; }

        [Index("IX_UniqueKeyString", IsUnique = true, Order = 1)]
        [MaxLength(50)]
        public string UniqueKeyStringPart1 { get; set; }

        [Index("IX_UniqueKeyString", IsUnique = true, Order = 2)]
        [MaxLength(50)]
        public string UniqueKeyStringPart2 { get; set; }
    }

    public class AppDbContext : DbContext
    {
        public virtual DbSet<User> UserSet { get; set; }
    }
}
coni2k
  • 2,535
  • 2
  • 20
  • 21
  • 47
    Not if you want to keep your domain model completely separate from storage concerns. – Rickard Liljeberg Oct 26 '14 at 20:40
  • 4
    You also need to make sure you have a reference to EntityFramework – MikeT Dec 02 '14 at 13:12
  • 2
    Be nice if the Index attribute was separated from Entity Framework so I could include it in my Models project. I understand that it is a storage concern, but the main reason I use it is to put unique constraints on things like UserNames and Role Names. – Sam Feb 20 '15 at 02:24
  • 2
    Can't seem to find it in EF7 - DNX – Shimmy Weitzhandler Jul 21 '15 at 02:52
  • 1
    This must be out of date. It does not work at all. You just get an exception: Column 'FieldName' in table 'dbo.TableName' is of a type that is invalid for use as a key column in an index. – Cheesus Toast Oct 02 '15 at 15:19
  • 1
    You get an exception if you do not set the string length. I have edited the answer. It think it may normally be 70 characters for the whole name but it is whatever you feel like. – Cheesus Toast Oct 02 '15 at 15:42
  • 2
    The code does not work and my edit to fix it got rejected. Is this some kind of joke? – Cheesus Toast Oct 02 '15 at 18:55
  • 1
    @CheesusToast - post your edit as a separate answer then. Point out the differences and the problems it solves. – H H Oct 28 '15 at 08:28
  • 4
    This only works if you also restrict the length of the string, as SQL doesn't allow *nvarchar(max)* to be used as a key. – JMK Sep 17 '16 at 16:54
  • After three and half years, updated the answer... :) – coni2k Feb 20 '18 at 17:52
19

Here is an extension method for setting unique indexes more fluently:

public static class MappingExtensions
{
    public static PrimitivePropertyConfiguration IsUnique(this PrimitivePropertyConfiguration configuration)
    {
        return configuration.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute { IsUnique = true }));
    }
}

Usage:

modelBuilder 
    .Entity<Person>() 
    .Property(t => t.Name)
    .IsUnique();

Will generate migration such as:

public partial class Add_unique_index : DbMigration
{
    public override void Up()
    {
        CreateIndex("dbo.Person", "Name", unique: true);
    }

    public override void Down()
    {
        DropIndex("dbo.Person", new[] { "Name" });
    }
}

Src: Creating Unique Index with Entity Framework 6.1 fluent API

Community
  • 1
  • 1
Bartho Bernsmann
  • 2,393
  • 1
  • 25
  • 34
17

@coni2k 's answer is correct however you must add [StringLength] attribute for it to work otherwise you will get an invalid key exception (Example bellow).

[StringLength(65)]
[Index("IX_FirstNameLastName", 1, IsUnique = true)]
public string FirstName { get; set; }

[StringLength(65)]
[Index("IX_FirstNameLastName", 2, IsUnique = true)]
public string LastName { get; set; }
Arijoon
  • 2,184
  • 3
  • 24
  • 32
11

Unfortunately this is not supported in Entity Framework. It was on the roadmap for EF 6, but it got pushed back: Workitem 299: Unique Constraints (Unique Indexes)

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
Kenneth
  • 28,294
  • 6
  • 61
  • 84
-1
modelBuilder.Property(x => x.FirstName).IsUnicode().IsRequired().HasMaxLength(50);
Rousonur Jaman
  • 1,183
  • 14
  • 19