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





5.00/5 (2投票s)
这是一篇关于使用 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.cs 和 ProjectDetail.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-migration
和 update-database
)。
PM> add-migration "InitialScaffolding"
现在,使用迁移更新数据库
PM> update-database
这将创建一个名为 ODataDb
的数据库,其中包含 Projects
和 ProjectDetails
表。
注意:出于某种原因,放在 OData.ORM
中 app.config 文件里的 connectionString
没有被识别,所以我不得不将其放在 ODataRestApiWithEntityFramework
的 web.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.cs 和 OrmModule.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));
}
}
修改项目 ODataRestApiWithEntityFramework
的 Global.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;
}
}
为了访问我们的数据表,如 Projects
、ProjectDetails
等,我们将利用我们的通用 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.OData
和 Microsoft.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
为了方便测试,请手动向 Projects
和 ProjectDetails
表添加一些记录,或者使用 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。
看点
- 如果您使用的是 dot net core,那么上述步骤可以减少,因为它现在支持依赖注入。
- 我们投入了大量时间在基础设施上,因为使此应用程序尽可能松耦合是核心思想,以便可以对其进行扩展和单元测试。
- 建议我们应为操作方法使用异步调用。
- GraphQL 与 OData 具有一定的相似性,因为它们都支持基于 URL 查询的操作。
- 必须特别注意,我们不要混用任何 OData v4 或 OData V3 的包。您的服务器端应用程序不会崩溃,但是,由于不一致,您的浏览器将无法接受数据。
- 确保 webapiconfig.cs 和 API 控制器具有与 OData 相同的命名空间。如果您将其与 OData v4 和 OData V3 混淆,则可能会出现第 5 点所述的问题,并且没有合适的文档可用。
- 如果您的客户端应用程序正在使用此 API,但它不在您的 REST API 的同一域中,请考虑在您的 API 应用程序中添加 CORS 支持。
- OData 不仅限于 MVC Web API,它还可以与 RESTful WCF 等一起使用。
历史
- 2019年12月8日:初始版本