Уникальное ограничение в коде Entity Framework First
Вопрос
Можно ли определить уникальное ограничение для свойства, используя либо свободный синтаксис, либо атрибут? Если нет, каковы обходные пути?
У меня есть класс пользователя с первичным ключом, но я хотел бы убедиться, что адрес электронной почты также является уникальным. Возможно ли это без непосредственного редактирования базы данных?
Решение (основано на ответе Мэтта)
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)");
}
}
}
}
19 ответов
Насколько я могу судить, в настоящее время нет способа сделать это с 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, вы можете использовать новый механизм миграции для достижения этого:
- http://msdn.microsoft.com/en-us/library/hh770484(v=vs.103).aspx
- http://blogs.msdn.com/b/adonet/archive/2012/02/09/ef-4-3-code-based-migrations-walkthrough.aspx
По сути, вам нужно вставить такой вызов в один из ваших сценариев миграции:
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 и наследую от одного из предоставленных инициализаторов.
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 следующим образом:
Property(s => s.EmailAddress).HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
new IndexAttribute("IX_UniqueEmail") { IsUnique = true }));
Свободный метод не является идеальным ИМО, но, по крайней мере, сейчас это возможно.
Больше блогов в блоге Артура Викерса 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
Простой способ в Visual Basic с помощью 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-igration 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
Аналогичен ответу Тобиаса Щитковски, но на C# и имеет возможность иметь несколько полей в константах.
Чтобы использовать это, просто поместите [Уникальный] в любое поле, которое вы хотите быть уникальным. Для строк вам нужно будет сделать что-то вроде (обратите внимание на атрибут 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; }
Во-первых, уникальный атрибут:
/// <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 (в моей версии я воссоздаю изменения БД в БД только в режиме отладки...). В этом 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
Возможно, это помогает...
Решение Fluent Api:
modelBuilder.Entity<User>(entity =>
{
entity.HasIndex(e => e.UserId)
.HasName("IX_User")
.IsUnique();
entity.HasAlternateKey(u => u.Email);
entity.HasIndex(e => e.Email)
.HasName("IX_Email")
.IsUnique();
});
Если вы используете EF5 и у вас все еще есть этот вопрос, решение ниже решило его для меня.
Я использую код первый подход, поэтому ставлю:
this.Sql("CREATE UNIQUE NONCLUSTERED INDEX idx_unique_username ON dbo.Users(Username) WHERE Username IS NOT NULL;");
в скрипте миграции все сделал хорошо. Это также позволяет значения NULL!
С подходом 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+.
Если вы переопределите метод ValidateEntity в своем классе DbContext, вы также можете поместить логику туда. Преимущество здесь в том, что у вас будет полный доступ ко всем вашим DbSets. Вот пример:
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; }
}
}
Я столкнулся с этой проблемой сегодня и, наконец, я смог ее решить. Я не знаю, правильный ли это подход, но, по крайней мере, я могу продолжать:
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);
}
}
}
После прочтения этого вопроса у меня возник собственный вопрос, когда я пытался реализовать атрибут для обозначения свойств в качестве уникальных ключей, таких как ответы Михеля Мюра, Тобиаса Щитковского и Мхеймана: " Сопоставить свойства кода Entity Framework со столбцами базы данных (от CSpace до SSpace)
Наконец, я пришел к этому ответу, который может сопоставить скалярные и навигационные свойства со столбцами базы данных и создать уникальный индекс в определенной последовательности, указанной в атрибуте. В этом коде предполагается, что вы реализовали UniqueAttribute со свойством Sequence и применили его к свойствам класса сущности EF, которые должны представлять уникальный ключ сущности (отличный от первичного ключа).
Примечание. Этот код основан на версии EF 6.1 (или более поздней), которая предоставляет EntityContainerMapping
недоступно в предыдущих версиях.
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
Для тех, кто использует конфигурации с первым кодом, вы также можете использовать объект 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, чтобы помочь нам,
Поскольку встроенной аннотации нет, я нашел обходной путь. Пожалуйста, обратитесь к этой ссылке для получения дополнительной информации /questions/44963493/unikalnyie-klyuchi-v-entity-framework-4/44963532#44963532
Используйте уникальный валидатор свойства.
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
не вызывается в одной и той же транзакции базы данных. Следовательно, могут быть расы с другими объектами в базе данных. Вы должны взломать EF несколько, чтобы заставить транзакцию вокруг SaveChanges
(и поэтому, ValidateEntity
). DBContext
не может открыть соединение напрямую, но ObjectContext
Можно.
using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required)) {
((IObjectContextAdapter)data_context).ObjectContext.Connection.Open();
data_context.SaveChanges();
transaction.Complete();
}
Извините за поздний ответ, но мне было приятно поболтать с вами
Я написал об этом в проекте кода
В целом, это зависит от атрибутов, которые вы помещаете в классы для генерации ваших уникальных индексов.