Уникальный ключ с кодом EF первым

В моем проекте есть следующая модель

public class Category
{   
    public Guid ID { get; set; }
    [Required(ErrorMessage = "Title cannot be empty")]
    public string Title { get; set; }
}

и я пытаюсь сделать Title уникальным ключом, я искал решение в Google, но не смог его найти. Может ли кто-нибудь подсказать мне, как это сделать, пожалуйста?


person Prashant Cholachagudda    schedule 18.04.2011    source источник


Ответы (5)


К сожалению, вы не можете сначала определить его как уникальный ключ в коде, потому что EF вообще не поддерживает уникальные ключи (мы надеемся, что это планируется в следующем крупном выпуске). Что вы можете сделать, так это создать собственный инициализатор базы данных и добавить уникальный индекс вручную, вызвав команду SQL:

public class MyInitializer : CreateDatabaseIfNotExists<MyContext>
{
  protected override void Seed(MyContext context)
  {
    context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX IX_Category_Title ON Categories (Title)");
  }
}

И вы должны установить этот инициализатор в начальной загрузке вашего приложения.

Database.SetInitializer<MyContext>(new MyInitializer());

Изменить

Теперь (начиная с EF 6.1) вы можете легко иметь уникальные ограничения,

[Index("TitleIndex", IsUnique = true)]
 public string Title { get; set; }
person Ladislav Mrnka    schedule 18.04.2011
comment
Я работаю с MVC 3 и EF 4, и код не распознает ExecuteSqlCommand в context.Database.ExecuteSqlCommand (CREATE UNIQUE INDEX IX_Category_Title ON Categories (Title)); это про версию или что-то в этом роде? - person Saeid; 16.11.2011
comment
@Saeid: это для DbContext API (EFv4.1). В EFv4 нет инициализатора базы данных. ObjectContext API предлагает свои собственные методы для прямого выполнения SQL - ExecuteStoreCommand. - person Ladislav Mrnka; 16.11.2011
comment
Также отличный способ добавить ограничения по умолчанию (например, GETDATE () и т. Д.) - person John Laffoon; 28.09.2012
comment
Seed выполняется несколько раз - не будет ли это ошибкой, поскольку индекс (или функция / хранимая процедура / или что-то еще) уже существует в базе данных? - person codeputer; 23.02.2013
comment
@codputer: в этом случае Seed выполняется только один раз, потому что он не использует миграции. В случае миграции вы можете создать индекс прямо в Up методе. - person Ladislav Mrnka; 25.02.2013
comment
+1: хороший ответ, это помогло мне решить проблему. Но мне нужно запустить это только один раз. И кажется, что моя инициализация происходит постоянно - т.е. даже когда модель не меняется. Я ТАК поищу средство. - person IAbstract; 17.04.2013
comment
С тех пор ситуация изменилась. - person Elan Hasson; 06.10.2014
comment
Когда вы помещаете индекс в строку, аннотируя ее с помощью такого атрибута, как [Index (TitleIndex, IsUnique = true)], вам ТАКЖЕ необходимо ограничить длину строки. Не хочу, чтобы SQL Server пытался проиндексировать тип VarChar (Max). Так что установите и максимальную длину. Например: [MaxLength (255)] В противном случае он может вызвать исключение и сообщить вам, что вам не разрешено помещать индекс для этого свойства, потому что это неправильный тип. Вы можете поместить индекс в строку, если она не слишком длинная. - person user1040323; 20.09.2018
comment
В сети 5 он говорит, что используется только для класса [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]. Как использовать его для струны? благодарить - person Trương Quốc Khánh; 25.12.2020

Сначала создайте класс настраиваемого атрибута:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UniqueAttribute : ValidationAttribute
{
   public override Boolean IsValid(Object value)
    {
        // constraint implemented on database
        return true;
    }
}

Затем добавьте в свои классы:

public class Email
{
    [Key]
    public int EmailID { get; set; }

    public int PersonId { get; set; }

    [Unique]
    [Required]
    [MaxLength(100)]
    public string EmailAddress { get; set; }
    public virtual bool IsDefault { get; set; }
    public virtual Boolean IsApprovedForLogin { get; set; }
    public virtual String ConfirmationToken { get; set; }

    [ForeignKey("PersonId")]
    public virtual Person Person { get; set; }
}

Затем добавьте инициализатор в свой DbContext:

public class Initializer : IDatabaseInitializer<myEntities>
{
    public void InitializeDatabase(myEntities context)
    {
        if (System.Diagnostics.Debugger.IsAttached && context.Database.Exists() && !context.Database.CompatibleWithModel(false))
        {
            context.Database.Delete();
        }

        if (!context.Database.Exists())
        {
            context.Database.Create();

            var contextObject = context as System.Object;
            var contextType = contextObject.GetType();
            var properties = contextType.GetProperties();
            System.Type t = null;
            string tableName = null;
            string fieldName = null;
            foreach (var pi in properties)
            {
                if (pi.PropertyType.IsGenericType && pi.PropertyType.Name.Contains("DbSet"))
                {
                    t = pi.PropertyType.GetGenericArguments()[0];

                    var mytableName = t.GetCustomAttributes(typeof(TableAttribute), true);
                    if (mytableName.Length > 0)
                    {
                        TableAttribute mytable = mytableName[0] as TableAttribute;
                        tableName = mytable.Name;
                    }
                    else
                    {
                        tableName = pi.Name;
                    }

                    foreach (var piEntity in t.GetProperties())
                    {
                        if (piEntity.GetCustomAttributes(typeof(UniqueAttribute), true).Length > 0)
                        {
                            fieldName = piEntity.Name;
                            context.Database.ExecuteSqlCommand("ALTER TABLE " + tableName + " ADD CONSTRAINT con_Unique_" + tableName + "_" + fieldName + " UNIQUE (" + fieldName + ")");
                        }
                    }
                }
            }
        }
    }
}

И, наконец, добавьте инициализатор в Application_Start внутри Global.asax.cs

System.Data.Entity.Database.SetInitializer<MyApp.Models.DomainModels.myEntities>(new MyApp.Models.DomainModels.myEntities.Initializer());

Вот и все. на основе кода vb на странице https://stackoverflow.com/a/7426773

person Joao Leme    schedule 12.05.2012
comment
Пара исправлений. 1. tableName следует заключать в квадратные скобки во время ExecuteSqlCommand 2. если вы используете имена без множественного числа, используйте else {tableName = t.Name} - person James; 26.10.2012

Вот версия VB.Net - обратите внимание на реализацию дженериков, которая немного отличается на уровне класса.

Public Class MyInitializer(Of T As DbContext)
    Inherits CreateDatabaseIfNotExists(Of T)
    Protected Overrides Sub Seed(context As T)
        context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX IX_Category_Title ON Categories (Title)")
    End Sub
End Class
person GilShalit    schedule 12.11.2011
comment
да ладно, что плохого в добавлении краткой версии VB для пользователей vb с такой же проблемой? Разве не в этом суть SO - предоставление ресурса не только для оригинального плаката? Кроме того, как уже отмечалось, реализация несколько отличается. - person GilShalit; 23.06.2014

Я создаю этот класс (который улучшен на основе другого ответа Stackoverflow - Выполнить большой сценарий SQL (с командами GO)), что позволяет мне помещать сценарии SQL в каталог и выполнять их все каждый раз, когда они требуются (Seed или Миграция). Я не собираюсь оставлять это открытым после развертывания в производственной среде, но во время разработки это упрощает применение сценариев при каждом воссоздании БД.

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//dll Microsoft.SqlServer.Smo
//dll Microsoft.SqlServer.Management.Sdk.Sfc
//dll Microsoft.SqlServer.ConnectionInfo
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Monitor.Common;

namespace MonitorDB.DataLayer.Migrations
{
  public class ExecuteSQLScripts :Monitor.Common.ExceptionHandling
  {
    public ExecuteSQLScripts()
    {
}

public bool ExecuteScriptsInDirectory(DBContext.SolArcMsgMonitorContext context, string scriptDirectory)
{
  bool Result = false;
  try
  {
    SqlConnection connection = new SqlConnection(context.Database.Connection.ConnectionString);
    Server server = new Server(new ServerConnection(connection));

    DirectoryInfo di = new DirectoryInfo(scriptDirectory);
    FileInfo[] rgFiles = di.GetFiles("*.sql");
    foreach (FileInfo fi in rgFiles)
    {

      FileInfo fileInfo = new FileInfo(fi.FullName);
      string script = fileInfo.OpenText().ReadToEnd();

      server.ConnectionContext.ExecuteNonQuery(script);
    }
    Result = true;
  }
  catch (Exception ex)
  {
    CatchException("ExecuteScriptsInDirectory", ex);
  }
  return Result;
}

} }

Вот как выглядит решение VS:

person codeputer    schedule 26.02.2013

Я нашел это решение, которое, хотя и не создает уникальный ключ на уровне SQL, но использует проверку DataAnnotations, проверьте это:

http://blogs.microsoft.co.il/blogs/shimmy/archive/2012/01/23/validationattribute-that-validates-a-unique-field-against-its-fellow-rows-in-the-database.aspx

person Shimmy Weitzhandler    schedule 14.04.2013