2013-02-16 36 views
4

对于实体框架中代码优先的数据模型的用途,我有点困惑。因为EF会自动从头开始为你生成一个数据库,如果它只用了数据模型(包括数据注释和Fluent API的东西,在DbContext.OnModelCreating),它就不存在了,我假定数据模型应该完全描述你的数据库的结构,并且在那之后你不需要修改任何基本的东西。EF代码优先模型是否旨在完整地描述数据库的结构?

但是,我遇到了this Codeplex issue,其中一位EF Triage团队成员建议在数据迁移中添加自定义索引,但不能将其作为数据模型字段的注释或Fluent API代码。

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

由于我上面概述的原因,不会在数据迁移中添加自定义索引,但不会在数据模型中添加自定义索引是个坏主意?对于这个问题,不会在迁移中添加任何数据库结构更改,但不会在数据模型中添加更改是个坏主意?

+0

当然不是。 SQL Server数据库首先提供的不仅仅是EF代码的通用功能。 – usr 2013-02-16 18:25:13

回答

3

是EF代码优先模型,是为了完全描述数据库的结构吗?

没有,他们并不完全描述数据库结构或schema.Still有方法,使数据库使用EF充分说明。它们如下:

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

最好的地方调用SqlCommand方法用于此目的是Seed方法已经在自定义Initializer类中重写内。例如:

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

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

OR

如果你急需的属性,然后在这里它去

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

然后在你的DbContext

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

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

OR

你可以做迁移以后。

说明: 我从here采取了很多此代码。

+0

通常这里的建议是可靠的。所以,从我+1。林好奇你为什么建议在一个选项种子覆盖作为创建索引的好地方。考虑:http://msdn.microsoft.com/en-us/data/jj591621一种基于MigrateDatabaseToLatestVersion的方法?将尝试每次构建索引。这是一个便捷的方式。但它对生产性数据库有影响。因此,您的最后一点“或”您可以稍后进行迁移......大概您意指包管理器控制台中高度管理的方法Update-Database命令。 – 2013-02-17 11:46:33

+0

我不明白你的意思是“稍后进行迁移”。一旦EF创建了最新的数据库模型,应用迁移就没有意义了。 – Jez 2013-03-19 11:04:44

+0

@Jez为什么根据你的理解没有意义?否则,你将不得不摧毁你的数据库并创建新的...我认为你不会喜欢... – 2013-03-22 18:24:36

0

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

创建的初始迁移还包含整个数据模型(因为它存在),所以通过添加迁移(包管理器控制台中的enable-migrations),实际上是为数据库创建内置机制为其他开发者创造了道路。

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

像这样的工作:

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

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

干杯。