Ràng buộc duy nhất trong mã khung thực thể Đầu tiên


125

Câu hỏi

Có thể xác định một ràng buộc duy nhất cho một thuộc tính bằng cách sử dụng cú pháp trôi chảy hoặc thuộc tính không? Nếu không, cách giải quyết là gì?

Tôi có một lớp người dùng với khóa chính, nhưng tôi muốn chắc chắn rằng địa chỉ email cũng là duy nhất. Điều này có thể mà không cần chỉnh sửa cơ sở dữ liệu trực tiếp?

Giải pháp (dựa trên câu trả lời của 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)");
            }
        }
    }
}

1
Hãy nhớ rằng làm điều này giới hạn ứng dụng của bạn chỉ các cơ sở dữ liệu chấp nhận cú pháp chính xác đó - trong trường hợp này là SQL Server. Nếu bạn chạy ứng dụng của mình với nhà cung cấp Oracle thì nó sẽ thất bại.
DamienG

1
Trong tình huống đó tôi chỉ cần tạo một lớp Trình khởi tạo mới, nhưng đó là một điểm hợp lệ.
kim3er


Có, hiện được hỗ trợ kể từ EF 6.1 .
Evandro Pomatti

Câu trả lời:


61

Theo như tôi có thể nói, hiện tại không có cách nào để làm điều này với Entity Framework. Tuy nhiên, đây không chỉ là vấn đề với các ràng buộc duy nhất ... bạn có thể muốn tạo chỉ mục, kiểm tra các ràng buộc và có thể cả các trình kích hoạt và các cấu trúc khác nữa. Đây là một mẫu đơn giản mà bạn có thể sử dụng với thiết lập đầu tiên mã của mình, mặc dù phải thừa nhận rằng đó không phải là cơ sở dữ liệu:

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

Một tùy chọn khác là nếu mô hình miền của bạn là phương pháp duy nhất để chèn / cập nhật dữ liệu trong cơ sở dữ liệu của bạn, bạn có thể tự thực hiện yêu cầu duy nhất và loại bỏ cơ sở dữ liệu ra khỏi cơ sở dữ liệu. Đây là một giải pháp di động hơn và buộc bạn phải rõ ràng về các quy tắc kinh doanh trong mã của mình, nhưng để cơ sở dữ liệu của bạn mở để dữ liệu không hợp lệ bị phản hồi.


Tôi thích DB của tôi chặt chẽ như một cái trống, logic được nhân rộng trong lớp nghiệp vụ. Câu trả lời của bạn chỉ hoạt động với CTP4 nhưng giúp tôi đi đúng hướng, tôi đã cung cấp một giải pháp tương thích với CTP5 bên dưới câu hỏi ban đầu của tôi. Cảm ơn rất nhiều!
kim3er

23
Trừ khi ứng dụng của bạn là một người dùng, tôi tin rằng một ràng buộc duy nhất là một điều bạn không thể thực thi bằng mã một mình. Bạn có thể giảm đáng kể xác suất vi phạm mã (bằng cách kiểm tra tính duy nhất trước khi gọi SaveChanges()), nhưng vẫn có khả năng một lần chèn / cập nhật khác bị trượt giữa thời điểm kiểm tra tính duy nhất và thời gian SaveChanges(). Vì vậy, tùy thuộc vào mức độ quan trọng của ứng dụng đối với ứng dụng và khả năng vi phạm tính duy nhất, có lẽ tốt nhất là thêm các ràng buộc vào cơ sở dữ liệu.
devuxer

1
Bạn phải kiểm tra tính duy nhất của bạn là một phần của cùng một giao dịch với SaveChanges của bạn. Giả sử cơ sở dữ liệu của bạn tuân thủ axit, bạn hoàn toàn có thể thực thi tính duy nhất theo cách này. Bây giờ liệu EF có cho phép bạn quản lý đúng vòng đời giao dịch theo cách này hay không là một câu hỏi khác.
mattmc3

1
@ mattmc3 Nó phụ thuộc vào mức độ cô lập giao dịch của bạn. Chỉ serializable isolation level(hoặc khóa bảng tùy chỉnh, ugh) thực sự sẽ cho phép bạn đảm bảo tính duy nhất trong mã của bạn. Nhưng hầu hết mọi người không sử dụng serializable isolation levelvì lý do hiệu suất. Mặc định trong MS Sql Server là read committed. Xem loạt 4 phần bắt đầu tại địa chỉ: michaeljswart.com/2010/03/...
Nathan

3
EntityFramework 6.1.0 hiện đã hỗ trợ IndexAttribution mà về cơ bản bạn có thể thêm nó vào đầu các thuộc tính.
sotn

45

Bắt đầu với EF 6.1, giờ đây có thể:

[Index(IsUnique = true)]
public string EmailAddress { get; set; }

Điều này sẽ giúp bạn có một chỉ mục duy nhất thay vì ràng buộc duy nhất, nói đúng ra. Đối với hầu hết các mục đích thực tế, chúng là như nhau .


5
@Dave: chỉ sử dụng cùng tên chỉ mục trên các thuộc tính của các thuộc tính tương ứng ( nguồn ).
Mihkel Müür

Lưu ý điều này tạo ra một chỉ mục duy nhất chứ không phải là một chống chỉ định duy nhất . Mặc dù gần giống nhau nhưng chúng không hoàn toàn giống nhau (theo tôi hiểu, các ràng buộc duy nhất có thể được sử dụng làm mục tiêu của FK). Đối với một ràng buộc, bạn cần phải thực thi SQL.
Richard

(Theo bình luận cuối cùng) Các nguồn khác cho thấy giới hạn này đã bị xóa trong các phiên bản gần đây hơn của SQL Server ... nhưng BOL không hoàn toàn nhất quán.
Richard

@Richard: các ràng buộc duy nhất dựa trên thuộc tính cũng có thể (xem câu trả lời thứ hai của tôi ), mặc dù không nằm ngoài hộp.
Mihkel Müür

1
@exSnake: Kể từ SQL Server 2008, chỉ mục duy nhất hỗ trợ một giá trị NULL duy nhất cho mỗi cột theo mặc định. Trong trường hợp cần hỗ trợ cho nhiều NULL, chỉ số được lọc sẽ cần một câu hỏi khác .
Mihkel Müür

28

Không thực sự liên quan đến điều này nhưng nó có thể giúp ích trong một số trường hợp.

Nếu bạn đang tìm cách tạo một chỉ mục tổng hợp duy nhất trên giả sử 2 cột sẽ đóng vai trò là một ràng buộc cho bảng của bạn, thì kể từ phiên bản 4.3, bạn có thể sử dụng cơ chế di chuyển mới để đạt được nó:

Về cơ bản, bạn cần chèn một cuộc gọi như thế này vào một trong các tập lệnh di chuyển của mình:

CreateIndex("TableName", new string[2] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2");

Một cái gì đó như thế:

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

Rất vui khi thấy EF đã di chuyển theo phong cách Rails. Bây giờ nếu tôi có thể chạy nó trên Mono.
kim3er

2
Không phải bạn cũng nên có một Drop Index trong thủ tục Down () sao? DropIndex("TableName", new[] { "Column1", "Column2" });
Michael Bisbjerg

5

Tôi thực hiện hack hoàn toàn để thực thi SQL khi cơ sở dữ liệu đang được tạo. Tôi tạo DatabaseInitializer của riêng mình và kế thừa từ một trong những công cụ khởi tạo được cung cấp.

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;
    }
}

Đó là nơi duy nhất tôi có thể tìm thấy để nêm vào các câu lệnh SQL của mình.

Đây là từ CTP4. Tôi không biết làm thế nào nó hoạt động trong CTP5.


Cảm ơn Kelly! Tôi đã không biết về xử lý sự kiện đó. Giải pháp cuối cùng của tôi đặt SQL vào phương thức LaunchizeDatabase.
kim3er

5

Chỉ cần cố gắng tìm hiểu xem có cách nào để làm điều này không, chỉ có cách tôi thấy cho đến nay là tự thực thi nó, tôi đã tạo một thuộc tính để được thêm vào mỗi lớp nơi bạn cung cấp tên của các trường bạn cần là duy nhất:

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

Sau đó, trong lớp của tôi, tôi sẽ thêm nó:

[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]
}

Cuối cùng, tôi sẽ thêm một phương thức vào kho lưu trữ của mình, trong phương thức Thêm hoặc khi Lưu Thay đổi như thế này:

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

Không quá hay vì chúng ta cần dựa vào sự phản chiếu nhưng cho đến nay đây là cách tiếp cận phù hợp với tôi! = D


5

Cũng trong 6.1, bạn có thể sử dụng phiên bản cú pháp trôi chảy của câu trả lời của @ mihkelmuur như vậy:

Property(s => s.EmailAddress).HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
    new IndexAttribute("IX_UniqueEmail") { IsUnique = true }));

Phương pháp lưu loát không hoàn hảo IMO nhưng ít nhất là có thể bây giờ.

Thêm deets trên blog Arthur Vickers http://blog.oneunicorn.com/2014/02/15/ef-6-1-creating-indexes-with-indexattribution/


4

Một cách dễ dàng trong cơ bản trực quan bằng cách sử dụng mã di chuyển đầu tiên của mã EF5

Mẫu lớp công

    Public Property SampleId As Integer

    <Required>
    <MinLength(1),MaxLength(200)>

    Public Property Code() As String

Lớp cuối

Thuộc tính MaxLạng rất quan trọng đối với chỉ mục duy nhất của loại chuỗi

Chạy cmd: update-database -verbose

sau khi chạy cmd: thêm di chuyển 1

trong tập tin được tạo

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

Đây thực sự là một câu trả lời thích hợp hơn so với trình khởi tạo DB tùy chỉnh.
Shaun Wilson

4

Tương tự như câu trả lời của Tobias Schittkowski nhưng C # và có khả năng có nhiều lĩnh vực trong các giải pháp.

Để sử dụng điều này, chỉ cần đặt [Duy nhất] trên bất kỳ trường nào bạn muốn là duy nhất. Đối với các chuỗi, bạn sẽ phải làm một cái gì đó như (lưu ý thuộc tính MaxLạng):

[Unique]
[MaxLength(450)] // nvarchar(450) is max allowed to be in a key
public string Name { get; set; }

bởi vì trường chuỗi mặc định là nvarchar (max) và điều đó sẽ không được phép trong khóa.

Đối với nhiều trường trong ràng buộc bạn có thể làm:

[Unique(Name="UniqueValuePairConstraint", Position=1)]
public int Value1 { get; set; }
[Unique(Name="UniqueValuePairConstraint", Position=2)]
public int Value2 { get; set; }

Đầu tiên, UniqueAttribution:

/// <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; }
}

Sau đó, bao gồm một phần mở rộng hữu ích để lấy tên bảng cơ sở dữ liệu từ một loại:

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&lt;Foo&gt;();
    /// </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&lt;Foo&gt;();
    /// </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;
    }
}

Sau đó, trình khởi tạo cơ sở dữ liệu:

/// <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))));
    }
}

2

Tôi đã giải quyết vấn đề bằng cách phản ánh (xin lỗi, folks, VB.Net ...)

Đầu tiên, xác định một thuộc tính UniqueAttribution:

<AttributeUsage(AttributeTargets.Property, AllowMultiple:=False, Inherited:=True)> _
Public Class UniqueAttribute
    Inherits Attribute

End Class

Sau đó, nâng cao mô hình của bạn như thế nào

<Table("Person")> _
Public Class Person

    <Unique()> _
    Public Property Username() As String

End Class

Cuối cùng, tạo một DatabaseInitializer tùy chỉnh (Trong phiên bản của tôi, tôi tạo lại DB trên DB chỉ thay đổi nếu ở chế độ gỡ lỗi ...). Trong DatabaseInitializer này, các chỉ mục được tạo tự động dựa trên các thuộc tính duy nhất:

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

Có lẽ điều này giúp ...


1

Nếu bạn ghi đè phương thức ValidateEntity trong lớp DbContext của mình, bạn cũng có thể đặt logic ở đó. Ưu điểm ở đây là bạn sẽ có quyền truy cập đầy đủ vào tất cả các DbSets của mình. Đây là một ví dụ:

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; }
    }
}

1

Nếu bạn đang sử dụng EF5 và vẫn còn câu hỏi này, giải pháp bên dưới đã giải quyết nó cho tôi.

Tôi đang sử dụng mã tiếp cận đầu tiên, do đó đặt:

this.Sql("CREATE UNIQUE NONCLUSTERED INDEX idx_unique_username ON dbo.Users(Username) WHERE Username IS NOT NULL;");

trong kịch bản di chuyển đã làm công việc tốt. Nó cũng cho phép các giá trị NULL!


1

Với cách tiếp cận First Code của EF, người ta có thể triển khai hỗ trợ ràng buộc duy nhất dựa trên thuộc tính bằng cách sử dụng kỹ thuật sau.

Tạo một thuộc tính đánh dấu

[AttributeUsage(AttributeTargets.Property)]
public class UniqueAttribute : System.Attribute { }

Đánh dấu các thuộc tính bạn muốn là duy nhất trên các thực thể, ví dụ:

[Unique]
public string EmailAddress { get; set; }

Tạo trình khởi tạo cơ sở dữ liệu hoặc sử dụng cơ sở dữ liệu hiện có để tạo các ràng buộc duy nhất

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);
            }
        }
    }   
}

Đặt bối cảnh cơ sở dữ liệu của bạn để sử dụng trình khởi tạo này trong mã khởi động (ví dụ: trong main()hoặc Application_Start())

Database.SetInitializer(new DbInitializer());

Giải pháp tương tự như của mheyman, với việc đơn giản hóa việc không hỗ trợ các phím tổng hợp. Được sử dụng với EF 5.0+.


1

Dung dịch Api trôi chảy:

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();
});

0

Tôi đã đối mặt với vấn đề đó ngày hôm nay và cuối cùng tôi đã có thể giải quyết nó. Tôi không biết có phải là một cách tiếp cận đúng hay không nhưng ít nhất tôi có thể tiếp tục:

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);
        }
    }
}

0

Sử dụng một trình xác nhận tài sản duy nhất.

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;
}

ValidateEntitykhông được gọi trong cùng một giao dịch cơ sở dữ liệu. Do đó, có thể có các điều kiện chạy đua với các thực thể khác trong cơ sở dữ liệu. Bạn phải hack EF phần nào để buộc giao dịch xung quanh SaveChanges(và do đó, ValidateEntity). DBContextkhông thể mở kết nối trực tiếp, nhưng ObjectContextcó thể.

using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required)) {
   ((IObjectContextAdapter)data_context).ObjectContext.Connection.Open();
   data_context.SaveChanges();
   transaction.Complete();
}


0

Sau khi đọc câu hỏi này, tôi đã có câu hỏi của riêng mình trong quá trình cố gắng thực hiện một thuộc tính để chỉ định các thuộc tính là các khóa duy nhất như Mihkel Müür , Tobias Schittkowskicác câu trả lời của mheyman đề xuất: Thuộc tính mã của Entity Framework cho các cột cơ sở dữ liệu (CSpace to SSpace)

Cuối cùng tôi đã đi đến câu trả lời này có thể ánh xạ cả hai thuộc tính vô hướng và điều hướng xuống các cột cơ sở dữ liệu và tạo một chỉ mục duy nhất trong một chuỗi cụ thể được chỉ định trên thuộc tính. Mã này giả định rằng bạn đã triển khai UniqueAttribution với thuộc tính Sequence và áp dụng nó cho các thuộc tính của lớp thực thể EF đại diện cho khóa duy nhất của thực thể (trừ khóa chính).

Lưu ý: Mã này dựa trên phiên bản EF 6.1 (hoặc phiên bản mới hơn) EntityContainerMappingkhông hiển thị trong các phiên bản trước.

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

0

Đối với những người sử dụng cấu hình mã đầu tiên, bạn cũng có thể sử dụng đối tượng IndexAttribution làm Cột và chú ý và đặt thuộc tính IsUnique của nó thành true.

Trong ví dụ:

var indexAttribute = new IndexAttribute("IX_name", 1) {IsUnique = true};

Property(i => i.Name).HasColumnAnnotation("Index",new IndexAnnotation(indexAttribute));

Điều này sẽ tạo một chỉ mục duy nhất có tên IX_name trên cột Tên.


0

Xin lỗi vì trả lời muộn nhưng tôi thấy thật tốt khi shae nó với bạn

Tôi đã đăng về điều này tại dự án mã

Nói chung, nó phụ thuộc vào các thuộc tính mà bạn đặt trên các lớp để tạo các chỉ mục duy nhất của bạn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.