When working with Entity Framework Code First, the database get’s generated for you… Duh!

You have control over several aspects of how this database get’s created.

First off are Data Annotations, such as Required, DefaultValue, ForeignKey, Key, etc.

Second is the Fluent Api which allows you to tap into the creation of the Database and remove all of those attributes off of your data classes.

You can specify the name of the database by passing a Name, or even a connectionString into the base constructor of your DbContext.

 

public class MyContext : DbContext
{
    public MyContext()
        base: ("MyDbName")
    {}
}

Lot’s of different control over the DB.

However, one thing that seems to be left out for now is explicitly naming your Primary Key constraints. By default, they come out looking something like… PK_USERS_FBC301?!?!?!? 

I think this is a default of SQL more than Code First’s fault or anything, but none the less it is kinda nasty.

The only way I have found to fix this problem is to tap into the Initialize method of the Context using either DropCreateDatabaseAlways, DropCreateDatabaseIfModelChanges, or by implementing your own version of IDatabaseInitializer. I did it like this…

 

// Somewhere in Global.asax
Database.SetInitializer(new DbInitializer());

// DbInitializer
public class DbInitializer : IDatabaseInitializer<MyContext>
{
    public void InitializeDatabase(MyDbcontext context)
        {
            if (context.Database.Exists())
            {
                if (!context.Database.CompatibleWithModel(true))
                {
                    string singleUserModeCommand =
                    string.Format("ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE",
                              context.Database.Connection.Database);
                    if (context.Database.Exists())
                    {
                        context.Database.ExecuteSqlCommand(singleUserModeCommand);
                        context.Database.Delete();
                    }
                    context.Database.Create();
                    Seed(context);
                }

            }
            else
            {
                context.Database.Create();
                Seed(context);
            }
        }

        protected void Seed(MyDbContext context)
        {
            FixPrimaryKeyConstraints(context);
        }

        private void FixPrimaryKeyConstraints(MyDbContext context)
        {
            const string sql =
                @"  SET NOCOUNT on
                    DECLARE @cnt int
                    DECLARE @table varchar(128)
                    DECLARE @cmd varchar(500)
                    --create table #rowcount (tablename varchar(128), rowcnt int)
                    DECLARE tables cursor for
                    SELECT table_name from information_schema.tables
	                    WHERE table_type = 'base table'
	                    ORDER BY table_name
                    OPEN tables
                    FETCH NEXT FROM tables into @table
                    WHILE @@fetch_status = 0
                    BEGIN
	                    DECLARE @TableName NVARCHAR(128)
	                    DECLARE @IndexName NVARCHAR(128)
	                    DECLARE @OldName NVARCHAR(128)
	                    DECLARE @NewName NVARCHAR(128)
	                    SELECT  @TableName = @table

	                    SELECT  @IndexName = C.CONSTRAINT_NAME
	                    FROM    INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ,INFORMATION_SCHEMA.KEY_COLUMN_USAGE C
	                    WHERE   pk.TABLE_NAME = @TableName
		                    AND   CONSTRAINT_TYPE = 'PRIMARY KEY'
		                    AND   C.TABLE_NAME = PK.TABLE_NAME
		                    AND   C.CONSTRAINT_NAME = PK.CONSTRAINT_NAME

	                    SELECT  @OldName = @TableName + '.' + @IndexName
	                    SELECT  @NewName = 'PK_' + @TableName
	                    exec sp_rename @OldName, @NewName, 'INDEX'
                      FETCH NEXT FROM tables into @table
                    END
                    CLOSE tables
                    DEALLOCATE tables";

            context.Database.ExecuteSqlCommand(sql);
        }
}

This file does a few things. First it detects changes to the DB, forces a drop if neccessary, then runs the seed.
The seed then calls the FixPrimaryKeyConstraints which loops over all of the tables in the Database and renames them PK_TableName.

Hope you enjoyed this Espresso Tip!

 
  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew#1007

  • Anonymous

    Interesting topic, Jonathan. NHibernate’s schema generation has this limitation too, though it does allow you do specify the foreign key constraint names.

    I’ve found being able to generate the database schema from the domain model mapping to be a great way to stay agile during the development of an initial release of a project, but unless you have a completely non-transcriptional database which you can blow away every time then you eventually need a better strategy (e.g. database migrations). That is to say, I don’t think the concept of ‘Code First” presumes you’re going to have your database generated, but it’s a nice time saver early on.

    One question about your implementation, however. It looks like you’re FixPrimaryKeyConstraints method is going to be executed every time. Wouldn’t you only want to do this when you recreate the database?

    Additionally, here’s a slightly more terse implementation of the SQL if you’re interested:


    DECLARE @PKname NVARCHAR(255),
    @TName NVARCHAR(255),
    @TName2 NVARCHAR(258)
    DECLARE pkcursor CURSOR FOR
    SELECT pk.name, t.name AS tname
    FROM sys.sysobjects AS pk
    INNER JOIN sys.sysobjects AS t ON pk.parent_obj = t.id
    WHERE ( pk.xtype = 'PK' )
    OPEN pkcursor
    FETCH NEXT FROM pkcursor INTO @PKname, @TName
    WHILE @@FETCH_STATUS = 0
    BEGIN
    SET @TName2 = 'PK_' + @TName
    EXEC Sp_rename @PKname, @TName2, 'OBJECT'
    FETCH NEXT FROM pkcursor INTO @PKname, @TName
    END
    CLOSE pkcursor
    DEALLOCATE pkcursor

  • Anonymous

    The code only fires when either, the DB does not exist, or the Model has changed. Thanks for the Sql! The Entity Frameowk Migrations http://blogs.msdn.com/b/adonet/archive/2011/11/29/code-first-migrations-beta-1-released.aspx will help Code First become even better than it already is…

  • http://twitter.com/cleytonferrari Cleyton Ferrari

    Thanks!