4

对于实体框架代码优先中数据模型的目的,我有点困惑。因为如果 EF 仅使用数据模型(包括数据注释和 Fluent API 中的内容),EF 将自动为您从头开始生成一个数据库DbContext.OnModelCreating,我假设数据模型应该完全描述您的数据库的结构,之后您无需修改​​任何基本内容。

但是,我遇到了这个 Codeplex 问题,其中一位 EF Triage Team 成员建议在数据迁移中添加自定义索引,而不是作为数据模型字段或 Fluent API 代码的注释。

但这是否意味着任何从头开始自动生成数据库的人都不会将这些自定义索引添加到他们的数据库中?假设似乎是,一旦您开始使用数据迁移,您将永远不会再从头开始创建数据库。如果您在一个团队中工作并且新的团队成员安装了新的 SQL Server,该怎么办?您是否希望从其他团队成员那里复制数据库?如果您想开始使用新的 DBMS,比如 Postgres,该怎么办?我认为 EF 的一个很酷的事情是它独立于 DBMS,但是如果你不再能够从头开始创建数据库,你就不能再以独立于 DBMS 的方式来做事。

由于我上面概述的原因,在数据迁移中而不是在数据模型中添加自定义索引不是一个坏主意吗?就此而言,在迁移中而不是在数据模型中添加任何数据库结构更改不是一个坏主意吗?

4

2 回答 2

3

EF 代码优先模型是否旨在全面描述数据库的结构?

,它们没有完全描述数据库结构或架构。仍然有一些方法可以使用 EF 完全描述数据库。它们如下:

您可以在 Database 类上使用新的 CTP5ExecuteSqlCommand方法,该方法允许对数据库执行原始 SQL 命令。

SqlCommand为此目的调用方法的最佳位置是在自定义类Seed中已被覆盖的方法内。Initializer例如:

protected override void Seed(EntityMappingContext context)
{
    context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
}

您甚至可以通过这种方式添加唯一约束。这不是一种解决方法,但将在生成数据库时强制执行。

或者

如果您非常需要该属性,那么就在这里

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class IndexAttribute : Attribute
{
    public IndexAttribute(string name, bool unique = false)
    {
        this.Name = name;
        this.IsUnique = unique;
    }

    public string Name { get; private set; }

    public bool IsUnique { get; private set; }
}

在此之后,您将有一个初始化程序,您将在您的OnModelCreating方法中调用它,如下所示:

public class IndexInitializer<T> : IDatabaseInitializer<T> where T : DbContext
{
    private const string CreateIndexQueryTemplate = "CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});";

    public void InitializeDatabase(T context)
    {
        const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;
        Dictionary<IndexAttribute, List<string>> indexes = new Dictionary<IndexAttribute, List<string>>();
        string query = string.Empty;

        foreach (var dataSetProperty in typeof(T).GetProperties(PublicInstance).Where(p => p.PropertyType.Name == typeof(DbSet<>).Name))
        {
            var entityType = dataSetProperty.PropertyType.GetGenericArguments().Single();
            TableAttribute[] tableAttributes = (TableAttribute[])entityType.GetCustomAttributes(typeof(TableAttribute), false);

            indexes.Clear();
            string tableName = tableAttributes.Length != 0 ? tableAttributes[0].Name : dataSetProperty.Name;

            foreach (PropertyInfo property in entityType.GetProperties(PublicInstance))
            {
                IndexAttribute[] indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false);
                NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])property.GetCustomAttributes(typeof(NotMappedAttribute), false);
                if (indexAttributes.Length > 0 && notMappedAttributes.Length == 0)
                {
                    ColumnAttribute[] columnAttributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), false);

                    foreach (IndexAttribute indexAttribute in indexAttributes)
                    {
                        if (!indexes.ContainsKey(indexAttribute))
                        {
                            indexes.Add(indexAttribute, new List<string>());
                        }

                        if (property.PropertyType.IsValueType || property.PropertyType == typeof(string))
                        {
                            string columnName = columnAttributes.Length != 0 ? columnAttributes[0].Name : property.Name;
                            indexes[indexAttribute].Add(columnName);
                        }
                        else
                        {
                            indexes[indexAttribute].Add(property.PropertyType.Name + "_" + GetKeyName(property.PropertyType));
                        }
                    }
                }
            }

            foreach (IndexAttribute indexAttribute in indexes.Keys)
            {
                query += CreateIndexQueryTemplate.Replace("{indexName}", indexAttribute.Name)
                .Replace("{tableName}", tableName)
                .Replace("{columnName}", string.Join(", ", indexes[indexAttribute].ToArray()))
                .Replace("{unique}", indexAttribute.IsUnique ? "UNIQUE" : string.Empty);
            }
        }

        if (context.Database.CreateIfNotExists())
        {
            context.Database.ExecuteSqlCommand(query);
        }
    }

    private string GetKeyName(Type type)
    {
        PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public);
        foreach (PropertyInfo propertyInfo in propertyInfos)
        {
            if (propertyInfo.GetCustomAttribute(typeof(KeyAttribute), true) != null)
            return propertyInfo.Name;
        }
        throw new Exception("No property was found with the attribute Key");
    }
}

然后OnModelCreating在你的 DbContext 中重载

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    Database.SetInitializer(new IndexInitializer<MyContext>());
    base.OnModelCreating(modelBuilder);
}

将索引属性应用于您的实体类型,使用此解决方案,您可以在同一索引中拥有多个字段,只需使用相同的名称和唯一性。

或者

您可以稍后进行迁移。

注意: 我从这里获取了很多代码。

于 2013-02-16T18:07:42.887 回答
0

问题似乎是在中途添加迁移是否有价值,或者这些是否会导致未来不同机器上的数据库初始化出现问题。

创建的初始迁移还包含存在的整个数据模型,因此通过添加迁移(enable-migrations在包管理器控制台中),您实际上是在为您的数据库创建内置机制,以便在以后正确创建其他开发商。

如果您这样做,我建议您修改数据库初始化策略以运行所有现有迁移,以免 EF 启动并使下一个开发人员的数据库不同步。

像这样的东西会起作用:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<YourNamespace.YourDataContext, Migrations.Configuration>());

所以,不,这本质上不会给未来的工作/开发人员带来问题。请记住,迁移只是变成了对数据库执行的有效 SQL……您甚至可以使用脚本模式来输出根据您创建的迁移中的任何内容进行数据库修改所需的 TSQL。

干杯。

于 2013-02-16T18:15:20.827 回答