I am developing a REST API using ASP.NET Web API, Code-First Entity Framework 5 and SQL Server 2012 and I need to be able to version the API. I've read a few blog posts and articles about indicating the API version either in the URI or in a custom HTTP header and using a custom IHttpControllerSelector to select different ApiControllers based on the indicated version. This all makes sense.
What I'm struggling to figure out is how to manage the affects of versioning beyond the Web API layer, specifically in Entity Framework. How do I go about evolving my DbContext without breaking older versions of the API? Can I version the DbContext as well? And if so, how?
What I ended up doing was combining the Repository Pattern with Pablo's answer. The gist of it is that my EF models are versioned, I use EF Code-First Migrations to migrate the database to the new versions of the models, my DbContext always works with the latest version of the models, I developed a number of concrete repositories that each implement the IRepository<TItem> interface below.
public interface IRepository<TItem> : IQueryable<TItem>, ICollection<TItem>, IDisposable
where TItem : class
{
void Update(TItem item);
void SaveChanges();
}
One implementation of IRepository<TItem> is DbRepository<TItem> which wraps the entity framework code used to talk to the database.
public class DbRepository<TItem> : IRepository<TItem>
where TItem : class
{
private MyDbContext _db;
public DbRepository()
{
_db = new MyDbContext();
}
// Implementation of IRepository<TItem> methods
}
Another implementation of IRepository<TItem> is TypeConversionRepository<TExternal,TInternal> which is an abstract class that facilitates converting from one model type to another.
public abstract class TypeConversionRepository<TExternal, TInternal> : IRepository<TExternal>
where TExternal : class
where TInternal : class
{
protected IRepository<TInternal> InternalRepository { get; set; }
protected abstract TInternal ConvertInbound(TExternal externalItem);
protected abstract TExternal ConvertOutbound(TInternal internalItem);
// Implementation of IRepository<TItem> methods
}
Methods that return models or accept models as parameters use ConvertInbound() and ConvertOutbound() to convert models of type TExternal to TInternal and vice versa. Therefore, given the following 2 versions of MyModel, we can write 2 versions of MyModelRepository; version 2 can talk directly to the database while version 1 will need to convert from version 2 back to version 1.
namespace Models.v1
{
public class MyModel
{
public int Id { get; set; }
public string MyProperty { get; set; }
}
public class MyModelRepository : TypeConversionRepository<Models.v1.MyModel,Models.v2.MyModel>
{
MyModelRepository()
{
this.InternalRepository = new Models.v2.MyModelRepository();
}
protected override TInternal ConvertInbound(TExternal externalItem)
{
return new Models.v2.MyModel
{
Id = externalItem.Id,
MyNewProperty = externalItem.MyProperty
};
}
protected override TExternal ConvertOutbound(TInternal internalItem)
{
return new Models.v1.MyModel
{
Id = internalItem.Id,
MyProperty = internalItem.MyNewProperty
};
}
}
}
namespace Models.v2
{
public class MyModel
{
public int Id { get; set; }
public string MyNewProperty { get; set; }
}
public class MyModelRepository : DbRepository<MyModel>
{
}
}
Now the v1 ApiController can use the v1 MyModelRepository, the v2 ApiController can use the v2 MyModelRepository, but in the end all requests utilize a database that has been migrated to v2.
I think it is a good practice to evolve the Web API and the underline DB model separately (or EF model). That means a DTO model for the Web API, which is mapped to the EF Model in the Web API. That layer of indirection will give you the chance to make changes that perhaps only affects the Web API or the EF model. In addition, a new version in Web API might not impact directly in existing EF model. For example, a new version of the Web API that uses a completely different set of tables.
Regards,
Pablo.
Related
I'm new to ASP.NET Boilerplate and Dapper. From the tutorial I got to know that ASP.NET Boilerplate works with Entity Framework Core. Can we use another Data Access Layer instead of EF Core? Is ASP.NET Boilerplate compatible with Dapper?
Sure! you can use Dapper.
Installation
Before you start, you need to install https://www.nuget.org/packages/Abp.Dapper, either EF Core, EF 6.x or the NHibernate ORM NuGet packages into the project you want to use.
Module Registration
First, you need to add the DependsOn attribute for the AbpDapperModule on your application module where you register it
[DependsOn(
typeof(AbpEntityFrameworkCoreModule),
typeof(AbpDapperModule)
)]
public class MyApplicationModule: AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(MyApplicationModule).GetAssembly());
}
}
Entity to Table Mapping
You can configure mappings. For example, the Person class maps to the Person table in the following example:
public class PersonMapper: ClassMapper<Person>
{
public PersonMapper()
{
Table("Persons");
Map(x => x.Roles).Ignore();
AutoMap();
}
}
Sample Application Module:
[DependsOn(
typeof(AbpEntityFrameworkModule),
typeof(AbpDapperModule)
)]
public class SampleApplicationModule: AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(SampleApplicationModule).GetAssembly());
DapperExtensions.SetMappingAssemblies(new List<Assembly> { typeof(SampleApplicationModule).GetAssembly() });
}
}
Usage
After registering AbpDapperModule, you can use the Generic IDapperRepository interface (instead of standard IRepository) to inject dapper repositories.
public class SomeApplicationService : ITransientDependency
{
private readonly IDapperRepository<Person> _personDapperRepository;
private readonly IRepository<Person> _personRepository;
public SomeApplicationService(
IRepository<Person> personRepository,
IDapperRepository<Person> personDapperRepository)
{
_personRepository = personRepository;
_personDapperRepository = personDapperRepository;
}
public void DoSomeStuff()
{
var people = _personDapperRepository.Query("select * from Persons");
}
}
Check out these links:
https://aspnetboilerplate.com/Pages/Documents/Dapper-Integration
https://github.com/aspnetboilerplate/aspnetboilerplate/pull/1854#issuecomment-284511423
https://github.com/aspnetboilerplate/aspnetboilerplate/pull/1854#issuecomment-284639277
Trying to set up a Dapper-based Data Access Layer. ABP.Dapper documentation is confusing and incomplete
2015-01-22 The question is solved by myself. I'd like to leave more codes here to show how it works, and wish it can help others.
Like what the guy wants to here(EF code first: inherited dbcontext creates two databases), I'd like to build a DbContext design for reusable class libraries as well.
The design sounds like this:
1. For each class library(dll, project), there is no dbContext, but an interface, to access entities it needs.
2. For each web application(sln, solution), there is only a single DbContext in the web application (and any web apps want to reuse those libs), which inherits all the interfaces.
3. When app started, the web application pass a dbContext instance to all libs via a static interface(might cause some multi-threads prolem? ), so they can access their entities.
4. The web application's dbContext is responsible for db initializing and seeding.
Background: The web application is called "Scrum"(yes, the scrum of agile), there are 2 reusable libraries called Team and Corporation.
Here are some critical codes:
In class libraries called MFCTeam, there is an entity called Team:
public partial class Team : LeveledItem
{
public static IEnumerable<Team> AllTeams(int? corporationId = null)
{
return MFCTeamDbFactory.TeamDbContext.Teams.Where(i => i.CorporationId == (corporationId??MFCSite.Corporation.CurrentCorporationId));
}
}
public interface ITeamDbContext
{
DbSet<Team> Teams { get; set; }
}
public class MFCTeamDbFactory
{
public static ITeamDbContext TeamDbContext;
}
The method "AllTeams" shows an example of accessing db via the interface. There is no direct reference to any DbContext.
Codes in class library Corporation is quite the same so I skipped it.
Then here are codes from the web application Scrum:
public class ScrumDbContext : DbContext, ITeamDbContext, ICorporationDbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new ScrumDbContextInitializer());
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<LeveledItem>()
.Map<Team>(m => m.Requires("Type").HasValue(ItemType.Team))
.Map<Story>(m => m.Requires("Type").HasValue(ItemType.Story));
}
public DbSet<Team> Teams { get; set; }
public DbSet<Corporation> Corporations {get; set; }
public DbSet<Story> Stories { get; set; }
So, ScrumDbContext inherits both the interfaces.
DbSetTeams is required by interface ITeamDbContext, and DbSet Corporations is required by interface ICorporaitonDbContext, and DbSetStories is one of ScrumDbContext's own attributes (or required by another library).
In global.cs
protected void Application_Start()
{
//...
MFCCorporationDbFactory.CorporationDbContext = new ScrumDbContext();
MFCTeamDbFactory.TeamDbContext = new ScrumDbContext();
ScrumDbContextInitializer.Seed();
}
Possible problems:
Accessing entities via a single static dbContext may lead to infliction. If so I will update the codes above.
Sorry, I just found the answer myself.
It turned out that in the codes:
public static IEnumerable<Team> AllTeams(int? corporationId = null)
{
return MFCTeamDbFactory.TeamDbContext.Teams.Where(i => i.CorporationId == (corporationId??MFCSite.Corporation.CurrentCorporationId));
}
MFCSite.Corporation.CurrentCorporationId called another standalone MFCCorporationDbContext and caused that problem. MFCCorporationDbContext is from another reusable class library.
Now I deleted that db context and implemented it with the same design. It works fine now.
I updated codes in the question to show how it works, and wish it can be an answer for similar questions.
Have anyone been successful in attaching an IoC with OWIN ASP.NET Identity to share the same DbContext as the WebApi (or MVC) application?
I would like that if ASP.Net identity loads the user, it loads in the same dbcontext as used in the rest of the application.
(using AutoFac as IoC - but wouldn't mind an example with other container)
Edit:
06/Jan/2014: Just to be a bit more specific, when the ASP.Net identity attempts to use a DbContext it needs to be the same DbContext instance as the rest of the application. Otherwise it will create two instances of the same DbContext. I am already using the .PerHttpApiRequest() extension.
The problem I found is that when setting up OWIN classes I couldn't find a way to:
1) attach the container to the OWIN pipeline;
2) tell OWIN to resolve the classes it needs using that container so it can share the same scope (i.e. OAUthServerOptions or any other that may contain other dependencies)
Having said that, how can you setup the ASP.Net Identity resolving the two points above?
EDIT: added instantiating the OWIN pipeline as per comment.
As the IdentityDbContext used by ASP.NET Identity inherits from DbContext, you can simply create your own implementation inheriting from IdentityDbContext like so
public class ApplicationContext : IdentityDbContext<ApplicationUser>
{
//additional DbSets
public DbSet<OtherEntity> OtherEntities { get; set; }
}
public class ApplicationUser : IdentityUser
{
//any custom properties you want for your user.
}
public class OtherEntity
{
[Key]
public int Id { get; set; }
}
now as far as using IoC, from a unity perspective, I register an instance of the UserStore like so
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(new InjectionConstructor(typeof(ApplicationContext)));
}
then create a wrapper around the user manager
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store)
{
}
}
and inject that wherever I need a user manager.
As for the OWIN pipeline, you need to instantiate the OAuthAuthorizationProvider through your container, like so.
container.RegisterType<IOAuthAuthorizationServerProvider, ApplicationOAuthProvider>(new InjectionConstructor("self", typeof(ApplicationUserManager)));
then for example in WebApi, you need to simply get the instance from the container in startup.
static Startup()
{
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = (IOAuthAuthorizationServerProvider)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IOAuthAuthorizationServerProvider)),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(5),
AllowInsecureHttp = true,
RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
}
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; }
}
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;
}
}
}
}
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).
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)
{
}
}
}
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
I have a project containing POCO entities. A database context has been created for it using Entity Framework 4.2 and code first. This works fine, but the context needs to be exposed as an OData service which does not work.
Browsing to the OData service gives this error:
The property 'DataSubmissionItems' on type
'Lifecycle.ProgramReportSubmission.Model.ProgramReportSubmission' is
not a valid property. Properties whose types are collection of
primitives or complex types are not supported.
The data service class looks like:
public class ExceptionReportDataService : DataService<ExceptionReportEntitiesContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
config.UseVerboseErrors = true;
}
}
The EF context class looks like:
public class ExceptionReportEntitiesContext : DbContext
{
public DbSet<ExceptionReport> ExceptionReports { get; set; }
public ExceptionReportEntitiesContext()
: base(DynamicConfig.GetAppSettingValue("DB_CONN_STRING_LIFECYCLE"))
{
}
}
The POCO entities look like:
namespace WBRT.ProgramData.Lifecycle.ExceptionReportModel
{
public class ExceptionReport
{
public virtual Guid ExceptionReportID { get; set; }
public virtual Lifecycle.ProgramReportSubmission.Model.ProgramReportSubmission ReportSubmission { get; set; }
}
}
namespace Lifecycle.ProgramReportSubmission.Model
{
public class ProgramReportSubmission
{
public Guid ProgramReportSubmissionId { get; set; }
public virtual ICollection<DataSubmissionItem> DataSubmissionItems { get; set; }
}
public class DataSubmissionItem
{
public Guid DataSubmissionItemId { get; set; }
}
}
What I've tried:
Setting DataServiceKey on the DataSubmissionItem class
Setting ProxyCreationEnabled to false on the ExceptionReportEntitiesContext constructor as well as in the data service
Overriding OnModelCreating and removing the IncludeMetadataConvention
Overriding OnModelCreating and setting modelBuilder.Entity<ProgramReportSubmission.Model.ProgramReportSubmission>().Ignore(prs => prs.DataSubmissionItems);
Note: I can't introduce a dependency on the EntityFramework DLL in the POCO entities project as this affects referencing projects that still run .NET 3.5.
Anyone know how to resolve this error?
THe RTM version of WCF DS doesn't support these kind of properties. But the latest CTP does. http://blogs.msdn.com/b/astoriateam/archive/2011/10/13/announcing-wcf-data-services-oct-2011-ctp-for-net-4-and-silverlight-4.aspx.
On the other hand, the fact that you get such an error probably means that WCF DS doesn't recognize the provider as EF, and istead works with it as with a reflection provider. So even the latest CTP won't really fix that problem.
WCF DS currently only recognizes EF provider if the T in DataService is ObjectContext or derived type. The typical workaround for EF Code First is to define the service as DataService and then override the CreateDataSource method on it and return the ObjectContext implementation from your DbContext. See this article about how to do that (it's about EF 4.1, but I think the same will apply to 4.2 as well): http://social.technet.microsoft.com/wiki/contents/articles/5234.aspx. You can skip down to the part about WCF DS.