質問
Fluent構文または属性を使用して、プロパティに一意の制約を定義することは可能ですか?そうでない場合、回避策は何ですか?
主キーを持つユーザークラスがありますが、メールアドレスも一意であることを確認したいと思います。データベースを直接編集せずにこれは可能ですか?
解決策(マットの回答に基づく)
public class MyContext : DbContext {
public DbSet<User> Users { get; set; }
public override int SaveChanges() {
foreach (var item in ChangeTracker.Entries<IModel>())
item.Entity.Modified = DateTime.Now;
return base.SaveChanges();
}
public class Initializer : IDatabaseInitializer<MyContext> {
public void InitializeDatabase(MyContext context) {
if (context.Database.Exists() && !context.Database.CompatibleWithModel(false))
context.Database.Delete();
if (!context.Database.Exists()) {
context.Database.Create();
context.Database.ExecuteSqlCommand("alter table Users add constraint UniqueUserEmail unique (Email)");
}
}
}
}
私が知る限り、現時点ではEntity Frameworkでこれを行う方法はありません。ただし、これは一意の制約だけの問題ではありません...インデックスを作成し、制約をチェックし、場合によってはトリガーやその他の構成要素を作成することもできます。 これは使用できる単純なパターンです コードファーストのセットアップでは、データベースに依存しないことは認められていますが:
public class MyRepository : DbContext {
public DbSet<Whatever> Whatevers { get; set; }
public class Initializer : IDatabaseInitializer<MyRepository> {
public void InitializeDatabase(MyRepository context) {
if (!context.Database.Exists() || !context.Database.ModelMatchesDatabase()) {
context.Database.DeleteIfExists();
context.Database.Create();
context.ObjectContext.ExecuteStoreCommand("CREATE UNIQUE CONSTRAINT...");
context.ObjectContext.ExecuteStoreCommand("CREATE INDEX...");
context.ObjectContext.ExecuteStoreCommand("ETC...");
}
}
}
}
別のオプションは、ドメインモデルがデータベースにデータを挿入/更新する唯一の方法である場合、一意性要件を自分で実装し、データベースを除外することができます。これは、より移植性の高いソリューションであり、コード内のビジネスルールを明確にする必要がありますが、無効なデータがバックドアにさらされることはありません。
EF 6.1以降では、次のことが可能になりました。
[Index(IsUnique = true)]
public string EmailAddress { get; set; }
これにより、厳密に言えば、一意制約ではなく一意インデックスが取得されます。最も実用的な目的のために それらは同じです 。
これとはあまり関係ありませんが、場合によっては役立つかもしれません。
たとえば、テーブルの制約として機能する2つの列に一意の複合インデックスを作成する場合は、バージョン4.3以降、新しい移行メカニズムを使用してそれを実現できます。
基本的に、移行スクリプトの1つに次のような呼び出しを挿入する必要があります。
CreateIndex("TableName", new string[2] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2");
そんな感じ:
namespace Sample.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class TableName_SetUniqueCompositeIndex : DbMigration
{
public override void Up()
{
CreateIndex("TableName", new[] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2");
}
public override void Down()
{
DropIndex("TableName", new[] { "Column1", "Column2" });
}
}
}
データベースの作成時にSQLを実行するための完全なハックを行います。独自のDatabaseInitializerを作成し、提供されている初期化子の1つから継承します。
public class MyDatabaseInitializer : RecreateDatabaseIfModelChanges<MyDbContext>
{
protected override void Seed(MyDbContext context)
{
base.Seed(context);
context.Database.Connection.StateChange += new StateChangeEventHandler(Connection_StateChange);
}
void Connection_StateChange(object sender, StateChangeEventArgs e)
{
DbConnection cnn = sender as DbConnection;
if (e.CurrentState == ConnectionState.Open)
{
// execute SQL to create indexes and such
}
cnn.StateChange -= Connection_StateChange;
}
}
これが、SQLステートメントに割り込むことができる唯一の場所です。
これはCTP4からです。 CTP5でどのように機能するかわかりません。
6.1では、@ mihkelmuurの答えの流fluentな構文バージョンを次のように使用できます。
Property(s => s.EmailAddress).HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
new IndexAttribute("IX_UniqueEmail") { IsUnique = true }));
流な方法は完璧なIMOではありませんが、少なくとも現在は可能です。
Arthur Vickersブログの詳細 http://blog.oneunicorn.com/2014/02/15/ef-6-1-creating-indexes-with-indexattribute/
これを行う方法があるかどうかを確認しようとして、私がこれまで見つけた唯一の方法はそれを自分で強制することでした。一意にする必要があるフィールドの名前を指定する各クラスに追加される属性を作成しました:
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=false,Inherited=true)]
public class UniqueAttribute:System.Attribute
{
private string[] _atts;
public string[] KeyFields
{
get
{
return _atts;
}
}
public UniqueAttribute(string keyFields)
{
this._atts = keyFields.Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries);
}
}
それから私のクラスでそれを追加します:
[CustomAttributes.Unique("Name")]
public class Item: BasePOCO
{
public string Name{get;set;}
[StringLength(250)]
public string Description { get; set; }
[Required]
public String Category { get; set; }
[Required]
public string UOM { get; set; }
[Required]
}
最後に、リポジトリ、Addメソッド、または次のような変更を保存するときにメソッドを追加します。
private void ValidateDuplicatedKeys(T entity)
{
var atts = typeof(T).GetCustomAttributes(typeof(UniqueAttribute), true);
if (atts == null || atts.Count() < 1)
{
return;
}
foreach (var att in atts)
{
UniqueAttribute uniqueAtt = (UniqueAttribute)att;
var newkeyValues = from pi in entity.GetType().GetProperties()
join k in uniqueAtt.KeyFields on pi.Name equals k
select new { KeyField = k, Value = pi.GetValue(entity, null).ToString() };
foreach (var item in _objectSet)
{
var keyValues = from pi in item.GetType().GetProperties()
join k in uniqueAtt.KeyFields on pi.Name equals k
select new { KeyField = k, Value = pi.GetValue(item, null).ToString() };
var exists = keyValues.SequenceEqual(newkeyValues);
if (exists)
{
throw new System.Exception("Duplicated Entry found");
}
}
}
}
リフレクションに依存する必要があるので、あまり良いことではありませんが、これまでのところ、これが私にとって有効なアプローチです! = D
EF5 Code First Migrationsを使用したビジュアルベーシックの簡単な方法
パブリッククラスのサンプル
Public Property SampleId As Integer
<Required>
<MinLength(1),MaxLength(200)>
Public Property Code() As String
終了クラス
属性MaxLengthは、文字列型の一意のインデックスにとって非常に重要です
Cmdを実行:update-database -verbose
cmdの実行後:add-migration 1
生成されたファイル内
Public Partial Class _1
Inherits DbMigration
Public Overrides Sub Up()
CreateIndex("dbo.Sample", "Code", unique:=True, name:="IX_Sample_Code")
End Sub
Public Overrides Sub Down()
'DropIndex if you need it
End Sub
End Class
Tobias Schittkowskiの答えに似ていますが、C#であり、consttaintsに複数のフィールドを持つ機能があります。
これを使用するには、一意にするフィールドに[一意]を配置します。文字列の場合、次のような処理を行う必要があります(MaxLength属性に注意してください):
[Unique]
[MaxLength(450)] // nvarchar(450) is max allowed to be in a key
public string Name { get; set; }
デフォルトの文字列フィールドはnvarchar(max)であり、キーでは許可されないためです。
制約内の複数のフィールドに対して、次のことができます。
[Unique(Name="UniqueValuePairConstraint", Position=1)]
public int Value1 { get; set; }
[Unique(Name="UniqueValuePairConstraint", Position=2)]
public int Value2 { get; set; }
まず、UniqueAttribute:
/// <summary>
/// The unique attribute. Use to mark a field as unique. The
/// <see cref="DatabaseInitializer"/> looks for this attribute to
/// create unique constraints in tables.
/// </summary>
internal class UniqueAttribute : Attribute
{
/// <summary>
/// Gets or sets the name of the unique constraint. A name will be
/// created for unnamed unique constraints. You must name your
/// constraint if you want multiple fields in the constraint. If your
/// constraint has only one field, then this property can be ignored.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the position of the field in the constraint, lower
/// numbers come first. The order is undefined for two fields with
/// the same position. The default position is 0.
/// </summary>
public int Position { get; set; }
}
次に、タイプからデータベーステーブル名を取得する便利な拡張機能を含めます。
public static class Extensions
{
/// <summary>
/// Get a table name for a class using a DbContext.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
/// <param name="type">
/// The class to look up the table name for.
/// </param>
/// <returns>
/// The table name; null on failure;
/// </returns>
/// <remarks>
/// <para>
/// Like:
/// <code>
/// DbContext context = ...;
/// string table = context.GetTableName<Foo>();
/// </code>
/// </para>
/// <para>
/// This code uses ObjectQuery.ToTraceString to generate an SQL
/// select statement for an entity, and then extract the table
/// name from that statement.
/// </para>
/// </remarks>
public static string GetTableName(this DbContext context, Type type)
{
return ((IObjectContextAdapter)context)
.ObjectContext.GetTableName(type);
}
/// <summary>
/// Get a table name for a class using an ObjectContext.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
/// <param name="type">
/// The class to look up the table name for.
/// </param>
/// <returns>
/// The table name; null on failure;
/// </returns>
/// <remarks>
/// <para>
/// Like:
/// <code>
/// ObjectContext context = ...;
/// string table = context.GetTableName<Foo>();
/// </code>
/// </para>
/// <para>
/// This code uses ObjectQuery.ToTraceString to generate an SQL
/// select statement for an entity, and then extract the table
/// name from that statement.
/// </para>
/// </remarks>
public static string GetTableName(this ObjectContext context, Type type)
{
var genericTypes = new[] { type };
var takesNoParameters = new Type[0];
var noParams = new object[0];
object objectSet = context.GetType()
.GetMethod("CreateObjectSet", takesNoParameters)
.MakeGenericMethod(genericTypes)
.Invoke(context, noParams);
var sql = (string)objectSet.GetType()
.GetMethod("ToTraceString", takesNoParameters)
.Invoke(objectSet, noParams);
Match match =
Regex.Match(sql, @"FROM\s+(.*)\s+AS", RegexOptions.IgnoreCase);
return match.Success ? match.Groups[1].Value : null;
}
}
次に、データベース初期化子:
/// <summary>
/// The database initializer.
/// </summary>
public class DatabaseInitializer : IDatabaseInitializer<PedContext>
{
/// <summary>
/// Initialize the database.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
public void InitializeDatabase(FooContext context)
{
// if the database has changed, recreate it.
if (context.Database.Exists()
&& !context.Database.CompatibleWithModel(false))
{
context.Database.Delete();
}
if (!context.Database.Exists())
{
context.Database.Create();
// Look for database tables in the context. Tables are of
// type DbSet<>.
foreach (PropertyInfo contextPropertyInfo in
context.GetType().GetProperties())
{
var contextPropertyType = contextPropertyInfo.PropertyType;
if (contextPropertyType.IsGenericType
&& contextPropertyType.Name.Equals("DbSet`1"))
{
Type tableType =
contextPropertyType.GetGenericArguments()[0];
var tableName = context.GetTableName(tableType);
foreach (var uc in UniqueConstraints(tableType, tableName))
{
context.Database.ExecuteSqlCommand(uc);
}
}
}
// this is a good place to seed the database
context.SaveChanges();
}
}
/// <summary>
/// Get a list of TSQL commands to create unique constraints on the given
/// table. Looks through the table for fields with the UniqueAttribute
/// and uses those and the table name to build the TSQL strings.
/// </summary>
/// <param name="tableClass">
/// The class that expresses the database table.
/// </param>
/// <param name="tableName">
/// The table name in the database.
/// </param>
/// <returns>
/// The list of TSQL statements for altering the table to include unique
/// constraints.
/// </returns>
private static IEnumerable<string> UniqueConstraints(
Type tableClass, string tableName)
{
// the key is the name of the constraint and the value is a list
// of (position,field) pairs kept in order of position - the entry
// with the lowest position is first.
var uniqueConstraints =
new Dictionary<string, List<Tuple<int, string>>>();
foreach (PropertyInfo entityPropertyInfo in tableClass.GetProperties())
{
var unique = entityPropertyInfo.GetCustomAttributes(true)
.OfType<UniqueAttribute>().FirstOrDefault();
if (unique != null)
{
string fieldName = entityPropertyInfo.Name;
// use the name field in the UniqueAttribute or create a
// name if none is given
string constraintName = unique.Name
?? string.Format(
"constraint_{0}_unique_{1}",
tableName
.Replace("[", string.Empty)
.Replace("]", string.Empty)
.Replace(".", "_"),
fieldName);
List<Tuple<int, string>> constraintEntry;
if (!uniqueConstraints.TryGetValue(
constraintName, out constraintEntry))
{
uniqueConstraints.Add(
constraintName,
new List<Tuple<int, string>>
{
new Tuple<int, string>(
unique.Position, fieldName)
});
}
else
{
// keep the list of fields in order of position
for (int i = 0; ; ++i)
{
if (i == constraintEntry.Count)
{
constraintEntry.Add(
new Tuple<int, string>(
unique.Position, fieldName));
break;
}
if (unique.Position < constraintEntry[i].Item1)
{
constraintEntry.Insert(
i,
new Tuple<int, string>(
unique.Position, fieldName));
break;
}
}
}
}
}
return
uniqueConstraints.Select(
uc =>
string.Format(
"ALTER TABLE {0} ADD CONSTRAINT {1} UNIQUE ({2})",
tableName,
uc.Key,
string.Join(",", uc.Value.Select(v => v.Item2))));
}
}
リフレクションで問題を解決しました(申し訳ありませんが、人々、VB.Net ...)
最初に、属性UniqueAttributeを定義します。
<AttributeUsage(AttributeTargets.Property, AllowMultiple:=False, Inherited:=True)> _
Public Class UniqueAttribute
Inherits Attribute
End Class
次に、モデルを次のように強化します
<Table("Person")> _
Public Class Person
<Unique()> _
Public Property Username() As String
End Class
最後に、カスタムDatabaseInitializerを作成します(私のバージョンでは、デバッグモードの場合のみDBの変更でDBを再作成します...)。このDatabaseInitializerでは、一意の属性に基づいてインデックスが自動的に作成されます。
Imports System.Data.Entity
Imports System.Reflection
Imports System.Linq
Imports System.ComponentModel.DataAnnotations
Public Class DatabaseInitializer
Implements IDatabaseInitializer(Of DBContext)
Public Sub InitializeDatabase(context As DBContext) Implements IDatabaseInitializer(Of DBContext).InitializeDatabase
Dim t As Type
Dim tableName As String
Dim fieldName As String
If Debugger.IsAttached AndAlso context.Database.Exists AndAlso Not context.Database.CompatibleWithModel(False) Then
context.Database.Delete()
End If
If Not context.Database.Exists Then
context.Database.Create()
For Each pi As PropertyInfo In GetType(DBContext).GetProperties
If pi.PropertyType.IsGenericType AndAlso _
pi.PropertyType.Name.Contains("DbSet") Then
t = pi.PropertyType.GetGenericArguments(0)
tableName = t.GetCustomAttributes(True).OfType(Of TableAttribute).FirstOrDefault.Name
For Each piEntity In t.GetProperties
If piEntity.GetCustomAttributes(True).OfType(Of Model.UniqueAttribute).Any Then
fieldName = piEntity.Name
context.Database.ExecuteSqlCommand("ALTER TABLE " & tableName & " ADD CONSTRAINT con_Unique_" & tableName & "_" & fieldName & " UNIQUE (" & fieldName & ")")
End If
Next
End If
Next
End If
End Sub
End Class
おそらくこれは役立ちます...
EF Code Firstアプローチでは、次の手法を使用して属性ベースの一意制約サポートを実装できます。
マーカー属性を作成する
_[AttributeUsage(AttributeTargets.Property)]
public class UniqueAttribute : System.Attribute { }
_
エンティティで一意にするプロパティにマークを付けます。
_[Unique]
public string EmailAddress { get; set; }
_
データベース初期化子を作成するか、既存の初期化子を使用して一意の制約を作成します
_public class DbInitializer : IDatabaseInitializer<DbContext>
{
public void InitializeDatabase(DbContext db)
{
if (db.Database.Exists() && !db.Database.CompatibleWithModel(false))
{
db.Database.Delete();
}
if (!db.Database.Exists())
{
db.Database.Create();
CreateUniqueIndexes(db);
}
}
private static void CreateUniqueIndexes(DbContext db)
{
var props = from p in typeof(AppDbContext).GetProperties()
where p.PropertyType.IsGenericType
&& p.PropertyType.GetGenericTypeDefinition()
== typeof(DbSet<>)
select p;
foreach (var prop in props)
{
var type = prop.PropertyType.GetGenericArguments()[0];
var fields = from p in type.GetProperties()
where p.GetCustomAttributes(typeof(UniqueAttribute),
true).Any()
select p.Name;
foreach (var field in fields)
{
const string sql = "ALTER TABLE dbo.[{0}] ADD CONSTRAINT"
+ " [UK_dbo.{0}_{1}] UNIQUE ([{1}])";
var command = String.Format(sql, type.Name, field);
db.Database.ExecuteSqlCommand(command);
}
}
}
}
_
スタートアップコードでこの初期化子を使用するようにデータベースコンテキストを設定します(例:main()
またはApplication_Start()
)
_Database.SetInitializer(new DbInitializer());
_
ソリューションはmheymanに似ていますが、複合キーをサポートしないという単純化が行われています。 EF 5.0以降で使用されます。
DbContextクラスのValidateEntityメソッドをオーバーライドする場合、そこにもロジックを配置できます。ここでの利点は、すべてのDbSetに完全にアクセスできることです。以下に例を示します。
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Data.Entity.Validation;
using System.Linq;
namespace MvcEfClient.Models
{
public class Location
{
[Key]
public int LocationId { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
}
public class CommitteeMeetingContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
List<DbValidationError> validationErrors = new List<DbValidationError>();
// Check for duplicate location names
if (entityEntry.Entity is Location)
{
Location location = entityEntry.Entity as Location;
// Select the existing location
var existingLocation = (from l in Locations
where l.Name == location.Name && l.LocationId != location.LocationId
select l).FirstOrDefault();
// If there is an existing location, throw an error
if (existingLocation != null)
{
validationErrors.Add(new DbValidationError("Name", "There is already a location with the name '" + location.Name + "'"));
return new DbEntityValidationResult(entityEntry, validationErrors);
}
}
return base.ValidateEntity(entityEntry, items);
}
public DbSet<Location> Locations { get; set; }
}
}
EF5を使用していて、まだこの質問がある場合は、以下の解決策で解決しました。
私はコードファーストのアプローチを使用しているため、
this.Sql("CREATE UNIQUE NONCLUSTERED INDEX idx_unique_username ON dbo.Users(Username) WHERE Username IS NOT NULL;");
移行スクリプトでは、仕事はうまくいきました。 NULL値も許可します!
一意のプロパティバリデータを使用します。
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) {
var validation_state = base.ValidateEntity(entityEntry, items);
if (entityEntry.Entity is User) {
var entity = (User)entityEntry.Entity;
var set = Users;
//check name unique
if (!(set.Any(any_entity => any_entity.Name == entity.Name))) {} else {
validation_state.ValidationErrors.Add(new DbValidationError("Name", "The Name field must be unique."));
}
}
return validation_state;
}
ValidateEntity
は、同じデータベーストランザクション内では呼び出されません。したがって、データベース内の他のエンティティとの競合状態が存在する可能性があります。 SaveChanges
(したがって、ValidateEntity
)の周りでトランザクションを強制するには、EFをいくらかハックする必要があります。 DBContext
は接続を直接開けませんが、ObjectContext
は開けます。
using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required)) {
((IObjectContextAdapter)data_context).ObjectContext.Connection.Open();
data_context.SaveChanges();
transaction.Complete();
}
Code First構成を使用する場合は、IndexAttributeオブジェクトをColumnAnnotationとして使用し、そのIsUniqueプロパティをtrueに設定することもできます。
例:
var indexAttribute = new IndexAttribute("IX_name", 1) {IsUnique = true};
Property(i => i.Name).HasColumnAnnotation("Index",new IndexAnnotation(indexAttribute));
これにより、[名前]列にIX_nameという名前の一意のインデックスが作成されます。
http://blogs.msdn.com/b/adonet/archive/2014/02/11/ef-6-1-0-beta-1-available.aspx によると、EF 6.1は役立つIndexAttributeがあります。
遅い返事で申し訳ありませんが、私はあなたとそれを剃ることが良いことがわかりました
これについて code projectに投稿しました
一般に、一意のインデックスを生成するためにクラスに付ける属性に依存します
今日私はその問題に直面し、ついにそれを解決することができました。正しいアプローチかどうかはわかりませんが、少なくとも私は続けることができます:
public class Person : IValidatableObject
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var field = new[] { "Name" }; // Must be the same as the property
PFContext db = new PFContext();
Person person = validationContext.ObjectInstance as Person;
var existingPerson = db.Persons.FirstOrDefault(a => a.Name == person.Name);
if (existingPerson != null)
{
yield return new ValidationResult("That name is already in the db", field);
}
}
}
組み込みの注釈がないため、回避策を見つけました。詳細については、このリンクを参照してください https://stackoverflow.com/a/16496291/187311
この質問を読んだ後、 MihkelMüür's 、 Tobias Schittkowski's 、および mheyman's 回答の提案: Entity Frameworkコードプロパティをデータベース列にマップ(CSpaceからSSpace)
私はついにこの答えにたどり着きました。これはスカラーとナビゲーションの両方のプロパティをデータベース列にマップし、属性で指定された特定の順序で一意のインデックスを作成できます。このコードは、SequenceプロパティでUniqueAttributeを実装し、エンティティの一意のキー(プライマリキー以外)を表すEFエンティティクラスプロパティに適用していることを前提としています。
注:このコードは、以前のバージョンでは使用できなかったEntityContainerMapping
を公開するEFバージョン6.1以降に依存しています。
Public Sub InitializeDatabase(context As MyDB) Implements IDatabaseInitializer(Of MyDB).InitializeDatabase
If context.Database.CreateIfNotExists Then
Dim ws = DirectCast(context, System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext.MetadataWorkspace
Dim oSpace = ws.GetItemCollection(Core.Metadata.Edm.DataSpace.OSpace)
Dim entityTypes = oSpace.GetItems(Of EntityType)()
Dim entityContainer = ws.GetItems(Of EntityContainer)(DataSpace.CSpace).Single()
Dim entityMapping = ws.GetItems(Of EntityContainerMapping)(DataSpace.CSSpace).Single.EntitySetMappings
Dim associations = ws.GetItems(Of EntityContainerMapping)(DataSpace.CSSpace).Single.AssociationSetMappings
For Each setType In entityTypes
Dim cSpaceEntitySet = entityContainer.EntitySets.SingleOrDefault( _
Function(t) t.ElementType.Name = setType.Name)
If cSpaceEntitySet Is Nothing Then Continue For ' Derived entities will be skipped
Dim sSpaceEntitySet = entityMapping.Single(Function(t) t.EntitySet Is cSpaceEntitySet)
Dim tableInfo As MappingFragment
If sSpaceEntitySet.EntityTypeMappings.Count = 1 Then
tableInfo = sSpaceEntitySet.EntityTypeMappings.Single.Fragments.Single
Else
' Select only the mapping (esp. PropertyMappings) for the base class
tableInfo = sSpaceEntitySet.EntityTypeMappings.Where(Function(m) m.IsOfEntityTypes.Count _
= 1 AndAlso m.IsOfEntityTypes.Single.Name Is setType.Name).Single().Fragments.Single
End If
Dim tableName = If(tableInfo.StoreEntitySet.Table, tableInfo.StoreEntitySet.Name)
Dim schema = tableInfo.StoreEntitySet.Schema
Dim clrType = Type.GetType(setType.FullName)
Dim uniqueCols As IList(Of String) = Nothing
For Each propMap In tableInfo.PropertyMappings.OfType(Of ScalarPropertyMapping)()
Dim clrProp = clrType.GetProperty(propMap.Property.Name)
If Attribute.GetCustomAttribute(clrProp, GetType(UniqueAttribute)) IsNot Nothing Then
If uniqueCols Is Nothing Then uniqueCols = New List(Of String)
uniqueCols.Add(propMap.Column.Name)
End If
Next
For Each navProp In setType.NavigationProperties
Dim clrProp = clrType.GetProperty(navProp.Name)
If Attribute.GetCustomAttribute(clrProp, GetType(UniqueAttribute)) IsNot Nothing Then
Dim assocMap = associations.SingleOrDefault(Function(a) _
a.AssociationSet.ElementType.FullName = navProp.RelationshipType.FullName)
Dim sProp = assocMap.Conditions.Single
If uniqueCols Is Nothing Then uniqueCols = New List(Of String)
uniqueCols.Add(sProp.Column.Name)
End If
Next
If uniqueCols IsNot Nothing Then
Dim propList = uniqueCols.ToArray()
context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX IX_" & tableName & "_" & String.Join("_", propList) _
& " ON " & schema & "." & tableName & "(" & String.Join(",", propList) & ")")
End If
Next
End If
End Sub