Entity Framework Code First Bootstrapping

This post is primarily for my fellow friends of local .net user group who asked for a rough guideline on how to get started with the latest Entity Framework Code First, I will try to keep it short and concise. I assume that it is going to be used in an ASP.NET MVC Application and behind the scene an IoC container would be used to glue everything. Before going into the implementation details there are certain design choice of mine, first persistence ignorance which means the domain model have no clue whether Entity Framework is used or any other ORM (nothing fancy here, out of the box the EF Code First has this support), next, I prefer to use class mapping which I think gives a bit more control comparing to decorating the domain classes with the data annotation attributes. Lets starts with a typical Category, Product, Customer and Order domain.

Domain Model
Entity Framework Code First Domain Model

Each Category can have zero or more products and a Product can also have many categories, the categories also have parent-child relation among them. A Customer can have many orders and an Order has many line items and each line item has associated Product. If you are familiar with the Domain Driven Design (in case if you are not then read this free online book from InfoQ) you already know the terms like Entity, Aggregate Root and Value Objects. In the above, except the Address everything is an Entity and these entities are also Aggregate Root except the OrderLineItem, the Address is a Value Object which embeds into the Customer and Order entity. There are one more convention I will follow that all of the entities should have a Id property which type is Guid. I am not going to post the codes of the above classes since they are pretty basic,  only property getters and setters and few shortcut methods to build up the object model properly, in real project this should contain most of the domain logics/business rules.

Now, lets focus on the Data Access part, I will follow the same Repository pattern to persist and retrieve these objects from database, but rather than creating separate repository for each Aggregate Root, I will create a generic repository. Here is the code:

Generic Repository

              public class Repository<TAggregateRoot> :
                  IRepository<TAggregateRoot> where TAggregateRoot : class, IAggregateRoot
              {
                  public Repository(DataContext context)
                  {
                      Contract.Requires<ArgumentNullException>(context != null);
              
                      Context = context;
                  }
              
                  protected DataContext Context { get; private set; }
              
                  public virtual void Save(TAggregateRoot instance)
                  {
                      if (instance.Id == Guid.Empty)
                      {
                          instance.Id = GuidComb.Generate();
                          instance.CreatedAt = Clock.Now();
                          CreateSet().Add(instance);
                      }
                      else
                      {
                          instance.UpdatedAt = Clock.Now();
                          Context.MarkAsModified(instance);
                      }
                  }
              
                  public virtual void Remove(TAggregateRoot instance)
                  {
                      CreateSet().Remove(instance);
                  }
              
                  public virtual TAggregateRoot One(
                      Expression<Func<TAggregateRoot, bool>> predicate = null,
                      params Expression<Func<TAggregateRoot, object>>[] includes)
                  {
                      var set = CreateIncludedSet(includes);
              
                      return (predicate == null) ?
                             set.FirstOrDefault() :
                             set.FirstOrDefault(predicate);
                  }
              
                  public virtual IQueryable<TAggregateRoot> All(
                      Expression<Func<TAggregateRoot, bool>> predicate = null,
                      params Expression<Func<TAggregateRoot, object>>[] includes)
                  {
                      var set = CreateIncludedSet(includes);
              
                      return (predicate == null) ? set : set.Where(predicate);
                  }
              
                  public virtual bool Exists(
                      Expression<Func<TAggregateRoot, bool>> predicate = null)
                  {
                      var set = CreateSet();
              
                      return (predicate == null) ? set.Any() : set.Any(predicate);
                  }
              
                  public virtual int Count(
                      Expression<Func<TAggregateRoot, bool>> predicate = null)
                  {
                      var set = CreateSet();
              
                      return (predicate == null) ?
                             set.Count() :
                             set.Count(predicate);
                  }
              
                  private IDbSet<TAggregateRoot> CreateIncludedSet(
                      IEnumerable<Expression<Func<TAggregateRoot, object>>> includes)
                  {
                      var set = CreateSet();
              
                      if (includes != null)
                      {
                          foreach (var include in includes)
                          {
                              set.Include(include);
                          }
                      }
              
                      return set;
                  }
              
                  private IDbSet<TAggregateRoot> CreateSet()
                  {
                      return Context.CreateSet<TAggregateRoot>();
                  }
              }
              

I think most the of the above code is self explanatory, in the constructor I are passing a custom DbContext(which I am going to explain a bit later), then instead of having separate Insert/Update, I only have Save which does both Insert/Update based upon the Id, for the Id generation rather than using Guid.NewGuid(), I am using the NHibernate GuidComb which generates the sequential value, for the timestamp properties I am using the standard Func<> based DateTime so that I can replace the value in the tests. If you are thinking why I am not using the new DateTimeOffset, well the main reason is that it is not supported in the SQL Server Compact Edition which I prefer to use in development and integration tests. Next, all the parameters of the query methods are optional, the One and All also takes an optional includes which is used to eager load the related objects.

Now, lets check how the custom DbContext is implemented. When you are using the class mapping the standard practice is to override the OnModelCreating method and register the mappings, something like the following:

Standard Class Mapping Registration

              public class MyDbContext: DbContext
              {
                  protected override void OnModelCreating(DbModelBuilder modelBuilder)
                  {
                      modelBuilder.Configurations.Add(new CategoryMapping());
                      modelBuilder.Configurations.Add(new ProductMapping());
              
                      base.OnModelCreating(modelBuilder);
                  }
              }
              

But the problem with this approach is that every time I add/edit/remove the mapping I have to modify this method. Instead, lets automate it, the goal is to collect all the mappings and register it automatically. Here is the actual code of DataContext:

Automated Mapping Registration in DbContext

              public class DataContext : DbContext
              {
                  private readonly IDictionary<MethodInfo, object> configurations;
              
                  public DataContext(
                      string nameOrConnectionString,
                      IDictionary<MethodInfo, object> configurations)
                      : base(nameOrConnectionString)
                  {
                      Contract.Requires<ArgumentNullException>(configurations != null);
              
                      this.configurations = configurations;
                  }
              
                  public virtual void MarkAsModified<TEntity>(TEntity instance)
                      where TEntity : class
                  {
                      Entry(instance).State = EntityState.Modified;
                  }
              
                  public virtual IDbSet<TEntity> CreateSet<TEntity>()
                      where TEntity : class
                  {
                      return Set<TEntity>();
                  }
              
                  protected override void OnModelCreating(DbModelBuilder modelBuilder)
                  {
                      if (modelBuilder == null)
                      {
                          throw new ArgumentNullException("modelBuilder");
                      }
              
                      foreach (var config in configurations)
                      {
                          config.Key.Invoke(
                              modelBuilder.Configurations,
                              new[] { config.Value });
                      }
              
                      base.OnModelCreating(modelBuilder);
                  }
              }
              

The dictionary in the above constructor holds the generic method of registration as key and class mapping instance as value. Now, I need a factory which should gather all the available mappings and creates this DataContext. Typically in a web application each request should have individual factory and in the lifetime of a request no matter how many times the DataContext is requested the factory would return the same DataContext instance. Here is the DataContextFactory Code:


              public class DataContextFactory : Disposable
              {
                  private static readonly Type entityType =
                      typeof(EntityTypeConfiguration<>);
              
                  private static readonly Type complexType =
                      typeof(ComplexTypeConfiguration<>);
              
                  private static readonly
                      ConcurrentDictionary<string, IDictionary<MethodInfo, object>>
                      mappingConfigurations =
                          new ConcurrentDictionary<string, IDictionary<MethodInfo, object>>();
              
                  private readonly string nameOrConnectionString;
              
                  private static Type dataContextType = typeof(DataContext);
              
                  private DataContext context;
              
                  public DataContextFactory(
                      string nameOrConnectionString)
                  {
                      Contract.Requires<ArgumentException>(
                          !string.IsNullOrWhiteSpace(nameOrConnectionString));
              
                      this.nameOrConnectionString = nameOrConnectionString;
                  }
              
                  public static Type DataContextType
                  {
                      get
                      {
                          return dataContextType;
                      }
              
                      set
                      {
                          Contract.Requires<ArgumentNullException>(value != null);
                          Contract.Requires<ArgumentException>(
                              typeof(DataContext).IsAssignableFrom(value));
              
                          dataContextType = value;
                      }
                  }
              
                  public virtual DataContext GetContext()
                  {
                      return context ?? (context = CreateContext());
                  }
              
                  protected override void DisposeCore()
                  {
                      if (context != null)
                      {
                          context.Dispose();
                      }
                  }
              
                  private static IDictionary<MethodInfo, object> BuildConfigurations()
                  {
                      var addMethods = typeof(ConfigurationRegistrar).GetMethods()
                          .Where(m => m.Name.Equals("Add"))
                          .ToList();
              
                      var entityTypeMethod = addMethods.First(m =>
                          m.GetParameters()
                              .First()
                              .ParameterType
                              .GetGenericTypeDefinition()
                              .IsAssignableFrom(entityType));
              
                      var complexTypeMethod = addMethods.First(m =>
                          m.GetParameters().First()
                              .ParameterType
                              .GetGenericTypeDefinition()
                              .IsAssignableFrom(complexType));
              
                      var configurations = new Dictionary<MethodInfo, object>();
              
                      var types = typeof(DataContextFactory).Assembly
                          .GetExportedTypes()
                          .Where(IsMappingType)
                          .ToList();
              
                      foreach (var type in types)
                      {
                          MethodInfo typedMethod;
                          Type modelType;
              
                          if (IsMatching(
                              type, out modelType, t => entityType.IsAssignableFrom(t)))
                          {
                              typedMethod = entityTypeMethod.MakeGenericMethod(
                                  modelType);
                          }
                          else if (IsMatching(
                              type, out modelType, t => complexType.IsAssignableFrom(t)))
                          {
                              typedMethod = complexTypeMethod.MakeGenericMethod(
                                  modelType);
                          }
                          else
                          {
                              continue;
                          }
              
                          configurations.Add(
                              typedMethod, Activator.CreateInstance(type));
                      }
              
                      return configurations;
                  }
              
                  private static bool IsMappingType(Type matchingType)
                  {
                      if (!matchingType.IsClass ||
                          matchingType.IsAbstract)
                      {
                          return false;
                      }
              
                      Type temp;
              
                      return IsMatching(
                          matchingType,
                          out temp,
                          t =>
                              entityType.IsAssignableFrom(t) ||
                              complexType.IsAssignableFrom(t));
                  }
              
                  private static bool IsMatching(
                      Type matchingType,
                      out Type modelType,
                      Predicate<Type> matcher)
                  {
                      modelType = null;
              
                      while (matchingType != null)
                      {
                          if (matchingType.IsGenericType)
                          {
                              var definationType = matchingType
                                  .GetGenericTypeDefinition();
              
                              if (matcher(definationType))
                              {
                                  modelType = matchingType.GetGenericArguments().First();
                                  return true;
                              }
                          }
              
                          matchingType = matchingType.BaseType;
                      }
              
                      return false;
                  }
              
                  private DataContext CreateContext()
                  {
                      var configurtions = mappingConfigurations.GetOrAdd(
                          nameOrConnectionString,
                          key => BuildConfigurations());
              
                      var localContext = (DataContext)Activator.CreateInstance(
                          DataContextType,
                          new object[] { nameOrConnectionString, configurtions });
              
                      return localContext;
                  }
              }
              

The DataContextFactory maintains a static dictionary where it uses the connection string name as key and the model builder generic method and class mapping instance as value. The idea is should only scan one for a specific connection string. The matching part is handled in the BuildConfigurations where it only scans the current assembly, in case of domain objects that spans multiple projects you may have to provide a build manager in the DataContextFactory constructor which contains all the assembly references.

Next thing I want to focus is the UnitOfWork, unless the DbContext.SaveChanges() is called, nothing gets sends to to the server. This UnitOfWork should be called by custom action filter, may be in the action executed method. Here is the code of UnitOfWork:

UnitOfWork

              public class UnitOfWork
              {
                  public UnitOfWork(DataContext context)
                  {
                      Contract.Requires<ArgumentNullException>(context != null);
              
                      Context = context;
                  }
              
                  protected DataContext Context { get; private set; }
              
                  public virtual void Commit()
                  {
                      if (Context.ChangeTracker.Entries().Any(HasChanged))
                      {
                          Context.SaveChanges();
                      }
                  }
              
                  private static bool HasChanged(DbEntityEntry entity)
                  {
                      return IsState(entity, EntityState.Added) ||
                             IsState(entity, EntityState.Deleted) ||
                             IsState(entity, EntityState.Modified);
                  }
              
                  private static bool IsState(DbEntityEntry entity, EntityState state)
                  {
                      return (entity.State & state) == state;
                  }
              }
              

There are one more little thing I need to do before wrapping up the data access, in development and test mode I always need the database schema to be updated according to the domain model but in the production mode I want this feature turned off. (As a side note the EF team is also working on migration support, currently it is in beta, which you should keep an eye on it.)

Schema Synchronizer

              public class SchemaSynchronizer
              {
                  private readonly Func<bool> debugMode;
              
                  public SchemaSynchronizer(Func<bool> debugMode)
                  {
                      Contract.Requires<ArgumentNullException>(debugMode != null);
              
                      this.debugMode = debugMode;
                  }
              
                  public void Execute()
                  {
                      var initializer = debugMode() ?
                          new DropCreateDatabaseAlways<DataContext>() :
                          new NullDatabaseInitializer<DataContext>()
                          as IDatabaseInitializer<DataContext>;
              
                      Database.SetInitializer(initializer);
                  }
              }
              

Usually this code will be executed in the application start, e.g. new SchemaSynchronizer(() => HttpContext.Current.IsDebuggingEnabled).Execute().

Now, everything is set for the integration tests (please note that the above code is written in test first manner, but the primary goal of this post is starting with EF so I kept the test parts in a separate section). As mention in the above that I prefer to use the SQL Server Compact Edition for development/test so I have to add the reference of it’s NuGet package in my test project,  other than it I will also add Machine.Specifications(aka MSpec) and AutoPoco NuGet packages in my test project. Next, we have make sure before running the tests our database is synced with the domain model. The MSpec has a hook, the IAssemblyContext interface, the MSpec test runner executes the code written in the OnAssemblyStart and OnAssemblyComplete methods when the test session begins and ends. So, all I have to do is to execute the SchemaSynchronizer when the session starts.


              public class Bootstrapper : IAssemblyContext
              {
                  public void OnAssemblyStart()
                  {
                      new SchemaSynchronizer(() => true).Execute();
                  }
              
                  public void OnAssemblyComplete()
                  {
                      // Do nothing just sleep
                  }
              }
              

Next,  I want to wrap each test in a database transaction and when the test completes rollback the transaction so that it does not interfere with each other result. Unlike, many test frameworks MSpec does not has out of box support for database transaction, but adding such feature is trivial. I will create a base class from which other test classes inherits, the base class with setup the environment for running the tests:

Test setup

              public abstract class DatabaseSpec
              {
                  protected static DataContextFactory contextFactory;
                  protected static UnitOfWork unitOfWork;
              
                  static DbTransaction transaction;
              
                  Establish context = () =>
                  {
                      contextFactory = new DataContextFactory("test");
                      var context = contextFactory.GetContext();
                      unitOfWork = new UnitOfWork(context);
              
                      IObjectContextAdapter adapter = context;
              
                      if ((adapter.ObjectContext.Connection.State &
                          ConnectionState.Open) != ConnectionState.Open)
                      {
                          adapter.ObjectContext.Connection.Open();
                      }
              
                      transaction = adapter.ObjectContext
                          .Connection
                          .BeginTransaction(IsolationLevel.ReadCommitted);
                  };
              
                  Cleanup on_exit = () =>
                  {
                      transaction.Rollback();
                      transaction.Dispose();
              
                      contextFactory.Dispose();
                  };
              }
              

Now, we can write the test to check whether our DataContextFactory is properly configured like this:

DataContextFactorySpec

              [Subject(typeof(DataContextFactory))]
              public class when_getting_context : DatabaseSpec
              {
                  static DataContext context;
              
                  Because of = () => context = contextFactory.GetContext();
              
                  It should_not_be_null = () => context.ShouldNotBeNull();
              
                  It should_be_data_context_type = () =>
                      context.ShouldBeOfType<DataContext>();
              
                  It should_register_available_mappings = () =>
                  {
                      var availableMappings = typeof(DataContextFactory).Assembly
                          .GetExportedTypes()
                          .Where(t =>
                              t.IsClass &&
                              !t.IsAbstract &&
                              (typeof(IEntity).IsAssignableFrom(t) ||
                              typeof(IValueObject).IsAssignableFrom(t)))
                          .Select(t => t.Name)
                          .ToList();
              
                      IObjectContextAdapter adapter = context;
              
                      var registeredMappings = adapter.ObjectContext
                          .MetadataWorkspace.GetItems(DataSpace.OSpace)
                          .OfType<EntityType>()
                          .Select(e => e.Name)
                          .Concat(adapter.ObjectContext
                                         .MetadataWorkspace
                                         .GetItems(DataSpace.OSpace)
                                         .OfType<ComplexType>()
                                         .Select(e => e.Name))
                          .Where(e => !e.Equals("EdmMetadata"))
                          .ToList();
              
                      registeredMappings.Count.ShouldEqual(availableMappings.Count);
                      registeredMappings.ShouldContain(availableMappings);
                  };
              }
              

Or Repository Tests like:

Repository

              [Subject(typeof(Repository<Customer>))]
              public class when_saving_new_customer : DatabaseSpec
              {
                  static Repository<Customer> repository;
                  static Customer customer;
              
                  Establish context = () =>
                      repository = new Repository<Customer>(contextFactory.GetContext());
              
                  Because of = () =>
                  {
                      customer = ObjectMother.Customer();
                      repository.Save(customer);
              
                      unitOfWork.Commit();
                  };
              
                  It should_generate_new_id = () =>
                      customer.Id.ShouldNotEqual(Guid.Empty);
              
                  It should_generate_created_at = () =>
                      customer.CreatedAt.ShouldNotEqual(DateTime.MinValue);
              
                  It should_persist = () =>
                      repository.Exists(c => c.Id == customer.Id).ShouldBeTrue();
              }
              

Or

Order Repository

              [Subject(typeof(Repository<Order>))]
              public class when_saving_order_hierarchy : DatabaseSpec
              {
                  static List<Product> products;
              
                  static Repository<Customer> customerRepository;
                  static Repository<Order> orderRepository;
              
                  static Customer customer;
                  static Order order;
              
                  Establish context = () =>
                  {
                      var productRepository = new Repository<Product>(
                          contextFactory.GetContext());
              
                      products = ObjectMother.Products().ToList();
                      products.ForEach(productRepository.Save);
                      unitOfWork.Commit();
              
                      customerRepository = new Repository<Customer>(
                          contextFactory.GetContext());
              
                      orderRepository = new Repository<Order>(
                          contextFactory.GetContext());
                  };
              
                  Because of = () =>
                  {
                      customer = ObjectMother.Customer();
                      customerRepository.Save(customer);
              
                      order = ObjectMother.Order(customer);
              
                      products.ForEach(p => order.AddLineItem(p, 1));
                      orderRepository.Save(order);
              
                      unitOfWork.Commit();
                  };
              
                  It should_persist_order = () =>
                      orderRepository.Exists(o => o.Id == order.Id).ShouldBeTrue();
              
                  It should_persist_customer = () =>
                      customerRepository.Exists(c => c.Id == order.Customer.Id)
                          .ShouldBeTrue();
              
                  It should_persist_line_items = () =>
                      orderRepository.One(o => o.Id == order.Id)
                                      .LineItems
                                      .Count
                                      .ShouldEqual(products.Count);
              }
              

In the above the ObjectMother is used to create different domain objects, behind the scene it uses the previously added AutoPoco package. I am skipping rest of the Tests but I think you got the idea, now let see how it is integrated with ASP.NET MVC Application. I will be using Ninject in this case but you can use whatever IoC Container you prefer. After adding the Ninject NuGet package the first thing I have to do is create a custom IDependencyResolver which behind the scene uses the Ninject container.

NinjectDependencyResolver

              public class NinjectDependencyResolver : IDependencyResolver
              {
                  private readonly IKernel kernel;
              
                  public NinjectDependencyResolver(IKernel kernel)
                  {
                      if (kernel == null)
                      {
                          throw new ArgumentNullException("kernel");
                      }
              
                      this.kernel = kernel;
                  }
              
                  public object GetService(Type serviceType)
                  {
                      try
                      {
                          return kernel.Get(serviceType);
                      }
                      catch
                      {
                          // Eat the exception so that ASP.NET MVC can work properly
                      }
              
                      return null;
                  }
              
                  public IEnumerable<object> GetServices(Type serviceType)
                  {
                      try
                      {
                          return kernel.GetAll(serviceType);
                      }
                      catch
                      {
                          // Eat the exception so that ASP.NET MVC can work properly
                      }
              
                      return Enumerable.Empty<object>();
                  }
              }
              

Next, a custom Ninject Module to register the data access components:

DataAccessModule

              public class DataAccessModule : NinjectModule
              {
                  public override void Load()
                  {
                      Bind<DataContextFactory>()
                          .ToMethod(c => new DataContextFactory("DefaultConnection"))
                          .InRequestScope();
              
                      Bind<DataContext>()
                          .ToMethod(c => c.Kernel.Get<DataContextFactory>().GetContext());
              
                      Bind(typeof(IRepository<>)).To(typeof(Repository<>));
                      Bind<UnitOfWork>().ToSelf();
                  }
              }
              

and then configure the ASP.NET MVC DependencyResolver.

DependencyResolver setup

              public class ConfigureContainer
              {
                  public ConfigureContainer()
                  {
                      var modules = typeof(ConfigureContainer).Assembly
                          .GetExportedTypes()
                          .Where(t =>
                                  t.IsClass && !t.IsAbstract
                                  && typeof(INinjectModule).IsAssignableFrom(t))
                          .Where(t =>
                                  t.GetConstructors().Any(ctor => !ctor.GetParameters().Any()))
                          .Select(t => (INinjectModule) Activator.CreateInstance(t))
                          .ToArray();
              
                      var container = new StandardKernel(modules);
                      var resolver = new NinjectDependencyResolver(container);
              
                      DependencyResolver.SetResolver(resolver);
                  }
              }
              

And finally in application start:

Global.asax

              protected void Application_Start()
              {
                  new SchemaSynchronizer(() => HttpContext.Current.IsDebuggingEnabled ).Execute();
                  new ConfigureContainer();
              
                  AreaRegistration.RegisterAllAreas();
              
                  RegisterGlobalFilters(GlobalFilters.Filters);
                  RegisterRoutes(RouteTable.Routes);
              }
              

Now, a typical scaffolded controller would look like something like this:

Scaffolded Controller

              public class ProductsController : Controller
              {
                  private readonly IRepository<Product> repository;
              
                  public ProductsController(IRepository<Product> repository)
                  {
                      Contract.Requires<ArgumentNullException>(repository != null);
              
                      this.repository = repository;
                  }
              
                  public ActionResult Index()
                  {
                      return View(repository.All());
                  }
              
                  public ActionResult Details(Guid id)
                  {
                      return View(repository.One(p => p.Id == id));
                  }
              
                  public ActionResult Create()
                  {
                      return View(new Product());
                  }
              
                  [HttpPost]
                  public ActionResult Create(FormCollection collection)
                  {
                      try
                      {
                          var product = new Product();
              
                          UpdateModel(
                              product,
                              new[] { "Name", "UnitPrice" },
                              collection.ToValueProvider());
              
                          repository.Save(product);
              
                          return RedirectToAction("Index");
                      }
                      catch
                      {
                          return View();
                      }
                  }
              
                  public ActionResult Edit(Guid id)
                  {
                      return View(repository.One(p => p.Id == id));
                  }
              
                  [HttpPost]
                  public ActionResult Edit(Guid id, FormCollection collection)
                  {
                      try
                      {
                          var product = repository.One(p => p.Id == id);
              
                          UpdateModel(
                              product,
                              new[] { "Name", "UnitPrice" },
                              collection.ToValueProvider());
              
                          repository.Save(product);
              
                          return RedirectToAction("Index");
                      }
                      catch
                      {
                          return View();
                      }
                  }
              }
              

There are one more thing that I have to do before wrapping up this post, remember in the above I mentioned that the UnitOfWork would be Committed in the Action Filter, here is the action filter that does the job:

UnitOfWork Action Filter

              public class UnitOfWorkAttribute : FilterAttribute, IActionFilter
              {
                  public void OnActionExecuting(ActionExecutingContext filterContext)
                  {
                      // Do Nothing
                  }
              
                  public void OnActionExecuted(ActionExecutedContext filterContext)
                  {
                      if (!filterContext.IsChildAction &&
                          (filterContext.Exception == null ||
                          filterContext.ExceptionHandled))
                      {
                          DependencyResolver.Current.GetService<UnitOfWork>().Commit();
                      }
                  }
              }
              

And then decorating the controller:

Applying UnitOfWork Action Filter

              [UnitOfWork]
              public class ProductsController : Controller
              {
                  // ... //
              }
              

And that’s it, I just shown you how to get started with EF quickly yet applying some of the good practices. Obviously this is not silver bullet that you apply in every cases but it is a solid foundation upon which you can add your own modifications. You can get the complete source in the following. Feel free to ask your questions or confusion you may have.

Download: EFBootstrapping.zip

Shout it

Comments

blog comments powered by Disqus