【问题标题】:How can I POST with navigation properties using Microsoft.OData.Client and Web Api如何使用 Microsoft.OData.Client 和 Web Api 发布导航属性
【发布时间】:2016-05-22 17:19:24
【问题描述】:

我有两个类,Vehicle 和 OwnershipRecord,如果没有另一个,它们都不能持久化到数据库中。 Vehicle 必须至少有一个OwnershipRecord,并且OwnershipRecord 必须与Vehicle 相关联。否则就没有意义了。

使用 Web Api 和 OData v4 客户端代码生成器我还没有想出一种方法来序列化这两个对象并将它们一起发布。看来我需要发布车辆然后添加 OwnershipRecord 或发布 OwnershipRecord 然后添加车辆,这是不可能的。

DataServiceContext.AddObject 提供以下内容:

对象被放入处于已添加状态的 DataServiceContext 的跟踪集中。 DataServiceContext 将在下次调用 SaveChanges 时尝试通过 HTTP POST 插入对象。此方法不会将与指定实体相关的对象添加到 DataServiceContext。必须通过单独调用 AddObject 来添加每个对象。

该方法不验证指定的实体集是否在与 DataServiceContext 关联的数据服务中,或者添加的对象是否具有需要添加到指定实体集的所需属性。..

因此,所有导航属性在传递时都为空。因此,当我将 OwnershipRecords 添加到 newVehicle 然后调用 Container.AddToVehicles(newVehicle) 时,VehiclesController 上的 POST 方法将 ModelState.IsValid 呈现为错误的 Vehicle must have an owner!。

如何使用客户端代码发送带有导航属性的车辆并将两者相加?我尝试在Container 上使用AddLink 和AddRelatedObject,但它不适用于相对网址,因为这些项目尚不存在。

public class Vehicle : IValidatableObject
{
    public const string MissingOwnerMessage = "Vehicle must have an owner!";

    public Vehicle()
    {
        OwnershipRecords = new HashSet<OwnershipRecord>();
    }

    [Key]
    public int Id { get; set; }

    public virtual ICollection<OwnershipRecord> OwnershipRecords { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (OwnershipRecords.Count == 0)
        {
            yield return new ValidationResult(MissingOwnerMessage);
        }
    }
}

public class OwnershipRecord : IValidatableObject
{
    public const string MissingOwnerMessage = "Owner is required when creating Ownership-Record!";
    public const string MissingVehicleMessage = "Vehicle is required when creating Ownership-Record!";

    public OwnershipRecord()
    {
        Owners = new HashSet<Entity>();
    }

    [Key]
    public int Id { get; set; }

    [Required]
    public int VehicleId { get; set; }

    [ForeignKey("VehicleId")]
    public virtual Vehicle Vehicle { get; set; }

    public virtual ICollection<Entity> Owners { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Owners.Count == 0)
        {
            yield return new ValidationResult(MissingOwnerMessage);
        }

        if (Vehicle == null)
        {
            yield return new ValidationResult(MissingVehicleMessage);
        }
    }
}

这是我的 WebApiConfig.cs 和我的 ODataControllers。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.EnableEnumPrefixFree(true);
        config.MapODataServiceRoute("nms", "nms", GetImplicitEdmModel(), new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));

        config.EnsureInitialized();
    }

    private static IEdmModel GetImplicitEdmModel()
    {
        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Entity>("Entities");
        builder.EntitySet<Vehicle>("Vehicles");
        builder.EntitySet<OwnershipRecord>("OwnershipRecords");

        builder.Namespace = "LocationService";

        return builder.GetEdmModel();
    }

[ODataRoutePrefix("OwnershipRecords")]
public class OwnershipRecordsController : ODataController
{
    private NirvcModelV2 db = new NirvcModelV2();

    // GET: odata/OwnershipRecords
    [EnableQuery]
    public IQueryable<OwnershipRecord> GetOwnershipRecords()
    {
        return db.OwnershipRecords;
    }

    // GET: odata/OwnershipRecords(5)
    [EnableQuery]
    public SingleResult<OwnershipRecord> GetOwnershipRecord([FromODataUri] int key)
    {
        return SingleResult.Create(db.OwnershipRecords.Where(ownershipRecord => ownershipRecord.Id == key));
    }

    // POST: odata/OwnershipRecords
    public IHttpActionResult Post(OwnershipRecord ownershipRecord)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        db.OwnershipRecords.Add(ownershipRecord);

        return Created(ownershipRecord);
    }

    // GET: odata/OwnershipRecords(5)/Vehicle
    [EnableQuery]
    public SingleResult<Vehicle> GetVehicle([FromODataUri] int key)
    {
        return SingleResult.Create(db.OwnershipRecords.Where(m => m.Id == key).Select(m => m.Vehicle));
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }

}

[ODataRoutePrefix("Vehicles")]
public class VehiclesController : ODataController
{
    private NirvcModelV2 db = new NirvcModelV2();

    // GET: odata/Vehicles
    [EnableQuery(MaxExpansionDepth = 0)]
    public IQueryable<Vehicle> GetVehicles()
    {
        return db.Vehicles;
    }

    // GET: odata/Vehicles(5)
    [EnableQuery(MaxExpansionDepth = 0)]
    public SingleResult<Vehicle> GetVehicle([FromODataUri] int key)
    {
        return SingleResult.Create(db.Vehicles.Where(vehicle => vehicle.Id == key));
    }

    // POST: odata/Vehicles
    public IHttpActionResult Post(Vehicle vehicle)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        db.Vehicles.Add(vehicle);
        db.SaveChanges();

        return Created(vehicle);
    }

    // PATCH: odata/Vehicles(5)
    [AcceptVerbs("PATCH", "MERGE")]
    public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Vehicle> patch)
    {
        Vehicle vehicle = await db.Vehicles.FindAsync(key);
        if (vehicle == null)
        {
            return NotFound();
        }

        patch.Patch(vehicle);

        try
        {
            await db.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!VehicleExists(key))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return Updated(vehicle);
    }

    // GET: odata/Vehicles(5)/OwnershipRecords
    [EnableQuery]
    public IQueryable<OwnershipRecord> GetOwnershipRecords([FromODataUri] int key)
    {
        return db.Vehicles.Where(m => m.Id == key).SelectMany(m => m.OwnershipRecords);
    }

    [HttpPost]
    [ODataRoute("({key})/OwnershipRecords")]
    public async Task<IHttpActionResult> PostOwnershipRecord([FromODataUri] int key, OwnershipRecord ownershipRecord)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        db.OwnershipRecords.Add(ownershipRecord);
        try
        {
            await db.SaveChangesAsync();
        }
        catch (DBConcurrencyException)
        {
            throw;
        }

        return Created(ownershipRecord);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }

    private bool VehicleExists(int key)
    {
        return db.Vehicles.Count(e => e.Id == key) > 0;
    }
}

==更新==

目前,我正在使用JsonConvert 序列化有效负载并使用WebClient 自己发送它。我已经从服务器中删除了所有 ModelState 逻辑。目前似乎不支持在客户端代码中包含导航属性。我可能不完全理解拦截batch 命令,因为如果我可以将expand 与GET 一起使用,我应该能够使用类似于expand 的东西进行POST

【问题讨论】:

    标签: entity-framework asp.net-web-api odata odatacontroller


    【解决方案1】:

    使用 $batch 请求在同一个变更集中发送两个帖子。

    请参阅此示例项目以了解如何支持 $batch,https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataBatchSample

    【讨论】:

    • 根据我对批处理的尝试,它仍然尝试单独保存每个。我需要将 OwnershipRecord 附加到 Vehicle 服务器端,这样调用 db.SaveChanges() 就可以了
    • 你是否尝试使用changeset,同一个changeset中的请求被保存在一次调用中,批处理请求中的每个请求和changeset按协议顺序处理。可以参考docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/…查看请求示例。
    • 我认为我的评论不清楚,问题在于验证。如果我使用container.SaveChanges(SaveChangesOptions.BatchWithSingleChangeset),该命令会发送一个失败的 Vehicle 帖子,以及一个失败的 OwnershipRecord 帖子。我需要在服务器端接收这两个对象,关联它们,然后服务器才能成功调用 SaveChanges()
    【解决方案2】:

    所以我意识到将两个参数一起发送我可以在WebApiConfig.cs 中使用一个动作

    var newVehicleFunction = builder.EntityType<Entity>().Action("AddVehicle").ReturnsFromEntitySet<OwnershipRecord>("OwnershipRecords");
            newVehicleFunction.Parameter<Vehicle>("Vehicle");
            newVehicleFunction.Parameter<OwnershipRecord>("OwnershipRecord");
    

    控制器

    public IHttpActionResult PostVehicle([FromODataUri] int key, ODataActionParameters parameters)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest();
            }
    
            Vehicle vehicle = (Vehicle)parameters["Vehicle"];
            OwnershipRecord ownRecord = (OwnershipRecord)parameters["OwnershipRecord"];
    
            var owner = db.Entities.Find(key);
            if (owner == null)
            {
                return NotFound();
            }
    
            ownRecord.Vehicle = vehicle;
            owner.OwnershipRecords.Add(ownRecord);
    
            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateException e)
            {
                throw;
            }
    
            return Created(ownRecord); 
        }
    

    客户

            if (newVehicle)
            {
                nmsContainer.Entities.ByKey(ownerId).AddVehicle(vehicle, ownRecord).GetValue();
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-05
      • 2014-05-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多