【发布时间】:2020-08-23 12:52:51
【问题描述】:
无论我尝试什么,我都无法让 OData 7.3.0 使用像 https://localhost:44316/odata/Widget(5) 这样的简单 URL 返回单个资源...
重现步骤:
我创建了一个名为“WidgetDB”的数据库是 SQL Server。
我使用以下 SQL 脚本添加了包含一些数据的单个表:
create table widget
(
widget_id int identity(1, 1) not null,
widget_name varchar(100) not null,
constraint PK_widget primary key clustered (widget_id)
)
GO
insert into widget (widget_name)
values
('Thingamabob'), ('Thingamajig'), ('Thingy'),
('Doomaflotchie'), ('Doohickey'), ('Doojigger'), ('Doodad'),
('Whatchamacallit'), ('Whatnot'), ('Whatsit'),
('Gizmo'), ('Nicknack')
GO
- 在 Visual Studio 2019 中,我使用 ASP.Net Core 3.1 创建了一个名为“WidgetWebAPI”的新 Web API 解决方案。
-
我为以下内容添加了 Nuget 包:
- Microsoft.EntityFrameworkCore 3.1.3
- Microsoft.EntityFrameworkCore.SqlServer 3.1.3
- Microsoft.EntityFrameworkCore.Tools 3.1.3
- Microsoft.Extensions.DependencyInjection 3.1.3
- Microsoft.AspNetCore.OData 7.4.0
我从 Visual Studio 创建的默认脚手架项目中删除了 Weatherforecast.cs 类和 WeatherforecastController.cs 类。
-
我转到包管理器控制台并输入以下行来为实体框架核心构建一个 DbContext:
PM> Scaffold-DbContext -Connection "Server=.;Database=WidgetDB;Trusted_Connection=True;" -Provider Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models Build started... Build succeeded. PM> - 我打开 appsettings.json 文件并添加了
ConnectionStrings部分:
{
"ConnectionStrings": {
"Default": "Server=.;Database=WidgetDB;Trusted_Connection=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
- 我打开第6步创建的Models\WidgetDBContext.cs文件,取出
OnConfiguring方法:
using Microsoft.EntityFrameworkCore;
namespace WidgetWebAPI.Models
{
public partial class WidgetDBContext : DbContext
{
public WidgetDBContext() { }
public WidgetDBContext(DbContextOptions<WidgetDBContext> options) : base(options) { }
public virtual DbSet<Widget> Widget { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Widget>(entity =>
{
entity.ToTable("widget");
entity.Property(e => e.WidgetId).HasColumnName("widget_id");
entity.Property(e => e.WidgetName)
.IsRequired()
.HasColumnName("widget_name")
.HasMaxLength(100)
.IsUnicode(false);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}
- 我打开了 Startup.cs 文件,并拼凑了我能找到的完整示例,我将代码清理为以下内容:
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OData.Edm;
using WidgetWebAPI.Models;
namespace WidgetWebAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// See note on https://devblogs.microsoft.com/odata/experimenting-with-odata-in-asp-net-core-3-1/
// Disabling end-point routing isn't ideal, but is required for the current implementation of OData
// (7.4.0 as of this comment). As OData is further updated, this will change.
//services.AddControllers();
services.AddControllers(mvcOoptions => mvcOoptions.EnableEndpointRouting = false);
services.AddDbContext<Models.WidgetDBContext>(optionsBuilder =>
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("Default"));
}
});
services.AddOData();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
// Again, this is temporary due to current OData implementation. See note above.
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapControllers();
//});
app.UseMvc(routeBuilder =>
{
routeBuilder.MapODataServiceRoute("odata", "odata", GetEdmModel());
});
}
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "WidgetData"; // Hide Model Schema from $metadata
builder.EntitySet<Widget>("Widgets").EntityType
.HasKey(r => r.WidgetId)
.Filter() // Allow for the $filter Command
.Count() // Allow for the $count Command
.Expand() // Allow for the $expand Command
.OrderBy() // Allow for the $orderby Command
.Page() // Allow for the $top and $skip Commands
.Select(); // Allow for the $select Command;
return builder.GetEdmModel();
}
}
}
-
A 通过右键单击 Controllers 文件夹并选择 Add Controller... 创建了类 Controllers\WidgetsController.cs,然后在向导对话框中选择 API Controller with actions, using Entity Framework 选项:
这是我的第一个错误。参见第 13 步。
我将
[EnableQuery]属性添加到脚手架创建的Controller类的GetWidget()方法中,并将类继承从ControllerBase更改为ODataController。除了确保我的命名空间得到正确解析外,我没有对现有文件做任何其他事情。我更改了调试设置,将 URL 设置为 odata/Widgets 而不是 weatherforecast,然后运行了应用程序。
没有任何效果! 经过数小时的诅咒、困惑和反复试验,我终于发现默认情况下 OData 讨厌复数命名对象和控制器。
- (或 9 次修订)我回到我的 Startup.cs 类并将这行代码更改为使用单数形式:
builder.EntitySet<Widget>("Widget").EntityType
(或 10 次修订)我再次运行 添加控制器... 向导,这一次,我将控制器名称设置为
WidgetController,然后重新应用步骤中提到的更改11.我将项目属性中的 Launch Browser Debug 设置更新为 odata/Widget 并再次运行应用程序:
所有小部件都返回了,所以我们取得了进展!
但是,任何使用格式良好的 OData Url(例如 https://localhost:44316/odata/Widget(4))获取单个实体的尝试都只会返回整个数据集,而不是 Id 为 4 的单个实体。事实上,SQL Profiler 跟踪显示构造的 SQL 查询除了从整个表中选择之外不包含任何内容:
SELECT [w].[widget_id], [w].[widget_name]
FROM [widget] AS [w]
我浏览了整个互联网,但我的 Google Fu 让我失望了。我找不到这不起作用的原因,也找不到一个当前示例来说明它在哪里工作以及我缺少什么!我可以找到许多演示 $filter、$expand 等的示例,但没有一个仅从集合中返回单个实体的示例。
我尝试过更改方法签名之类的方法。这也没有效果:
[HttpGet]
[EnableQuery]
public IQueryable<Widget> GetWidget() => _context.Widget.AsQueryable();
[HttpGet("{id}")]
[EnableQuery]
public IQueryable<Widget> GetWidget([FromODataUri] int id) => _context.Widget.Where(r => r.WidgetId == id);
我知道终点是能够返回单个实体。我可以通过输入 URL:https://localhost:44316/odata/Widget?$filter=WidgetId eq 5 来实现它,它工作正常,并且可以适当地导致针对数据库生成正确的 SQL。
【问题讨论】:
-
我也在github.com/mitselplik/widget-web-api分享了演示项目。
标签: c# asp.net-core asp.net-web-api entity-framework-core odata