Question
Est-il possible de définir une contrainte unique sur une propriété en utilisant soit la syntaxe fluide, soit un attribut? Si non, quelles sont les solutions de contournement?
J'ai une classe d'utilisateurs avec une clé primaire, mais j'aimerais m'assurer que l'adresse email est également unique. Est-ce possible sans modifier directement la base de données?
Solution (basée sur la réponse de Matt)
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)");
}
}
}
}
Autant que je sache, il n’ya aucun moyen de faire cela avec Entity Framework pour le moment. Cependant, ce n'est pas simplement un problème avec des contraintes uniques ... vous pouvez aussi créer des index, vérifier des contraintes et éventuellement des déclencheurs et d'autres constructions. Voici un modèle simple que vous pouvez utiliser avec votre configuration premier code, bien que ce ne soit pas une base de données agnostique:
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...");
}
}
}
}
Une autre option est que si votre modèle de domaine est la seule méthode pour insérer/mettre à jour des données dans votre base de données, vous pouvez implémenter l'exigence d'unicité vous-même et laisser la base de données en dehors. Il s’agit d’une solution plus portable qui vous oblige à définir clairement les règles de votre entreprise dans votre code, tout en laissant votre base de données ouverte aux données non valides.
À partir de EF 6.1, il est maintenant possible:
[Index(IsUnique = true)]
public string EmailAddress { get; set; }
Cela vous donnera un index unique au lieu d'une contrainte unique, à proprement parler. Pour la plupart des buts pratiques ils sont les mêmes .
Pas vraiment lié à cela, mais cela pourrait aider dans certains cas.
Si vous souhaitez créer un index composite unique sur deux colonnes, par exemple, qui servira de contrainte pour votre table, vous pouvez utiliser le nouveau mécanisme de migration à partir de la version 4.3:
En gros, vous devez insérer un appel comme celui-ci dans l'un de vos scripts de migration:
CreateIndex("TableName", new string[2] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2");
Quelque chose comme ca:
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" });
}
}
}
Je fais un piratage complet pour obtenir SQL exécuté lors de la création de la base de données. Je crée mon propre DatabaseInitializer et hérite de l'un des initialiseurs fournis.
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;
}
}
C'est le seul endroit que j'ai pu trouver dans mes instructions SQL.
Ceci est de CTP4. Je ne sais pas comment cela fonctionne dans CTP5.
Également en 6.1, vous pouvez utiliser la version à la syntaxe courante de la réponse de @ mihkelmuur comme suit:
Property(s => s.EmailAddress).HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
new IndexAttribute("IX_UniqueEmail") { IsUnique = true }));
La méthode couramment utilisée n’est pas parfaite, mais au moins, c’est maintenant possible.
Plus de détails sur le blog d'Arthur Vickers http://blog.oneunicorn.com/2014/02/15/ef-6-1-creating-indexes-with-indexattribute/
J'essayais simplement de savoir s'il y avait un moyen de le faire, le seul moyen que j'ai trouvé jusqu'ici consistait à l'appliquer moi-même. J'ai créé un attribut à ajouter à chaque classe où vous fournissez le nom des champs dont vous avez besoin pour être unique:
[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);
}
}
Puis dans ma classe je vais l'ajouter:
[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]
}
Enfin, je vais ajouter une méthode dans mon référentiel, dans la méthode Add ou lors de l'enregistrement des modifications, comme ceci:
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");
}
}
}
}
Pas trop gentil car nous devons compter sur la réflexion, mais c’est jusqu’à présent l’approche qui me convient! = D
Un moyen simple d'utiliser Visual Basic avec EF5 Code First Migrations
Échantillon de classe publique
Public Property SampleId As Integer
<Required>
<MinLength(1),MaxLength(200)>
Public Property Code() As String
Classe de fin
L'attribut MaxLength est très important pour l'index unique du type chaîne.
Exécuter cmd: update-database -verbose
après l'exécution de cmd: add-migration 1
dans le fichier généré
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
Semblable à la réponse de Tobias Schittkowski mais C # et a la capacité d'avoir plusieurs champs dans les constrtaints.
Pour l'utiliser, placez simplement un [Unique] sur le champ que vous souhaitez être unique. Pour les chaînes, vous devrez faire quelque chose comme (notez l'attribut MaxLength):
[Unique]
[MaxLength(450)] // nvarchar(450) is max allowed to be in a key
public string Name { get; set; }
car le champ de chaîne par défaut est nvarchar (max) et que cela ne sera pas autorisé dans une clé.
Pour plusieurs champs dans la contrainte, vous pouvez faire:
[Unique(Name="UniqueValuePairConstraint", Position=1)]
public int Value1 { get; set; }
[Unique(Name="UniqueValuePairConstraint", Position=2)]
public int Value2 { get; set; }
Tout d'abord, 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; }
}
Ensuite, incluez une extension utile pour obtenir le nom de la table de la base de données à partir d’un type:
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;
}
}
Ensuite, l'initialiseur de base de données:
/// <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))));
}
}
J'ai résolu le problème par réflexion (désolé, les gens, VB.Net ...)
Premièrement, définissez un attribut UniqueAttribute:
<AttributeUsage(AttributeTargets.Property, AllowMultiple:=False, Inherited:=True)> _
Public Class UniqueAttribute
Inherits Attribute
End Class
Ensuite, améliorez votre modèle comme
<Table("Person")> _
Public Class Person
<Unique()> _
Public Property Username() As String
End Class
Enfin, créez un DatabaseInitializer personnalisé (dans ma version, je ne recrée la base de données que si elle est modifiée en mode débogage ...). Dans ce DatabaseInitializer, les index sont automatiquement créés en fonction des attributs uniques:
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
Peut-être que cela aide ...
Avec l'approche EF Code First, il est possible d'implémenter la prise en charge de contraintes uniques basée sur des attributs en utilisant la technique suivante.
Créer un attribut de marqueur
[AttributeUsage(AttributeTargets.Property)]
public class UniqueAttribute : System.Attribute { }
Marquez les propriétés que vous souhaitez être uniques sur les entités, par exemple.
[Unique]
public string EmailAddress { get; set; }
Créer un initialiseur de base de données ou utiliser un existant pour créer les contraintes uniques
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);
}
}
}
}
Définissez le contexte de votre base de données pour utiliser cet initialiseur dans le code de démarrage (par exemple, dans main()
ou Application_Start()
).
Database.SetInitializer(new DbInitializer());
La solution est similaire à celle de mheyman, avec une simplification consistant à ne pas prendre en charge les clés composites. A utiliser avec EF 5.0+.
Si vous substituez la méthode ValidateEntity dans votre classe DbContext, vous pouvez également y placer la logique. L'avantage ici est que vous aurez un accès complet à tous vos DbSets. Voici un exemple:
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; }
}
}
Si vous utilisez EF5 et avez toujours cette question, la solution ci-dessous l'a résolue pour moi.
J'utilise la première approche de code, mettant donc:
this.Sql("CREATE UNIQUE NONCLUSTERED INDEX idx_unique_username ON dbo.Users(Username) WHERE Username IS NOT NULL;");
dans le script de migration a bien fait le travail. Il permet également des valeurs NULL!
Utilisez un validateur de propriété unique.
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
n'est pas appelé dans la même transaction de base de données. Par conséquent, il peut exister des conditions de concurrence avec d'autres entités de la base de données. Vous devez modifier quelque peu EF pour forcer une transaction autour de SaveChanges
(et donc, ValidateEntity
). DBContext
ne peut pas ouvrir la connexion directement, mais ObjectContext
peut le faire.
using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required)) {
((IObjectContextAdapter)data_context).ObjectContext.Connection.Open();
data_context.SaveChanges();
transaction.Complete();
}
Pour ceux qui utilisent les premières configurations de code, vous pouvez également utiliser l'objet IndexAttribute en tant que ColumnAnnotation et définir sa propriété IsUnique sur true.
Par exemple:
var indexAttribute = new IndexAttribute("IX_name", 1) {IsUnique = true};
Property(i => i.Name).HasColumnAnnotation("Index",new IndexAnnotation(indexAttribute));
Cela créera un index unique nommé IX_name dans la colonne Name.
Selon http://blogs.msdn.com/b/adonet/archive/2014/02/11/ef-6-1-0-beta-1-available.aspx , EF 6.1 sera avoir un IndexAttribute pour nous aider.
Désolé pour la réponse tardive mais j'ai trouvé bon de le partager avec vous
J'ai posté à ce sujet à code project
En général, cela dépend des attributs que vous mettez sur les classes pour générer vos index uniques
J'ai fait face à ce problème aujourd'hui et j'ai finalement réussi à le résoudre. Je ne sais pas si c'est une bonne approche mais au moins, je peux continuer:
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);
}
}
}
Comme il n'y a pas d'annotation intégrée, j'ai trouvé une solution de contournement. Veuillez vous référer à ce lien pour plus d'informations https://stackoverflow.com/a/16496291/187311
Après avoir lu cette question, j’avais ma propre question en train de tenter d’implémenter un attribut pour désigner des propriétés comme clés uniques comme de Mihkel Müür , de Tobias Schittkowski et de mheyman réponses suggèrent: Mappe les propriétés du code Entity Framework aux colonnes de la base de données (CSpace à SSpace)
Je suis finalement arrivé à cette réponse qui peut mapper les propriétés scalaires et de navigation vers les colonnes de la base de données et créer un index unique dans une séquence spécifique désignée sur l'attribut. Ce code suppose que vous avez implémenté un UniqueAttribute avec une propriété Sequence et que vous l'avez appliqué à des propriétés de classe d'entité EF qui devraient représenter la clé unique de l'entité (autre que la clé primaire).
Remarque: Ce code repose sur la version 6.1 d'EF (ou ultérieure) qui expose EntityContainerMapping
non disponible dans les versions antérieures.
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