65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .NET 中使用 Entity Framework 和 AutoFac 构建 OData REST API

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2019年12月8日

CPOL

7分钟阅读

viewsIcon

14608

这是一篇关于使用 Entity Framework 协作创建 OData REST API 的入门文章。我们还将使用 Autofac 作为我们的 IOC 容器。此外,还将使用 Repository 和 Unit of Work 模式,通过我们的 ORM (Entity Framework) 更清晰地访问持久化模型。

引言

传统的 REST API 方法在定制 CRUD 操作方面灵活性不高。这会导致在 API 的控制器中添加适合我们需求的自定义方法。但是,当涉及到 OData REST API 时,有些功能是开箱即用的,我们可以直接从“URL”使用它们,而无需在控制器中编写单独的操作方法。这可以节省大量时间并减少代码量。由于每种技术都有其优缺点。我不会说 OData 是 REST API 开发的理想选择。您应该始终考虑实际情况并做出务实的决定。但是,我的目标是向您展示如何与 Entity Framework 6 协作创建 OData REST API。

我们还将使用 Autofac 作为我们的 IOC 容器。

我们还将实现 Repository 和 Unit of Work 模式,以便通过我们的 ORM 更清晰地访问持久化模型(数据库)。

背景

  • OData:OData 或 Open Data Protocol 是一个开放协议,它允许以简单标准的方式创建和使用可查询、可互操作的 RESTful API。
  • Rest API:它是一个命名或表述性状态转移应用程序编程接口,它使用 HTTP 动词,如 Get 用于获取信息,Put/Patch 用于更新信息,Delete 用于删除,等等。
  • IOC:IoC 容器(或依赖注入容器)是实现自动依赖注入的框架。这类 IOC 容器的示例包括 Autofac、Castle Windsor、Unity 等。
  • ORM:对象关系映射 (ORM) 是一种技术,它允许您使用面向对象的范例从数据库中查询和操作数据。
  • MVC:一种架构风格,有助于区分 Model(“业务模型和规则”)、Controller(“根据路由接收的请求操作这些模型”)和 Views(表示层)。
  • Repository 和 Unit of Work 模式:它是数据库表和 Entity Framework 之间的桥梁。它(Repository 模式)给人一种数据库表只是一个属性或内存对象的错觉。它还有助于将您的应用程序与数据存储(Unit of Work)的变化隔离开来。它通过我们的 ORM 提供对持久化模型(数据库)的更清晰访问。有些人不倾向于使用这种模式,而完全依赖 Entity Framework 的 datacontext 类。嗯,这是一个主观话题,但我个人认为 Repository 和 Unit of Work 为数据中心操作提供了一种更清晰的方式。

开始吧

第一步:初始设置

打开 Visual Studio(我使用的是 VS 2019),创建一个名为 ODataRestApiWithEntityFramework 的 ASP.NET Web 应用程序,您也可以为解决方案保留相同的名称。

选择项目模板为 WebApi。

第二步:创建一些基础设施

添加一个名为“Infrastructure”的解决方案文件夹。在此文件夹中,添加四个类库项目,名称如下:

  • OData.Business:这将包含我们的领域类。
  • OData.InternalDataService:这将是我们的内部通信通道或数据服务。
  • OData.IOC:我们将用于依赖注入。
  • OData.ORM:这将用于 Entity Framework 相关操作。

您的完整解决方案结构应如下所示:

第三步:添加模型文件

OData.Business 中创建 DomainClasses 文件夹,并添加所有模型文件,如 Project.csProjectDetail.cs

Project.cs

using System.ComponentModel.DataAnnotations;

namespace OData.Business.DomainClasses
{
    public class Project
    {
        [Key]
        [Required]
        public long Id { get; set; }

        [Required]
        public string ProjectNumber { get; set; }

        [Required]
        [MaxLength(300)]
        public string ProjectName { get; set; }

        public virtual ProjectDetail Detail { get; set; }
    }
}

ProjectDetail.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace OData.Business.DomainClasses
{
    public class ProjectDetail
    {
        [Key, ForeignKey("Project")]
        [Required]
        public long Id { get; set; }

        public Project Project { get; set; }

        public string TechnologiesUsed { get; set; }

        public string ManagerName { get; set; }

        public string Description { get; set; }

        public int TeamSize { get; set; }

        public double PlannedBudget { get; set; }
    }
}

第四步:添加 Db Context

OData.ORM 中,添加一个名为 Context 的文件夹。在其中添加名为 ODataDbContext.cs 的文件。

同时,安装 Entity Framework 的 nuget 包。

using OData.Business.DomainClasses;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OData.ORM.Context
{
    public class ODataDbContext : DbContext
    {
        public ODataDbContext() : base("ODataDbContext") { }
        public DbSet<Project> Projects { get; set; }
        public DbSet<ProjectDetail> ProjectDetails { get; set; }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    }
}

第五步:添加 ConnectionString

<connectionStrings>
    <add name="ODataDbContext" connectionString="Data Source = .\SQL14; 
     Initial Catalog=ODataDb;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>

第六步:添加 Migration 并更新数据库

我使用的是 MS SQL Server。请确保您已安装 SQL Server。您也可以使用 SQL Lite,但需要相应地修改连接字符串等。

打开程序包管理器控制台(工具 => NuGet 程序包管理器 => 程序包管理器控制台)。

OData.ORM 中依次输入以下命令:

PM> enable-migrations

enable-migrations 将启用迁移,以支持应用程序开发过程。这将在 OData.ORM 中创建一个 Configuration.cs 文件和一个 Migration 文件夹。

现在,添加迁移(如果您从 github 获取了我的代码,只需执行 enable-migrationupdate-database)。

PM> add-migration "InitialScaffolding"

现在,使用迁移更新数据库

PM> update-database

这将创建一个名为 ODataDb 的数据库,其中包含 ProjectsProjectDetails 表。

注意:出于某种原因,放在 OData.ORMapp.config 文件里的 connectionString 没有被识别,所以我不得不将其放在 ODataRestApiWithEntityFrameworkweb.config 文件中。如果它在 OData.ORM 中对您起作用,那么就使用它。

第七步:在 OData.ORM 中实现 Repository 和 Unit Of Work 模式

添加接口 IGenericRepository.cs 及其实现类 GenericRepository.cs

对于 Repository 和 Unit of Work 模式,我们遵循一种通用方法,以便我们的数据服务可以使用它处理任何类型的实体(数据表类型)。

注意:请参阅我的 github 链接以确保准确性。

IGenericRepository.cs

public interface IGenericRepository<TEntity> : IDisposable where TEntity : class
    {
        TEntity Get<TDataType>(TDataType id) where TDataType : struct;

        TEntity Get<TDataType>(TDataType id
            , Expression<Func<TEntity, object>> includes
            , Expression<Func<TEntity, bool>> predicate) where TDataType : struct;

        IEnumerable<TEntity> GetAll();
        
        IEnumerable<TEntity> GetAll(Expression<Func<TEntity, object>> includes);
        
        IEnumerable<TEntity> GetAll(Expression<Func<TEntity, object>> includes, 
                             Expression<Func<TEntity, bool>> predicate);
        
        void Add(TEntity entity);
        
        void AddRange(IEnumerable<TEntity> entities);
        
        void Update(TEntity entity);
        
        void Remove(TEntity entity);
    }

GenericRepository.cs

public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
    {
        protected readonly DbContext Context;
        private bool _disposed;

        public GenericRepository(DbContext context)
        {
            Context = context;
        }

        public virtual void Dispose(bool disposing)
        {
            if (!_disposed)
                if (disposing)
                    Context.Dispose();

            _disposed = true;
        }

        public TEntity Get<TDataType>(TDataType id) where TDataType : struct
        {
            return Context.Set<TEntity>().Find(id);
        }

        public TEntity Get<TDataType>(TDataType Id
            , Expression<Func<TEntity, object>> includes
            , Expression<Func<TEntity, bool>> predicate) where TDataType : struct
        {
            return Context.Set<TEntity>().Include(includes).SingleOrDefault(predicate);
        }

        public IEnumerable<TEntity> GetAll()
        {
            return Context.Set<TEntity>().ToList();
        }

        public IEnumerable<TEntity> GetAll(Expression<Func<TEntity, object>> includes)
        {
            return Context.Set<TEntity>().Include(includes).ToList();
        }

        public IEnumerable<TEntity> GetAll(Expression<Func<TEntity, object>> includes, 
               Expression<Func<TEntity, bool>> predicate)
        {
            return Context.Set<TEntity>().Include(includes).Where(predicate).ToList();
        }

        public void Add(TEntity entity)
        {
            Context.Set<TEntity>().Add(entity);
        }

        public void AddRange(IEnumerable<TEntity> entities)
        {
            Context.Set<TEntity>().AddRange(entities);
        }

        public virtual void Update(TEntity entity)
        {
            this.Context.Entry<TEntity>(entity).State = 
                                    System.Data.Entity.EntityState.Modified;
        }
        public void Remove(TEntity entity)
        {
            Context.Set<TEntity>().Attach(entity);
            Context.Entry(entity).State = EntityState.Deleted;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

IUnitOfWork.cs

public interface IUnitOfWork : IDisposable
    {
        void SaveChanges();
        Task<int> SaveChangesAsync();
        IGenericRepository<T> Repository<T>() where T : class;
    }

UnitOfWork.cs

public class UnitOfWork : IUnitOfWork
    {
        private bool _disposed;
        private readonly DbContext _context;
        private Hashtable _repositories;

        public UnitOfWork(DbContext context)
        {
            _context = context;

        }

        public void SaveChanges()
        {
            _context.SaveChanges();
        }

        public async Task<int> SaveChangesAsync()
        {
            return await _context.SaveChangesAsync();
        }

        public IGenericRepository<T> Repository<T>() where T : class
        {
            if (_repositories == null)
                _repositories = new Hashtable();

            var type = typeof(T).Name;

            if (!_repositories.ContainsKey(type))
            {
                var repositoryType = typeof(GenericRepository<>);
                var repositoryInstance = Activator.CreateInstance
                     (repositoryType.MakeGenericType(typeof(T)), _context);
                _repositories.Add(type, repositoryInstance);
            }

            return (IGenericRepository<T>)_repositories[type];
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual void Dispose(bool disposing)
        {
            if (!_disposed)
                if (disposing)
                    _context.Dispose();

            _disposed = true;
        }
    }

第八步:在 OData.IOC 中添加 IOC 容器 Autofac

OData.IOC 中安装 Autofac nuget 包。

添加 2 个文件:DataServiceModule.csOrmModule.cs

public class DataServiceModule : Module
    {
        public DataServiceModule(){ }

        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<ProjectRepository>().As<IProjectRepository>();
            builder.RegisterType<ProjectDetailRepository>().As<IProjectDetailRepository>();
            
            base.Load(builder);
        }
    }
public class OrmModule : Module
    {
        public OrmModule(){ }

        protected override void Load(ContainerBuilder builder)
        {
            builder.Register(c => new ODataDbContext()).As<DbContext>();

            builder.RegisterGeneric
              (typeof(GenericRepository<>)).As(typeof(IGenericRepository<>));
            builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
            
            base.Load(builder);
        }
    }

另外,在项目 ODataRestApiWithEntityFramework 中添加 AutoFacConfig.cs

public class AutofacConfig
    {
        public static void ConfigureContainer(HttpConfiguration config)
        {
            var builder = new ContainerBuilder();

            // Register your Web API controllers.
            builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

            // OPTIONAL: Register the Autofac filter provider.
            builder.RegisterWebApiFilterProvider(config);

            // Register dependencies in controllers/
            // WebApiApplication is a class inside Global.asax.cs
            builder.RegisterControllers(typeof(WebApiApplication).Assembly);

            // Register dependencies in filter attributes
            builder.RegisterFilterProvider();

            // Register our Data dependencies
            builder.RegisterModule(new DataServiceModule());

            //Register our Service dependencies
            builder.RegisterModule(new OrmModule());

            builder.RegisterHttpRequestMessage(config);

            var container = builder.Build();

            config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

            // Set MVC DI resolver to use our Autofac container
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        }
    }

修改项目 ODataRestApiWithEntityFrameworkGlobal.asax 文件。

public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AutofacConfig.ConfigureContainer(GlobalConfiguration.Configuration);
            GlobalConfiguration.Configure(WebApiConfig.Register);
        }
    }

第九步:在 OData.InternalDataService 中添加内部数据服务(Repository 服务)

添加 Service.cs,它将作为我们 UnitOfWork 的基础。

public class Service<TEntity> where TEntity : class
    {
        protected readonly IUnitOfWork _unitOfWork;

        public Service(IUnitOfWork unitOfWork)
        {
            this._unitOfWork = unitOfWork;
        }
    }

为了访问我们的数据表,如 ProjectsProjectDetails 等,我们将利用我们的通用 Repository。为此,请添加类,如 ProjectRepository.cs(此类将模拟与数据库中的 Project 表进行操作)。

ProjectRepository.cs

public class ProjectRepository : Service<Project>, IProjectRepository
    {
        private readonly IGenericRepository<Project> _ProjectRepository;

        public ProjectRepository(IUnitOfWork unitOfWork)
            : base(unitOfWork)
        {
            this._ProjectRepository = unitOfWork.Repository<Project>();
        }

        public Project GetProject(long id)
        {
            return _ProjectRepository.Get(id,
                proj => proj.Detail, proj => proj.Id == id);
        }

        public IEnumerable<Project> GetProjects()
        {
            return _ProjectRepository.GetAll();
        }

        public void AddProject(Project project)
        {
            _ProjectRepository.Add(project);
            _unitOfWork.SaveChanges();
        }

        public void UpdateProject(Project project)
        {
            this._ProjectRepository.Update(project);
            _unitOfWork.SaveChanges();
        }

        public void RemoveProject(Project project)
        {
            this._ProjectRepository.Remove(project);
            _unitOfWork.SaveChanges();
        }
    }

注意:我在 github 项目中也添加了额外的 Repository 类(ProjectDetailRepository.cs),您可以参考它以获取更多信息。

IProjectRepository.cs

public interface IProjectRepository
    {
        void AddProject(Project project);
        Project GetProject(long id);
        IEnumerable<Project> GetProjects();
        void RemoveProject(Project project);
        void UpdateProject(Project project);
    }

第十步:在项目 "ODataRestApiWithEntityFramework" 中添加 OData

请安装 Microsoft.AspNet.ODataMicrosoft.AspNet.WebApi.OData 等 nuget 包。

将您的 WebApiConfig.cs 配置如下:

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapODataServiceRoute("odata", null, GetEdmModel(), 
                   new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
            config.EnsureInitialized();

        }

        private static IEdmModel GetEdmModel()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder
            {
                Namespace = "ODataRestAPI",
                ContainerName = "DefaultContainer"
            };

            builder.EntitySet<Project>("Project");
            builder.EntitySet<ProjectDetail>("ProjectDetail");

            return builder.GetEdmModel();
        }
    }

注意:请确保不要混用 OData V4 和 OData V3 包。否则,这会导致数据显示在浏览器或客户端时出现问题,并且不会收到错误。

另外,请确保如果您有 ODate V3/V4 包,那么 webapiconfig.cs 和 API 控制器中的命名空间应该相同。

如果出现 Entity Framework 错误,请在项目 ODataRestApiWithEntityFramework 中也安装 Entity Framework

第十一步:最后,添加 ProjectController.cs 文件,其中包含 RESTful 方法

public class ProjectController : ODataController
    {
        private readonly IProjectRepository _projectRepository;

        public ProjectController(IProjectRepository projectRepository)
        {
            _projectRepository = projectRepository;
        }

        // GET: localhost/Project
        [EnableQuery]
        [HttpGet]
        public IHttpActionResult Get()
        {
            var projects = _projectRepository.GetProjects();
            return Ok(projects.ToList());
        }

        // GET: localhost/Project(1)
        [EnableQuery]
        [HttpGet]
        public IHttpActionResult Get(long key)
        {
            Project temp = null;
            try
            {
                var resource = _projectRepository.GetProject(key);
                temp = resource;
            }
            catch (ArgumentNullException)
            {
                return BadRequest();
            }
            catch (ArgumentException)
            {
                return BadRequest();
            }
            catch (InvalidOperationException)
            {
                return Conflict();
            }
            return Ok(temp);
        }

        [HttpPost]
        public IHttpActionResult Post(Project project)
        {
            try
            {
                _projectRepository.AddProject(project);
            }
            catch (ArgumentNullException)
            {
                return BadRequest();
            }
            catch (ArgumentException)
            {
                return BadRequest();
            }
            catch (InvalidOperationException)
            {
                return Conflict();
            }

            return Ok("Record saved successfully");
        }

        [HttpPut]
        public IHttpActionResult Put(Project project)
        {
            try
            {
                _projectRepository.UpdateProject(project);
            }
            catch (ArgumentNullException)
            {
                return BadRequest();
            }
            catch (ArgumentException)
            {
                return BadRequest();
            }
            catch (InvalidOperationException)
            {
                return Conflict();
            }

            return Ok("Record updated successfully");
        }

        [HttpDelete]
        public IHttpActionResult Delete(Project project)
        {
            try
            {
                _projectRepository.RemoveProject(project);
            }
            catch (ArgumentNullException)
            {
                return BadRequest();
            }
            catch (ArgumentException)
            {
                return BadRequest();
            }
            catch (InvalidOperationException)
            {
                return Conflict();
            }

            return Ok("Record deleted successfully");
        }
    }

使用 PostMan 测试 OData REST API

为了方便测试,请手动向 ProjectsProjectDetails 表添加一些记录,或者使用 Configuration.cs 中的 seed 方法。但是,建议您使用 Post 请求添加记录。Post 请求通常需要一个对象,因此建议使用 PostMan 或类似的框架来完成这项工作。

构建整个解决方案并运行项目 ODataRestApiWithEntityFramework

您将在屏幕上看到以下输出:

{
  "@odata.context":"https://:44385/$metadata","value":[
    {
      "name":"Project","kind":"EntitySet","url":"Project"
    }
  ]
}

现在,要获取 Project 表的所有记录,请使用 https://:44385/Project

要获取 Project 表中的特定记录,请使用 https://:44385/Project(1)

要获取 Project 中的内部已知属性(如果有),请使用 https://:44385/Project?$expand=Detail

您可以尝试 Post/Put/Delete 请求。


此外,还有各种 OData 查询选项,一些常见的如下:

  • $top:可用于从数据存储中检索前 n 条记录。
  • $orderby:可用于按升序或降序对数据进行排序。
  • $filter:可用于基于某个条件过滤数据。
  • $skip:可与 $orderby 一起使用,以跳过特定数量的记录。

Github 链接

您也可以从我的 github 页面下载源代码:https://github.com/SunnyMakode/OData.NetFramework.git

看点

  1. 如果您使用的是 dot net core,那么上述步骤可以减少,因为它现在支持依赖注入。
  2. 我们投入了大量时间在基础设施上,因为使此应用程序尽可能松耦合是核心思想,以便可以对其进行扩展和单元测试。
  3. 建议我们应为操作方法使用异步调用。
  4. GraphQL 与 OData 具有一定的相似性,因为它们都支持基于 URL 查询的操作。
  5. 必须特别注意,我们不要混用任何 OData v4 或 OData V3 的包。您的服务器端应用程序不会崩溃,但是,由于不一致,您的浏览器将无法接受数据。
  6. 确保 webapiconfig.cs 和 API 控制器具有与 OData 相同的命名空间。如果您将其与 OData v4 和 OData V3 混淆,则可能会出现第 5 点所述的问题,并且没有合适的文档可用。
  7. 如果您的客户端应用程序正在使用此 API,但它不在您的 REST API 的同一域中,请考虑在您的 API 应用程序中添加 CORS 支持。
  8. OData 不仅限于 MVC Web API,它还可以与 RESTful WCF 等一起使用。

历史

  • 2019年12月8日:初始版本
© . All rights reserved.