私は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;}
}
FirstColumn
とSecondColumn
の組み合わせを一意にしたいです。
例:
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
それをする方法はありますか?
Entity Framework 6.1では、これを行うことができます。
[Index("IX_FirstAndSecond", 1, IsUnique = true)]
public int FirstColumn { get; set; }
[Index("IX_FirstAndSecond", 2, IsUnique = true)]
public int SecondColumn { get; set; }
属性の2番目のパラメーターは、インデックス内の列の順序を指定できる場所です。
追加情報: MSDN
私は問題を解決するための3つの方法を見つけました。
EntityFramework Coreの一意のインデックス:
最初のアプローチ:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasIndex(p => new {p.FirstColumn , p.SecondColumn}).IsUnique();
}
代替キーを使用してEF Coreで一意制約を作成する2番目のアプローチ。
例
1列
modelBuilder.Entity<Blog>().HasAlternateKey(c => c.SecondColumn).HasName("IX_SingeColumn");
複数の列
modelBuilder.Entity<Entity>().HasAlternateKey(c => new [] {c.FirstColumn, c.SecondColumn}).HasName("IX_MultipleColumns");
EF 6以下:
最初のアプローチ:
dbContext.Database.ExecuteSqlCommand(string.Format(
@"CREATE UNIQUE INDEX LX_{0} ON {0} ({1})",
"Entitys", "FirstColumn, SecondColumn"));
このアプローチは非常に高速で便利ですが、主な問題はEntity Frameworkがこれらの変更について何も知らないことです。
2番目のアプローチ:
この記事で見つけましたが、自分で試したことはありません。
CreateIndex("Entitys", new string[2] { "FirstColumn", "SecondColumn" },
true, "IX_Entitys");
このアプローチの問題は次のとおりです。それはDbMigrationを必要とします、それであなたがそれを持っていない場合、あなたは何をしますか?
3番目のアプローチ:
これが最良の方法だと思いますが、それにはしばらく時間がかかります。その背後にあるアイデアを紹介します。このリンク http://code.msdn.Microsoft.com/CSASPNETUniqueConstraintInE-d357224a に、一意のキーデータ注釈のコード:
[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
このアプローチの問題どうやってそれらを組み合わせることができますか?例えば、私はこのマイクロソフトの実装を拡張する考えを持っています。
[UniqueKey, 1] // Unique Key
public int FirstColumn { get; set;}
[UniqueKey ,1] // Unique Key
public int SecondColumn { get; set;}
後でMicrosoftの例で説明されているようにIDatabaseInitializerで、与えられた整数に従ってキーを組み合わせることができます。ただし、注意が必要なことが1つあります。uniqueプロパティが文字列型の場合は、MaxLengthを設定する必要があります。
Code-Firstを使用している場合は、カスタム拡張機能を実装できます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);
}
}
それを次のように使います。
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);
これにより、この移行が行われます。
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");
}
そして最終的には次のようにデータベースに入ります。
CREATE UNIQUE NONCLUSTERED INDEX [UQ_User_EmailPerApplication] ON [dbo].[User]
(
[Email] ASC,
[ApplicationId] ASC
)
複合キーを定義する必要があります。
データ注釈では、これは次のようになります。
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;}
}
次のように指定してOnModelCreatingをオーバーライドするときにmodelBuilderでこれを行うこともできます。
modelBuilder.Entity<Entity>().HasKey(x => new { x.FirstColumn, x.SecondColumn });
複合インデックスと外部キーを使った@chuck回答を完成させます。
外部キーの値を保持するプロパティを定義する必要があります。その後、このプロパティをインデックス定義内で使用できます。
たとえば、従業員がいる会社があり、従業員には(名前、会社)に対する唯一の制約があります。
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; }
}
Employeeクラスのマッピングは次のとおりです。
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);
}
}
独自のインデックスアノテーションにも@niaher拡張子を使用したことに注意してください。
流暢なAPIを使用するにはカスタム拡張機能が必要であると述べているというよりはっきりした答えは、執筆時点では正しかったかもしれません。以下のようにして(EFコア2.1)流暢なAPIを使用できます。
modelBuilder.Entity<ClassName>()
.HasIndex(a => new { a.Column1, a.Column2}).IsUnique();
私はあなたが常にEntityId
を主キーにしたいと思うので、それを複合キーで置き換えることは選択できません(複合キーがとを扱うのがはるかに複雑だからです。ビジネスロジックにおいても意味を持つ主キーを持つことはあまり意味がありません。
最低限必要なことは、データベース内の両方のフィールドに固有のキーを作成し、特に変更を保存するときに固有のキー違反の例外を確認することです。
さらに、変更を保存する前に一意の値を確認することができます(するべきです)。これを行う最良の方法はAny()
クエリを使うことです。転送されるデータの量を最小限に抑えるからです。
if (context.Entities.Any(e => e.FirstColumn == value1
&& e.SecondColumn == value2))
{
// deal with duplicate values here.
}
このチェックだけでは十分ではないことに注意してください。チェックと実際のコミットの間には常にいくらかの待ち時間があるので、あなたは常にユニークな制約+例外処理が必要になるでしょう。