简体   繁体   English

使用ASP.NET Web API和实体框架进行API版本控制

[英]API Versioning with ASP.NET Web API and Entity Framework

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. 我正在使用ASP.NET Web API,Code-First Entity Framework 5和SQL Server 2012开发REST API,我需要能够对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. 我已经阅读了一些博客文章和文章,关于在URI或自定义HTTP标头中指示API版本,并使用自定义IHttpControllerSelector根据指定的版本选择不同的ApiControllers。 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. 我正在努力弄清楚的是如何管理Web API层以外的版本控制的影响,特别是在Entity Framework中。 How do I go about evolving my DbContext without breaking older versions of the API? 如何在不破坏旧版本API的情况下改进我的DbContext? Can I version the DbContext as well? 我也可以对DbContext进行版本控制吗? And if so, how? 如果是这样,怎么样?

What I ended up doing was combining the Repository Pattern with Pablo's answer. 我最终做的是将Repository Pattern与Pablo的答案结合起来。 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. 它的要点是我的EF模型是版本化的,我使用EF Code-First Migrations将数据库迁移到新版本的模型,我的DbContext始终使用最新版本的模型,我开发了许多具体的存储库每个都实现下面的IRepository<TItem>接口。

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. IRepository<TItem>一个实现是DbRepository<TItem> ,它包装用于与数据库通信的实体框架代码。

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. IRepository<TItem>另一个实现是TypeConversionRepository<TExternal,TInternal> ,它是一个抽象类,便于从一种模型类型转换为另一种模型类型。

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. 返回模型或接受模型作为参数的方法使用ConvertInbound()ConvertOutbound()TExternal类型的模型转换为TInternal ,反之亦然。 Therefore, given the following 2 versions of MyModel , we can write 2 versions of MyModelRepository; 因此,给定以下2个版本的MyModel ,我们可以编写2个版本的MyModelRepository; version 2 can talk directly to the database while version 1 will need to convert from version 2 back to version 1. 版本2可以直接与数据库通信,而版本1需要从版本2转换回版本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. 现在v1 ApiController可以使用v1 MyModelRepository,v2 ApiController可以使用v2 MyModelRepository,但最后所有请求都使用已迁移到v2的数据库。

I think it is a good practice to evolve the Web API and the underline DB model separately (or EF model). 我认为分别开发Web API和下划线数据库模型(或EF模型)是一种很好的做法。 That means a DTO model for the Web API, which is mapped to the EF Model in the Web API. 这意味着Web API的DTO模型,它映射到Web API中的EF模型。 That layer of indirection will give you the chance to make changes that perhaps only affects the Web API or the EF model. 该间接层将使您有机会进行可能仅影响Web API或EF模型的更改。 In addition, a new version in Web API might not impact directly in existing EF model. 此外,Web API中的新版本可能不会直接影响现有的EF模型。 For example, a new version of the Web API that uses a completely different set of tables. 例如,Web API的新版本使用完全不同的表集。

Regards, Pablo. 此致,巴勃罗。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM