I'm using Entity Framework 5.0 Code First;
public class Entity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string EntityId { get; set;}
public int FirstColumn { get; set;}
public int SecondColumn { get; set;}
}
I want to make the combination between FirstColumn
and SecondColumn
as unique.
Example:
Id FirstColumn SecondColumn
1 1 1 = OK
2 2 1 = OK
3 3 3 = OK
5 3 1 = THIS OK
4 3 3 = GRRRRR! HERE ERROR
Is there anyway to do that?
With Entity Framework 6.1, you can now do this:
[Index("IX_FirstAndSecond", 1, IsUnique = true)]
public int FirstColumn { get; set; }
[Index("IX_FirstAndSecond", 2, IsUnique = true)]
public int SecondColumn { get; set; }
The second parameter in the attribute is where you can specify the order of the columns in the index.
More information: MSDN
I found three ways to solve the problem.
Unique indexes in EntityFramework Core:
First approach:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasIndex(p => new {p.FirstColumn , p.SecondColumn}).IsUnique();
}
The second approach to create Unique Constraints with EF Core by using Alternate Keys.
Examples
One column:
modelBuilder.Entity<Blog>().HasAlternateKey(c => c.SecondColumn).HasName("IX_SingeColumn");
Multiple columns:
modelBuilder.Entity<Entity>().HasAlternateKey(c => new [] {c.FirstColumn, c.SecondColumn}).HasName("IX_MultipleColumns");
EF 6 and below:
First approach:
dbContext.Database.ExecuteSqlCommand(string.Format(
@"CREATE UNIQUE INDEX LX_{0} ON {0} ({1})",
"Entitys", "FirstColumn, SecondColumn"));
This approach is very fast and useful but the main problem is that Entity Framework doesn't know anything about those changes!
Second approach: I found it in this post but I did not tried by myself.
CreateIndex("Entitys", new string[2] { "FirstColumn", "SecondColumn" },
true, "IX_Entitys");
The problem of this approach is the following: It needs DbMigration so what do you do if you don't have it?
Third approach:
I think this is the best one but it requires some time to do it. I will just show you the idea behind it: In this link http://code.msdn.microsoft.com/CSASPNETUniqueConstraintInE-d357224a you can find the code for unique key data annotation:
[UniqueKey] // Unique Key
public int FirstColumn { get; set;}
[UniqueKey] // Unique Key
public int SecondColumn { get; set;}
// The problem hier
1, 1 = OK
1 ,2 = NO OK 1 IS UNIQUE
The problem for this approach; How can I combine them? I have an idea to extend this Microsoft implementation for example:
[UniqueKey, 1] // Unique Key
public int FirstColumn { get; set;}
[UniqueKey ,1] // Unique Key
public int SecondColumn { get; set;}
Later in the IDatabaseInitializer as described in the Microsoft example you can combine the keys according to the given integer. One thing has to be noted though: If the unique property is of type string then you have to set the MaxLength.
CREATE UNIQUE INDEX ix_{1}_{2} ON {0} ({1}, {2})
? (see BOL )
If you're using Code-First, you can implement a custom extension HasUniqueIndexAnnotation
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration.Configuration;
internal static class TypeConfigurationExtensions
{
public static PrimitivePropertyConfiguration HasUniqueIndexAnnotation(
this PrimitivePropertyConfiguration property,
string indexName,
int columnOrder)
{
var indexAttribute = new IndexAttribute(indexName, columnOrder) { IsUnique = true };
var indexAnnotation = new IndexAnnotation(indexAttribute);
return property.HasColumnAnnotation(IndexAnnotation.AnnotationName, indexAnnotation);
}
}
Then use it like so:
this.Property(t => t.Email)
.HasColumnName("Email")
.HasMaxLength(250)
.IsRequired()
.HasUniqueIndexAnnotation("UQ_User_EmailPerApplication", 0);
this.Property(t => t.ApplicationId)
.HasColumnName("ApplicationId")
.HasUniqueIndexAnnotation("UQ_User_EmailPerApplication", 1);
Which will result in this migration:
public override void Up()
{
CreateIndex("dbo.User", new[] { "Email", "ApplicationId" }, unique: true, name: "UQ_User_EmailPerApplication");
}
public override void Down()
{
DropIndex("dbo.User", "UQ_User_EmailPerApplication");
}
And eventually end up in database as:
CREATE UNIQUE NONCLUSTERED INDEX [UQ_User_EmailPerApplication] ON [dbo].[User]
(
[Email] ASC,
[ApplicationId] ASC
)
this.Property(t => t.Email)
), what is that containing class? (ie: what is this
)
EntityTypeConfiguration<T>
The answer from niaher stating that to use the fluent API you need a custom extension may have been correct at the time of writing. You can now (EF core 2.1) use the fluent API as follows:
modelBuilder.Entity<ClassName>()
.HasIndex(a => new { a.Column1, a.Column2}).IsUnique();
System.InvalidOperationException: 'Organization' cannot be used as a property on entity type 'Association' because it is configured as a navigation..
.HasIndex(x => new{ x.InstitutionId, x.ExternalId })
. Produces migrationBuilder.CreateIndex( name: "IX_Customers_InstitutionId_ExternalId", table: "Customers", columns: new[] { "InstitutionId", "ExternalId" }, unique: true);
You need to define a composite key.
With data annotations it looks like this:
public class Entity
{
public string EntityId { get; set;}
[Key]
[Column(Order=0)]
public int FirstColumn { get; set;}
[Key]
[Column(Order=1)]
public int SecondColumn { get; set;}
}
You can also do this with modelBuilder when overriding OnModelCreating by specifying:
modelBuilder.Entity<Entity>().HasKey(x => new { x.FirstColumn, x.SecondColumn });
Completing @chuck answer for using composite indices with foreign keys.
You need to define a property that will hold the value of the foreign key. You can then use this property inside the index definition.
For example, we have company with employees and only we have a unique constraint on (name, company) for any employee:
class Company
{
public Guid Id { get; set; }
}
class Employee
{
public Guid Id { get; set; }
[Required]
public String Name { get; set; }
public Company Company { get; set; }
[Required]
public Guid CompanyId { get; set; }
}
Now the mapping of the Employee class:
class EmployeeMap : EntityTypeConfiguration<Employee>
{
public EmployeeMap ()
{
ToTable("Employee");
Property(p => p.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(p => p.Name)
.HasUniqueIndexAnnotation("UK_Employee_Name_Company", 0);
Property(p => p.CompanyId )
.HasUniqueIndexAnnotation("UK_Employee_Name_Company", 1);
HasRequired(p => p.Company)
.WithMany()
.HasForeignKey(p => p.CompanyId)
.WillCascadeOnDelete(false);
}
}
Note that I also used @niaher extension for unique index annotation.
For those finding this looking for a 2021 solution, the working version of the accepted answer should now look like this:
[Index(nameof(FirstColumn), nameof(SecondColumn), IsUnique = true)]
public class Entity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string EntityId { get; set;}
public int FirstColumn { get; set;}
public int SecondColumn { get; set;}
}
So that the annotation should live on the model and not the individual columns. Also note the nameof()
syntax.
This answer was derived from the official documentation: https://docs.microsoft.com/en-us/ef/core/modeling/indexes?tabs=data-annotations
In the accepted answer by @chuck, there is a comment saying it will not work in the case of FK.
it worked for me, case of EF6 .Net4.7.2
public class OnCallDay
{
public int Id { get; set; }
//[Key]
[Index("IX_OnCallDateEmployee", 1, IsUnique = true)]
public DateTime Date { get; set; }
[ForeignKey("Employee")]
[Index("IX_OnCallDateEmployee", 2, IsUnique = true)]
public string EmployeeId { get; set; }
public virtual ApplicationUser Employee{ get; set; }
}
I assume you always want EntityId
to be the primary key, so replacing it by a composite key is not an option (if only because composite keys are far more complicated to work with and because it is not very sensible to have primary keys that also have meaning in the business logic).
The least you should do is create a unique key on both fields in the database and specifically check for unique key violation exceptions when saving changes.
Additionally you could (should) check for unique values before saving changes. The best way to do that is by an Any()
query, because it minimizes the amount of transferred data:
if (context.Entities.Any(e => e.FirstColumn == value1
&& e.SecondColumn == value2))
{
// deal with duplicate values here.
}
Beware that this check alone is never enough. There is always some latency between the check and the actual commit, so you'll always need the unique constraint + exception handling.
Recently added a composite key with the uniqueness of 2 columns using the approach that 'chuck' recommended, thank @chuck. Only this approached looked cleaner to me:
public int groupId {get; set;}
[Index("IX_ClientGrouping", 1, IsUnique = true)]
public int ClientId { get; set; }
[Index("IX_ClientGrouping", 2, IsUnique = true)]
public int GroupName { get; set; }
I wanted to add my answer since the provided solutions did not help me. In my case one of the columns was a foreign key reference.
Old model:
public class Matrix
{
public int ID { get; set; }
public MachineData MachineData { get; set; }
public MachineVariant MachineVariant { get; set; }
}
Note that MachineVariant is an enum and MachineData is a reference.
Trying to use the provided solution by @Bassam Alugili:
modelBuilder.Entity<Matrix>()
.HasIndex(sm => new { sm.MachineData, sm.DoughVariant }).IsUnique(true);
Didn't work. So I added an ID column for the machineData foreign key like so:
public class Matrix
{
public int ID { get; set; }
public MachineData MachineData { get; set; }
[ForeignKey("MachineData")]
public int MachineDataID { get; set; }
public MachineVariant MachineVariant { get; set; }
}
And changed the modelbuilder code to this:
modelBuilder.Entity<Matrix>()
.HasIndex(sm => new { sm.MachineDataID, sm.DoughVariant }).IsUnique(true);
Which resulted in the desired solution
You should place Index attrib on top of the entity classs and define your multiple keys in string[]
[Index("FirstColumn", "SecondColumn", IsUnique = true, Name = "My_Unique_Index")]
public class Entity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string EntityId { get; set;}
public int FirstColumn { get; set;}
public int SecondColumn { get; set;}
}
Success story sharing