EF Code First中的唯一键

63

我在我的项目中有一个下面的模型

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

我想将Title作为唯一键,我在Google上搜索了解决方案,但没有找到任何内容。请问有人可以建议我如何做吗?

5个回答

111

不幸的是,你无法在代码优先中将其定义为唯一键,因为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; }

我使用MVC 3和EF 4,但是代码无法识别context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX IX_Category_Title ON Categories (Title)"); 这是版本问题还是其他问题? - Saeid
1
@Saeid:这是针对DbContext API(EFv4.1)的。在EFv4中没有数据库初始化程序。ObjectContext API提供了自己的方法来直接执行SQL - ExecuteStoreCommand - Ladislav Mrnka
1
另外一个很好的方法是添加默认约束(例如GETDATE()等)。 - John Laffoon
2
种子被多次执行 - 这不会因为索引(或函数/存储过程/或其他任何内容)已经存在于数据库中而导致错误吗? - codeputer
自那时以来,这已经发生了变化。 - Elan Hasson
显示剩余4条评论

22

首先创建自定义属性类:

[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 + ")");
                        }
                    }
                }
            }
        }
    }
}

在Global.asax.cs文件的Application_Start方法中添加初始化程序。
System.Data.Entity.Database.SetInitializer<MyApp.Models.DomainModels.myEntities>(new MyApp.Models.DomainModels.myEntities.Initializer());

就是这样。基于https://dev59.com/h2855IYBdhLWcg3wUSgW#7426773上的VB代码。


1
几个更正。1. 在ExecuteSqlCommand期间,tableName应该用括号括起来。2. 如果您使用的是非复数名称,请使用else { tableName = t.Name }。 - James

2

这里是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

哦,拜托了——添加一个简洁的VB版本有什么问题呢?这是为VB用户提供相同问题解决方案的SO的目的不是吗?此外,正如所指出的那样,实现方式有些不同。 - GilShalit

0

0
我创建了这个类(它是从另一个Stackoverflow答案 - 执行大型SQL脚本(带有GO命令)进行了增强),它允许我将SQL脚本放入一个目录中,并在每次需要时执行它们(种子或迁移)。我不会在部署到生产环境后保持它处于开放状态,但在开发过程中,它使得每次重新创建数据库时应用脚本变得容易。
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 解决方案的样子:

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接