Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC 3 Application using Ninject, Entity Framework 4 Code-First CTP 5, Patterns

I've tried to build some base project with above technologies. I wanted maximum flexibility and testability so I tried to use patterns along the way to make this as a base for future projects. However, it seem something is wrong or whatever and I really need help here. So i have two questions :

  1. Is there anything wrong with my current code? I've applied patterns correctly? Any suggestions or recommendation that would lead me in the right direction?

  2. Why do this code actually connect to the database, create it, but doesn't support insert even if I perform the corrects operation? (Look at the end of the post for details about this error) FIXED

I believe this could also help others since I haven't found enough information in order to make something up correctly. I am pretty sure lots of people try to do it the right way and are not sure like me if what I am doing is right.

I have two entities: Comment and Review

COMMENT

public class Comment
{
 [Key]
 public virtual int Id { get; set; }

 public virtual string Name { get; set; }
 public virtual string Author { get; set; }
 public virtual string Body { get; set; }
}

REVIEW

public class Review
{
 [Key]
 public virtual int Id { get; set; }

 public virtual string Name { get; set; }
 public virtual string Author { get; set; }
 public virtual string Body { get; set; }
 public virtual bool Visible { get; set; }

 public IEnumerable<Comment> Comments { get; set; }
}

I built up a base repository for each of them this way :

GENERIC REPOSITORY

public abstract class EFRepositoryBase<T> : IRepository<T> where T : class
{
 private Database _database;
 private readonly IDbSet<T> _dbset;

 protected IDatabaseFactory DatabaseFactory { get; private set; }
 protected Database Database { get { return _database ?? (_database = DatabaseFactory.Get()); } }

 public EFRepositoryBase(IDatabaseFactory databaseFactory)
 {
  DatabaseFactory = databaseFactory;
  _dbset = Database.Set<T>();
 }

 public virtual void Add(T entity)
 {
  _dbset.Add(entity);
 }

 public virtual void Delete(T entity)
 {
  _dbset.Remove(entity);
 }

 public virtual T GetById(long id)
 {
  return _dbset.Find(id);
 }

 public virtual IEnumerable<T> All()
 {
  return _dbset.ToList();
 }
}

For specific operations, I use an interface:

public interface IReviewRepository : IRepository<Review> {
 // Add specific review operations
 IEnumerable<Review> FindByAuthor(string author);
}

So I am getting the generics operations from the abstract class plus the specific operations:

public class EFReviewRepository : EFRepositoryBase<Review>, IReviewRepository
{
 public EFReviewRepository(IDatabaseFactory databaseFactory) 
  : base(databaseFactory)
 { }

 public IEnumerable<Review> FindByAuthor(string author)
 {
  return base.Database.Reviews.Where(r => r.Author.StartsWith(author))
   .AsEnumerable<Review>();
 }
}

As you figured out, I also use a database factory will produce the database context :

DATABASE FACTORY

public class DatabaseFactory : Disposable, IDatabaseFactory
{
 private Database _database;

 public Database Get()
 {
  return _database ?? (_database = new Database(@"AppDb"));
 }

 protected override void DisposeCore()
 {
  if (_database != null)
   _database.Dispose();
 }
}

DISPOSABLE (Some extensions methods...)

public class Disposable : IDisposable
{
 private bool isDisposed;

 ~Disposable()
 {
  Dispose(false);
 }

 public void Dispose()
 {
  Dispose(true);
  GC.SuppressFinalize(this);
 }
 private void Dispose(bool disposing)
 {
  if (!isDisposed && disposing)
  {
   DisposeCore();
  }

  isDisposed = true;
 }

 protected virtual void DisposeCore()
 {
 }
}

DATABASE

public class Database : DbContext
{
 private IDbSet<Review> _reviews;

 public IDbSet<Review> Reviews
 {
  get { return _reviews ?? (_reviews = DbSet<Review>()); }
 }

 public virtual IDbSet<T> DbSet<T>() where T : class
 {
  return Set<T>();
 }

 public Database(string connectionString)
  : base(connectionString)
 {
  //_reviews = Reviews;
 }

 public virtual void Commit()
 {
  base.SaveChanges();
 }

 /*
 protected override void OnModelCreating(ModelBuilder modelBuilder)
 {
  // TODO: Use Fluent API Here 
 }
 */
}

And to finish, I have my unit of work....

UNIT OF WORK

public class UnitOfWork : IUnitOfWork
{
 private readonly IDatabaseFactory _databaseFactory;
 private Database _database;

 public UnitOfWork(IDatabaseFactory databaseFactory)
 {
  _databaseFactory = databaseFactory;
 }

 protected Database Database
 {
  get { return _database ?? (_database = _databaseFactory.Get()); }
 }

 public void Commit()
 {
  Database.Commit();
 }
}

I also bound using Ninject the interfaces:

NINJECT CONTROLLER FACTORY

public class NinjectControllerFactory : DefaultControllerFactory
{
 // A Ninject "Kernel" is the thing that can supply object instances
 private IKernel kernel = new StandardKernel(new ReviewsDemoServices());

 // ASP.NET MVC calls this to get the controller for each request
 protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
 {
  if (controllerType == null)
   return null;
  return (IController)kernel.Get(controllerType);
 }

 private class ReviewsDemoServices : NinjectModule
 {
  public override void Load()
  {
   // Bindings...
   Bind<IReviewRepository>().To<EFReviewRepository>();
   Bind<IUnitOfWork>().To<UnitOfWork>();
   Bind<IDatabaseFactory>().To<DatabaseFactory>();
   Bind<IDisposable>().To<Disposable>();
  }
 }
}

However, when I call in the constructor (the default action) ...

public class ReviewController : Controller
    {
        private readonly IReviewRepository _reviewRepository;
        private readonly IUnitOfWork _unitOfWork;

        public ReviewController(IReviewRepository postRepository, IUnitOfWork unitOfWork)
        {
            _reviewRepository = postRepository;
            _unitOfWork = unitOfWork;
        }

        public ActionResult Index()
        {
            Review r = new Review { Id = 1, Name = "Test", Visible = true, Author = "a", Body = "b" };
            _reviewRepository.Add(r);
            _unitOfWork.Commit();

            return View(_reviewRepository.All());
        }

    }

This seem to create the database but doesnt't insert anything in the database in EF4. It seem that I may figured out the problem.. while looking at the database object.. the connection state is closed and server version throw an exception of this kind :

ServerVersion = '(((System.Data.Entity.DbContext (_database)).Database.Connection).ServerVersion' threw an exception of type 'System.InvalidOperationException'

I am doing the right things? Is there anything wrong in what I've built ?

Also if you have recommandation about the code I posted, I would be glad. I am just trying to the learn the right way for building any kind of application in MVC 3. I want a good a start.

I use :

  • Entity Framework 4 with Code-First

  • ASP.NET MVC 3

  • Ninject as DI Container

  • SQL Server Express (not R2)

  • Visual Studio 2010 Web Express

like image 351
Rushino Avatar asked Jan 24 '11 13:01

Rushino


People also ask

How to create a MVC 4 web application using mvcninject?

In this, select “ASP.NET MVC 4 Web Application. ” After selecting, just name your project as “MvcNInject” and finally click the OK button to create the project. Figure 2. Selecting Template After clicking OK button, another project template selection wizard will pop up with the name “New ASP.NET MVC 4 Project.”

How to create mvcninject project in Visual Studio Code?

After selecting New Project, a new dialog will pop up with the name Add new Project from the panel. Select type Visual C#; inside it, select Windows from the template, select [Class Library], name your project as MvcNInject.Concrete and click OK button.

How to install ninject framework in Visual Studio Code?

To install Ninject Framework using Package Manager Console, first from IDE menu select Tools - then NuGet package Manager - inside that select Package Manager Console. In Package Manager Console enter Command: Install-Package Ninject.MVC4. After entering command just press enter button it will get Installed.

How to update one line code in Entity Framework?

For convenience, you can update one line code in file Views/Shared/_Layout.chtml Select the Create New link. Enter some details about a movie and then click the Create button. We will write a series of articles for Entity Framework with various approaches, such as code-first or database-first.


2 Answers

Eww. This one was sneaky. Actually i don't know ninject much so i couldnt figure it out right away.

I found the solution for the SECOND question which was related to the error by finding that ninject actually shoot two instance of the DatabaseFactory, one for the repository and one for the unit of work. Actually, the error was not the problem. It was an internal error in the object database but its normal i think since im using Entity Framework.

The real problem was that Ninject was binding two different instance of IDatabaseFactory which lead to 2 connection open.

The review was added to the first set in _reviewRepostory which was using the first instance of the Database.

When calling commit on the unit of work.. it saved nothing due to the fact that the review wasnt on this database instance. In fact, the unit of work called the databasefactory which lead to creating a new instance since ninject sent a new instance of it.

To fix it simply use :

 Bind<IDatabaseFactory>().To<DatabaseFactory>().InSingletonScope();

instead of

Bind<IDatabaseFactory>().To<DatabaseFactory>();

And now all the system work correctly!

Now, would love some answers regarding the first question which was if there anything wrong with my current code ? Ive applied patterns correctly ? Any suggestions or recommendation that would lead me in the right direction ?

like image 62
Rushino Avatar answered Dec 10 '22 18:12

Rushino


One small observation: by having your EFRepositoryBase and IReviewRepository have methods that return an IEnumerable<> instead of an IQueryable<>, you prevent subsequent methods from adding filter expressions/constraints or projections or so on to the query. Instead, by using IEnumerable<>, you will do any subsequent filtering (e.g. using LINQ extension methods) on the full result set, rather than allowing those operations to affect and simplify the SQL statement that gets run against the datastore.

In other words, you are doing further filtering at the webserver level, not at the database level where it really belongs if possible.

Then again, this may be intentional - sometimes using IEnumerable<> is valid if you do want to prevent callers of your function from modifying the SQL that is generated, etc.

like image 24
GregL Avatar answered Dec 10 '22 18:12

GregL