59

Why there is no IDbContext interface in the Entity Framework? Wouldn't it be easier to test things if there was an existing interface with methods like SaveChanges() etc. from which you could derive your custom database context interface?

public interface ICustomDbContext : IDbContext
{
    // add entity set properties to existing set of methods in IDbContext
    IDbSet<SomeEntity> SomeEntities { get; }
}
Scott
  • 13,735
  • 20
  • 94
  • 152
Grief Coder
  • 6,508
  • 9
  • 38
  • 51
  • 3
    You can still create such interface and implement it on your derived context but it will really [not be very helpful for unit testing](http://stackoverflow.com/questions/6766478/unit-testing-dbcontext). – Ladislav Mrnka Sep 04 '12 at 13:00
  • 1
    Yeah, I'm just curious why there is no such interface at the first place, out of the box. There wouldn't be a need to create it manually. – Grief Coder Sep 04 '12 at 17:05
  • 2
    You can wrap the DbContext in a Repository, so can mock that instead. Again as Ladislav points out you have to be careful not to expose anything on the Repository that would be effected by linq to entities / linq to sql – Steven Mortimer Nov 06 '12 at 22:57
  • 5
    @StevenMortimer it's my belief that a context is nearer a Unit Of Work than a repository. The repository is closer to the DbSet. – Bruno Brant Jun 17 '13 at 19:45
  • 1
    @Bruno, you are right, but still, a repo implementation _must_ use a context. A "repo uses/have a unit of work" does not make sense. A "unit of work uses/have one or more repo" that makes sense. And again, you are right, a context have one or more DbSet. This warn us, when building a repo around a context, and building a unit of work around of the built repos: We are doing some redundant thing, embed a design pattern to the same design pattern... And why we doing this? Because the lack of the original implementation's clear interfaces: IDbRepository is missing (that would be the unit of work) – g.pickardou Nov 12 '13 at 08:47
  • Well it is not a good idea: 100 000s of developers can create it for herself/himself (1 000 000) times... It would be better if EF team care about accepted design patterns and well known unit testing practices. Its' the EF _6_ now – g.pickardou Nov 12 '13 at 08:52
  • @g.pickardou Excellent thoughts. I actually abandoned the use of the pattern and am using the DbContext directly, which I think is a misstep of some sort, but [keep things simpler](http://en.wikipedia.org/wiki/KISS_principle). – Bruno Brant Nov 19 '13 at 19:32

4 Answers4

17

I see this IDbContext:

See this link And then you make a new partial class for your Entities Context With That interface.

public partial class YourModelEntities : DbContext, IDbContext 

EDITED: I edited this post, This Works for me. My Context

namespace dao
{
    public interface ContextI : IDisposable
    {
        DbSet<TEntity> Set<TEntity>() where TEntity : class;
        DbSet Set(Type entityType);
        int SaveChanges();
        IEnumerable<DbEntityValidationResult> GetValidationErrors();
        DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity:class;
        DbEntityEntry Entry(object entity);
        string ConnectionString { get; set; }
        bool AutoDetectChangedEnabled { get; set; }
        void ExecuteSqlCommand(string p, params object[] o);
        void ExecuteSqlCommand(string p);
    }
}

YourModelEntities is your auto-generated partial class, and your need to create a new partial class with the same name, then add your new context interface, for this example is ContextI

NOTE: The interface hasn't implement all methods, because the methods are implemented in your auto-generate code.

namespace dao
{
    public partial class YourModelEntities :DbContext, ContextI
    {
        public string ConnectionString
        {
            get
            {
                return this.Database.Connection.ConnectionString;
            }
            set
            {
                this.Database.Connection.ConnectionString = value;
            }
        }

        bool AutoDetectChangedEnabled
        {
            get
            {
                return true;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public void ExecuteSqlCommand(string p,params object[] os)
        {
            this.Database.ExecuteSqlCommand(p, os);
        }

        public void ExecuteSqlCommand(string p)
        {
            this.Database.ExecuteSqlCommand(p);
        }

        bool ContextI.AutoDetectChangedEnabled
        {
            get
            {
                return this.Configuration.AutoDetectChangesEnabled;
            }
            set
            {
                this.Configuration.AutoDetectChangesEnabled = value;
            }
        }
      
    }
}
Ashraf Sada
  • 4,527
  • 2
  • 44
  • 48
user1626116
  • 183
  • 2
  • 8
0

I was thinking also about that, I assume you are going to use it for mocking DbContext. I find no reason for that, except that you will need to implement your own DbSet manually in your anyway for your mocked class (so will need to rewrite your own interface anyway).

Mohammed Noureldin
  • 14,913
  • 17
  • 70
  • 99
0

Just create a mock DbContext extending your production DbContext overriding the methods that complicate testing. That way, any changes to the production DbContext are automatically reflected in the tests, save for the overridden methods. For any other classes that deal with persistence and take the DbContext just extend them as well passing in the extended mock DbContext.

namespace Test.Mocks
{  
    public sealed class MockDatabaseContext : MainProject.Persistence.Database.DatabaseContext
    {
        public MockDatabaseContext(ConfigurationWrapper config) : base(config)
        {

        }      
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {

            var dbPath = "test.db";
            optionsBuilder.UseSqlite($"Filename={dbPath}");


        }
    }
}

namespace Test.Mocks
{

    public class MockInventoryFacade : InventoryFacade
    {        
        public MockInventoryFacade(MockDatabaseContext databaseContext) : base(databaseContext)
        {

        }    
    }
}
Sean Anderson
  • 614
  • 8
  • 20
-13

There is no IDbContext because it would be useless, the only implementation of it would be the DbContext.

EF team is also going this way with IDbSet if you look at this design meeting note

For me, the real problem with EF when it comes to unit testing is the DbConnection in the DbContext, fortunately there is Effort a nice project on codeplex that starts to fill this.

Effort is a powerful tool that enables a convenient way to create automated tests for Entity Framework based applications. It is basically an ADO.NET provider that executes all the data operations on a lightweight in-process main memory database instead of a traditional external database. It provides some intuitive helper methods too that make really easy to use this provider with existing ObjectContext or DbContext classes. A simple addition to existing code might be enough to create data driven tests that can run without the presence of the external database.

With this, you can leave your DbContext and DbSet as is and do your unit tests easily. The only drawback with this is the difference between Linq providers where some unit tests may pass with effort and not with the real backend.

UPDATE with EF7

I still maintain that IDbContext would be useless and the problem comes from the DbConnection.

EF7 will not have an IDbContext either, in order to do unit testing they are now giving an in memory provider.

You can see Rowan Miller doing a demo here: Modern Data Applications with Entity Framework 7

JuChom
  • 5,717
  • 5
  • 45
  • 78
  • 3
    This would not be useless at all. Right now we are using WCF Data Services that relies on DBContext instance. If it relied on the interface we would be able to get access to the Set() method call and provide the filtering layer which we desperately need (and query interceptors are simply not enough). – pkolodziej Oct 10 '13 at 07:24
  • With EF6 DbSet's methods are now virtual, you could inherit from DbSet and override needed methods. – JuChom Oct 14 '13 at 11:30
  • MS Data Services work directly on DbContext or something that inherits from it. Is there a way to make the Set<> method (which is not virtual even is EF6) return my own DbSet? What I really need to do is filter out the entities (return only some of them). – pkolodziej Oct 17 '13 at 07:40
  • Actually Set is in DbContext not DbSet :/ For this specific problem there is an issue open https://entityframework.codeplex.com/workitem/945 – JuChom Oct 17 '13 at 19:57
  • 4
    IDbContext definitely would not be useless. As it one of the main component in implementation of the Repository pattern, unit testing is impossible if we can not inject a mock context instead of the actual real implemented. This problem valid even if we can not use the Repository pattern. All DAL layers main component is the context, and must be mockable. Of course if can be mocked with some tricks and code generators etc, but the universal and generic way to mock it is always based to mock the interface what the component is implementing. EF designers are completely ignore this since _years_ – g.pickardou Nov 12 '13 at 08:40
  • 2
    You should read this http://msdn.microsoft.com/en-US/data/dn314429 . You'll see mocking with no interface in action. – JuChom Nov 12 '13 at 15:39
  • @pkolodziej whould this solve your problem of filtering: http://www.wiktorzychla.com/2013/10/soft-delete-pattern-for-entity.html – JuChom Jan 16 '14 at 16:19
  • @Swell Thank you for the comment (I really appreciate that). Do you think it will work with database-first approach? – pkolodziej Jan 24 '14 at 16:37
  • Effort seems to be awesome! – Akira Yamamoto Oct 10 '14 at 18:28
  • The usecase is mocking out the SaveChanges method and it's still a problem in 2016. – Izzy Aug 19 '16 at 12:03
  • @Swell Adding the `virtual` keyword to concrete classes just for the purposes of testing seems like a bit of a code smell – Dan Nov 10 '16 at 11:32
  • @DanPantry I agree with that! I do my unit tests with a real DB to avoid different behaviors. Actual DbSet in EF6 are done like that. – JuChom Nov 10 '16 at 14:00