1

I've got an layered design in my webapplication with an generic service and repository construction. I want to use code first so I can code my entities and then create/update my database. However I can't seem to get it working. I'm new to the code first concept with generating the database and seeding it, so it could very well be something obvious;-)

My application design is as following.

  • Website
  • Website.DAL
  • Website.TESTS (not used yet)

The website.DAL project contains my generic service and repository, as well as DataContext and my entities. The idea is that I can instantiate an generics ervice inside my controller of an certain entity. The service can contain more functions to do calculations etc.. and the repository is only intended to handle the CRUD-actions. The website project ofcourse has an reference to the Website.DAL project and also EF5 is installed in both project through NuGet.

The DataContext looks like this:

using System.Data.Entity;
using System.Web;
using Website.DAL.Entities;

namespace Website.DAL.Model
{
    public class MyContext : DbContext
    {
        public IDbSet<Project> Projects { get; set; }
        public IDbSet<Portfolio> Portfolios { get; set; }

        /// <summary>
        /// The constructor, we provide the connectionstring to be used to it's base class.
        /// </summary>
        public MyContext()
            : base("MyConnectionstringName")
        {
            //MyContext.Database.Initialize(true);

            //if (HttpContext.Current.IsDebuggingEnabled)
            //{
            //    //Database.SetInitializer<MyContext>(new DatabaseInitializer());
            //    Database.SetInitializer<MyContext>(null);
            //}
            //else
            //{
            //    //Database.SetInitializer<MyContext>(new CreateInitializer());
            //}
        }

        static MyContext()
        {
            Database.SetInitializer<MyContext>(null);
        }

        /// <summary>
        /// This method prevents the plurarization of table names
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
        }

        //public void Seed(MyContextContext)
        //{
        //    // Normal seeding goes here

        //    Context.SaveChanges();
        //}
    }
}

I've also created an DatabaseInitialiser class which is currently empty, but the idea ofcourse is to make it seed my database when it's created or updated.

The DatabaseInitialiser class looks like this:

using System.Data.Entity;
using Website.DAL.Model;

namespace Website.DAL
{
    public class DatabaseInitializer : DropCreateDatabaseIfModelChanges<MyContext>
    {
        public DatabaseInitializer()
        {

        }             

        protected override void Seed(MyContextcontext)
        {
            //TODO: Implement code to seed database

            //Save all changes
            context.SaveChanges();
        }
    }
}

Since the GenericService isn't relevant to the question i'll leave it out since it's currenlty only making direct calls to the repository without any specific business intelligence.

The generic repository used looks like this. Things still need to be improved here, but it works for now.

GenericRepository

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Website.DAL.Model;
using Website.DAL.RepositoryInterfaces;

namespace Website.DAL.Repositories
{

    public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
    {
        #region Implementation of IRepository<TEntity>

        //protected SceObjectContext DataContext;
        //protected ObjectContext DataContext;
        private MyContext _context;
        //private IObjectSet<T> ObjectSet;
        private DbSet<TEntity> _dbSet;

        public GenericRepository()
        {
            //DataContext = SceObjectContext.Current;
            //DataContext = new ObjectContext("dbConnection");
            _context = new MyContext();
            //ObjectSet = DataContext.CreateObjectSet<T>();
            _dbSet = _context.Set<TEntity>();
        }

        /// <summary>
        /// Inserts a new object into the database
        /// </summary>
        /// <param name="entity">The entity to insert</param>
        public void Insert(TEntity entity)
        {
            //var entitySetName = GetEntitySetName(typeof(T));
            //DataContext.AddObject(entitySetName, entity);
            _dbSet.Add(entity);
        }

        /// <summary>
        /// Deletes the specified entity from the database
        /// </summary>
        /// <param name="entity">The object to delete</param>
        public void Delete(TEntity entity)
        {
            //DataContext.DeleteObject(entity);
            if (_context.Entry(entity).State == System.Data.EntityState.Detached)
            {
                _dbSet.Attach(entity);
            }

            _dbSet.Remove(entity);
        }

        /// <summary>
        /// Saves all pending chances to the database
        /// </summary>
        public void Save()
        {
            _context.SaveChanges();
        }

        /// <summary>
        /// Retrieves the first object matching the specified query.
        /// </summary>
        /// <param name="where">The where condition to use</param>
        /// <returns>The first matching object, null of none found</returns>
        public TEntity First(Expression<Func<TEntity, bool>> @where)
        {
            return _dbSet.FirstOrDefault(where);
        }

        /// <summary>
        /// Gets a list of all objects
        /// </summary>
        /// <returns>An strong typed list of objects</returns>
        public IEnumerable<TEntity> GetAll()
        {
            return _dbSet.AsEnumerable<TEntity>();
        }

        /// <summary>
        /// Returns ans iQueryable of the matching type
        /// </summary>
        /// <returns>iQueryable</returns>
        public IQueryable<TEntity> AsQueryable()
        {
            return _dbSet.AsQueryable();
        }
        #endregion
    }
}

I've got two entities which i've created. Portfolio is one of them which is displayed belowd. Project is the second one which just is an simple POCO class with an Id and a few properties.

Portfolio.cs

public class Portfolio
{
    [Key]
    public Guid Id { get; set; }
    public String Name { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public bool IsPublished { get; set; }

    public virtual ICollection<Project> Projects { get; set; }
}

All the classes above are maintained in my Website.DAL project. The Global.asax in my Website project contains some code calling the initialiser which, as far as I know, should make sure the seeding can be done in the near future and maintain the database table.

Global.asax

try
{
    //Regenerate database if needed.
    //Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>());
    //Database.SetInitializer(new DatabaseInitializer());
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BorloContext>());
    //Database.SetInitializer<MyContext>(new MigrateDatabaseToLatestVersion<MyContext>());
}
catch (Exception)
{
    throw;
}

Just for the sake of it i've got a piece of code in my HomeController which should get an id of all portfolio items.

var list = _portfolioService.GetAll();

The following things are happening when debugging through the code;

  • Initialiser code in Global.asax passes.
  • Constructor for databaseinitialiser is called
  • Code in homecontroller doesn't throw an exception. But when adding an watch to the call to '_portfolioService.GetAll();' i'm getting the following exception;

enter image description here

I can't figure out what is going wrong here. Ofcourse the exception isn't good but I can't view the inner exception since it isn't giving me one. What could I possibly do to get this working? Or isn't the thing I want to achieve not possible and should the DAL-layer be merged into the website to get the code generation working?

UPDATE 1:

Okay, i've changed the following line in my context

Database.SetInitializer<MyContext>(null);

To

Database.SetInitializer<MyContext>(new DatabaseInitializer());

Now i'm getting this error and stacktrace when debugging the '_portfolioService.GetAll();' call in the homecontroller

Error:

Model compatibility cannot be checked because the database does not contain model metadata. Model compatibility can only be checked for databases created using Code First or Code First Migrations.

bij System.Data.Entity.Internal.ModelCompatibilityChecker.CompatibleWithModel(InternalContext internalContext, ModelHashCalculator modelHashCalculator, Boolean throwIfNoMetadata)
   bij System.Data.Entity.Internal.InternalContext.CompatibleWithModel(Boolean throwIfNoMetadata)
   bij System.Data.Entity.Database.CompatibleWithModel(Boolean throwIfNoMetadata)
   bij System.Data.Entity.DropCreateDatabaseIfModelChanges`1.InitializeDatabase(TContext context)
   bij System.Data.Entity.Database.<>c__DisplayClass2`1.<SetInitializerInternal>b__0(DbContext c)
   bij System.Data.Entity.Internal.InternalContext.<>c__DisplayClass8.<PerformDatabaseInitialization>b__6()
   bij System.Data.Entity.Internal.InternalContext.PerformInitializationAction(Action action)
   bij System.Data.Entity.Internal.InternalContext.PerformDatabaseInitialization()
   bij System.Data.Entity.Internal.LazyInternalContext.<InitializeDatabase>b__4(InternalContext c)
   bij System.Data.Entity.Internal.RetryAction`1.PerformAction(TInput input)
   bij System.Data.Entity.Internal.LazyInternalContext.InitializeDatabaseAction(Action`1 action)
   bij System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
   bij System.Data.Entity.Internal.InternalContext.Initialize()
   bij System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   bij System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
   bij System.Data.Entity.Internal.Linq.InternalSet`1.GetEnumerator()
   bij System.Data.Entity.Infrastructure.DbQuery`1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator()
   bij System.Linq.SystemCore_EnumerableDebugView`1.get_Items()
Rob
  • 6,731
  • 12
  • 52
  • 90
  • HA! "**An error occured while executing the command definition...**" You don't make it easy to answer your question, at least not for non-Dutch speakers :) I had to type the Dutch message from your screenshot into the google translator. Is there something interesting following in the message? It is cut off in the screenshot... – Slauma Jul 08 '13 at 18:30
  • @Slauma, sorry. Will translate the error and update the question:) – Rob Jul 08 '13 at 18:34
  • @Slauma, see comments after Update 1:) – Rob Jul 08 '13 at 18:41
  • I actually meant the error before your "UPDATE 1". For the error in your update: It means that the DB hasn't been created with code-first (or not successfully created, maybe due to the first error). Can you delete the database manually (in sql server management studio for example) and then run again? The initializer should work then. – Slauma Jul 08 '13 at 18:47
  • @Slauma. The error before 'update 1' only say the following: There was an error while executing the command definition. See the inner exception for details. I''ll try dropping the database. – Rob Jul 08 '13 at 18:50
  • @Slauma, Got no management studio on this machine. SO i'll also check tomorrow on a different machine. I Was able to drop the database, VS2012 is giving me connection errors now. However when I run the code i'm still getting the same error. Strange enough the context seems the be having a valid connectionstring, but the database isn't created. – Rob Jul 08 '13 at 19:09
  • @Slauma, database was dropped. Code First unfortunately didn't recreate it for me, so I had to do it manually. Also under a different name during an server issue. I keep getting the same error, it keeps saying that the model can't be validated against the database, which is the same error as before. – Rob Jul 09 '13 at 11:01
  • 1
    I suggest that you open a new question only about why code-first didn't create the DB. If you use the `DropCreateDatabaseIfModelChanges` initializer and the DB does not exist EF **must** create the DB. Something is wrong with your setup (maybe connection string or access rights). It's clear that you get the same error when you create the DB manually because it won't contain the metadata that the initializer would insert. – Slauma Jul 09 '13 at 11:22
  • Okay, will do so. thanks for your help so far!:) – Rob Jul 09 '13 at 13:49
  • New question created here: http://stackoverflow.com/questions/17601459/entity-framework-5-code-first-nog-creating-database – Rob Jul 11 '13 at 19:09

1 Answers1

0

Since no other solution came by I decided to change my approach.

I've first created the database myself and made sure the correct SQL user was configured and I had access.

Then I removed the initializer and the code from the Global.asax file. After that I ran the following command in the Package Manager Console (since the layered design I had to select the correct project in the console);

Enable-Migrations

After the migrations where enabled and I made some last minute changes to my entities I ran the command below to scaffold an new migration;

Add-Migration AddSortOrder

After my migrations were created I ran the following command in the console and voila, the database was updated with my entities;

Update-Database -Verbose

To be able to seed the database when running the migration i've overridden the Seed method in my Configuraton.cs class, which was created when enabling the migrations. The final code in this method is like this;

protected override void Seed(MyContext context)
        {
            //  This method will be called after migrating to the latest version.

            //Add menu items and pages
            if (!context.Menu.Any() && !context.Page.Any())
            {
                context.Menu.AddOrUpdate(new Menu()
                                             {
                                                 Id = Guid.NewGuid(),
                                                 Name = "MainMenu",
                                                 Description = "Some menu",
                                                 IsDeleted = false,
                                                 IsPublished = true,
                                                 PublishStart = DateTime.Now,
                                                 LastModified = DateTime.Now,
                                                 PublishEnd = null,
                                                 MenuItems = new List<MenuItem>()
                                                                 {
                                                                     new MenuItem()
                                                                         {
                                                                             Id = Guid.NewGuid(),
                                                                             IsDeleted = false,
                                                                             IsPublished = true,
                                                                             PublishStart = DateTime.Now,
                                                                             LastModified = DateTime.Now,
                                                                             PublishEnd = null,
                                                                             Name = "Some menuitem",
                                                                             Page = new Page()
                                                                                        {
                                                                                            Id = Guid.NewGuid(),
                                                                                            ActionName = "Some Action",
                                                                                            ControllerName = "SomeController",
                                                                                            IsPublished = true,
                                                                                            IsDeleted = false,
                                                                                            PublishStart = DateTime.Now,
                                                                                            LastModified = DateTime.Now,
                                                                                            PublishEnd = null,
                                                                                            Title = "Some Page"
                                                                                        }
                                                                         },
                                                                     new MenuItem()
                                                                         {
                                                                             Id = Guid.NewGuid(),
                                                                             IsDeleted = false,
                                                                             IsPublished = true,
                                                                             PublishStart = DateTime.Now,
                                                                             LastModified = DateTime.Now,
                                                                             PublishEnd = null,
                                                                             Name = "Some MenuItem",
                                                                             Page = new Page()
                                                                                        {
                                                                                            Id = Guid.NewGuid(),
                                                                                            ActionName = "Some Action",
                                                                                            ControllerName = "SomeController",
                                                                                            IsPublished = true,
                                                                                            IsDeleted = false,
                                                                                            PublishStart = DateTime.Now,
                                                                                            LastModified = DateTime.Now,
                                                                                            PublishEnd = null,
                                                                                            Title = "Some Page"
                                                                                        }
                                                                         }
                                                                 }
                                             });
            }

            if (!context.ComponentType.Any())
            {
                context.ComponentType.AddOrUpdate(new ComponentType()
                {
                    Id = Guid.NewGuid(),
                    IsDeleted = false,
                    IsPublished = true,
                    LastModified = DateTime.Now,
                    Name = "MyComponent",
                    PublishEnd = null,
                    PublishStart = DateTime.Now
                });
            }


            try
            {
                // Your code...
                // Could also be before try if you know the exception occurs in SaveChanges

                context.SaveChanges();
            }
            catch (DbEntityValidationException e)
            {
                //foreach (var eve in e.EntityValidationErrors)
                //{
                //    Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
                //        eve.Entry.Entity.GetType().Name, eve.Entry.State);
                //    foreach (var ve in eve.ValidationErrors)
                //    {
                //        Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
                //            ve.PropertyName, ve.ErrorMessage);
                //    }
                //}
                //throw;

                var outputLines = new List<string>();
                foreach (var eve in e.EntityValidationErrors)
                {
                    outputLines.Add(string.Format(
                        "{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:",
                        DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State));
                    foreach (var ve in eve.ValidationErrors)
                    {
                        outputLines.Add(string.Format(
                            "- Property: \"{0}\", Error: \"{1}\"",
                            ve.PropertyName, ve.ErrorMessage));
                    }
                }
                System.IO.File.AppendAllLines(@"c:\temp\errors.txt", outputLines);
                throw;
            }
        }

The disadvantage at the moment is that I have to manually migrate with (only) 2 commands in the package manager console. But the same time, the fact that this doesn't happen dynamically is also good because this prevents possibly inwanted changes to my database. Further everything works just perfect.

Rob
  • 6,731
  • 12
  • 52
  • 90