【问题标题】:Ignoring a JSON property in POST with oData使用 oData 忽略 POST 中的 JSON 属性
【发布时间】:2018-02-27 17:43:02
【问题描述】:

我正在尝试使用 EntityFramework 和 OData v4 构建 API。

问题:我需要一些额外的数据 extraProperty,这些数据不在我的数据库中以创建新的 Item,但 oData 无法识别如果我在POST 调用中向我的 JSON 对象添加一些数据,则作为 Item

我使用 EntityFrameWork 所以,根据this question,我尝试在我的模型中使用数据注释[NotMapped].Ignore(t => t.extraProperty);。但 oData 似乎忽略了它。

我从这个帖子中得到的所有信息,这个extraProperty,是:

不支持非开放类型的无类型值。

代码

我在 POST 调用中发送的 JSON:

{
  "name": "John Doe",
  "extraProperty": "Random string"
}

$元数据:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
    <edmx:DataServices>
        <Schema Namespace="MyApi.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
            <EntityType Name="Items">
                <Key>
                    <PropertyRef Name="id" />
                </Key>
                <Property Name="id" Type="Edm.Int32" Nullable="false" />
                <Property Name="name" Type="Edm.String" Nullable="false" />                
            </EntityType>           
        </Schema>
    </edmx:DataServices>
</edmx:Edmx>

ODataConfig.cs

namespace MyApi.App_Start
{
    public class OdataConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Items>("Items");
            config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
            config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
        }
    }
}

Items.cs

[Table("Item.Items")]
public partial class Items
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Items(){}

    public int id { get; set; }

    public string name { get; set; }

    [NotMapped] // I already tried this, it's not working
    public string extraProperty{ get; set; }
 }

MyModel.cs

public partial class MyModel: DbContext
{
    public MyModel()
        : base("name=MyModel")
    {

        Database.SetInitializer<MyModel>(null);
    }

    public virtual DbSet<Items> Items{ get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // I also tried this but not working
        modelBuilder.Entity<Items>()
            .Ignore(e => e.extraProperty);
    }
}

MyController.cs

public class ItemsController : ODataController
{
    private MyModeldb = new MyModel();

    // POST: odata/Items 
    public async Task<IHttpActionResult> Post(Items items)
    {
        // items is always null when enterring here
        // and this condition is always triggered
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        // Do some stuff with extraProperty here

        db.Items.Add(items);
        await db.SaveChangesAsync();

        return Created(items);
    }
}

部分 package.config

<package id="EntityFramework" version="6.2.0" targetFramework="net461" />
<package id="Microsoft.Data.Edm" version="5.8.3" targetFramework="net461" />
<package id="Microsoft.AspNet.OData" version="6.1.0" targetFramework="net45" />
<package id="Microsoft.Data.OData" version="5.8.3" targetFramework="net461" />
<package id="Microsoft.OData.Core" version="7.4.1" targetFramework="net45" />
<package id="Microsoft.OData.Edm" version="7.4.1" targetFramework="net45" />

我也想过做一个拦截器,在调用 post 之前清除我的 json,但是根据this question,Web API OData 不支持查询拦截器...

我该如何处理这个错误并避免它?我真的需要在 POST 方法中处理extraProperty,或者至少在之前处理。

【问题讨论】:

  • 异常消息提示您应该使用Open Types 来处理其他数据。
  • 您是否只尝试过modelBuilder.Entity().Ignore(e => e.extraProperty);没有 [NotMapped] 注释?
  • 另外,您能否在 Fiddler 中捕获 Post 请求并查看是否在 Raw 请求中实际发送了 Items?
  • @hem,raw 很好,我已经尝试过 modelBuilder.Entity().Ignore(e => e.extraProperty);有和没有 NotMapped。感谢您的评论。
  • @hem,我重试了 modelBuilder.Entity().Ignore(e => e.extraProperty);没有 NotMapped,它终于奏效了。你能把这个作为答案发布吗,所以我会给你赏金。

标签: c# entity-framework api odata


【解决方案1】:

在您的Items 类中,删除[NotMapped] 属性

public string extraProperty{ get; set; }

并将以下代码留在您的 MyModel 类中

modelBuilder.Entity<Items>()
            .Ignore(e => e.extraProperty);

[NotMapped] 属性告诉OData 在序列化和反序列化 Items 类时忽略 extraProperty。但是由于你想在ItemsControllerPOST请求中使用它,所以在这种情况下你不能使用[NotMapped]属性,所以Model Binding是你想要的。

【讨论】:

  • 我发现这仍然不起作用,当我这样做并且您执行 POST 时,您会收到以下模型验证错误:The property 'x' does not exist on type 'model'. Make sure to only use property names that are defined by the type..Ignore 似乎也将其从模型中完全删除。
【解决方案2】:

根据您要使用“额外数据”的目的,为您的 post 方法使用输入模型,对数据执行您想要的操作,然后填充正确的 EF 模型属性,这不是更简单吗.如果 Annotations 和 FluentAPI 不适合您,那将是最简单的解决方案

public partial class ItemsInput
{
    public int id { get; set; }
    public string name { get; set; }
    public string extraProperty{ get; set; }
}
 
public async Task<IHttpActionResult> Post(ItemsInput itemsInput)
{
    // This shouldn't be triggered anymore unless it's a valid error
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // Do some stuff with extraProperty here
    
    //Convert the input object to json string
    var itemsInputJson = JsonConvert.SerializeObject(itemsInput);
    //Load json string to the EF Model, this will fill up all compatible
    //properties and ignore non-matching ones
    Items items = JsonConvert.DeserializeObject<Items>(itemsInputJson);
    db.Items.Add(items);
    await db.SaveChangesAsync();

    return Created(items);
}

【讨论】:

  • 您好,感谢您的回答。我考虑了这个解决方案一段时间,但我有很多属性,重建正确的 EF 模型将是丑陋和肮脏的......
  • 没有问题只是想帮忙。通过重建,您的意思是将属性从输入模型转移到 EF 模型?您还可以使用 newtonsoft json 库来序列化和反序列化对象。如果目标对象中不存在该属性,则将其忽略。这可以使 EF 模型的重建“更干净”
  • 我编辑了我的答案,以展示您如何通过 newtonsoft 进行重建。对此的处理开销应该可以忽略不计
【解决方案3】:

FluentAPI 方式有效(经过多次测试)。 你能提供你的$元数据吗? 再次尝试删除 NotMapped 属性并在模型构建器上添加 Ignore。

或者,您可以在 GetEdmModel 方法中将此属性添加到 IEdmModel:

model.StructuralTypes.First(t => t.ClrType == typeof(Items)).AddProperty(typeof(Items).GetProperty("extraProperty"));

【讨论】:

  • 感谢您的回答。我再次尝试了方法、注释和流利的 API,但我仍然得到错误。我还将我的 $metadata 和我的 OdataConfig.cs 添加到我的帖子中。找不到您说的 IEdModel 在哪里。
  • 在OdataConfig->注册它的builder :)
【解决方案4】:

您可以使用 AutoMapper 将所有内容映射到 DTO,然后在控制器中手动应用 QueryOptions。

注意:记得包含

使用自动映射器;

使用 AutoMapper.QueryableExtensions;

public class ItemDTO 
{
     public int Id { get; set;}
     public string Name { get; set;}
     public string CustomProperty { get; set; }
}

public class ItemsController : ApiController
{
    MyCustomContext _context;
    public ItemsController(MyCustomContext context)
    {
        _context = context;
    }

    public IEnumerable<ItemDTO> Get(ODataQueryOptions<Item> q)
    {
       var itemsQuery = _context.Items.AsQueryable();
       itemsQuery = q.ApplyTo(itemsQuery , new ODataQuerySettings()) as IQueryable<Item>;
       
       var mapperConfiguration = this.GetMapperConfiguration();
       return itemsQuery.ProjectTo<ItemDTO>(mapperConfiguration);
    }

    public IConfigurationProvider GetMapperConfiguration()
    {
        return new MapperConfiguration(x => x.CreateMap<Item, ItemDTO>().ForMember(m => m.CustomProperty, o => o.MapFrom(d => d.Id + "Custom")));
    }
}

注意:您必须使用MapFrom 方法进行映射,而不能使用ResolveUsing

【讨论】:

    猜你喜欢
    • 2017-11-11
    • 2013-07-26
    • 1970-01-01
    • 2021-01-20
    • 2022-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-24
    相关资源
    最近更新 更多