我有两个类,
Vehicle
和 OwnershipRecord
,没有一个不能持久保存到数据库中。一个Vehicle
必须至少有一个OwnershipRecord
并且一个OwnershipRecord
必须与一个Vehicle
相关联。否则没有意义。
使用 Web Api 和 OData v4 客户端代码生成器我还没有想出一种方法来序列化这两个对象并将它们一起发布。看来我需要发布一个车辆然后添加一个 OwnershipRecord 或发布一个 OwnershipRecord 然后添加一个车辆,这是不可能的。
DataServiceContext.AddObject
提供以下内容:
对象在Added状态下被放入DataServiceContext的tracking set。 DataServiceContext 将在下次调用 SaveChanges 时尝试通过 HTTP POST 插入对象。此方法不会将与指定实体相关的对象添加到 DataServiceContext。每个对象都必须通过单独调用 AddObject 添加。
该方法不验证指定的实体集是否在与 DataServiceContext 关联的数据服务中,或者添加的对象是否具有添加到指定实体集所需的属性。
因此,所有导航属性在传递时都为空。因此,当我将
OwnershipRecord
s 添加到 newVehicle 然后调用 Container.AddToVehicles(newVehicle)
时,VehiclesController
上的 POST 方法将 ModelState.IsValid
呈现为错误的说法 Vehicle must have an owner!
.
如何使用客户端代码发送具有导航属性的车辆并将两者加在一起?我尝试在
AddLink
上使用 AddRelatedObject
和 Container
但它不适用于相对 url,因为这些项目尚不存在。
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
使用 $batch 请求在同一变更集中发送两个帖子。
参考这个示例项目,了解如何支持 $batch,https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataBatchSample
所以我意识到一起发送两个参数我可以在
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();
}
有一个解决方法是使用 SetLink 这是一个例子:
var context = new DataServiceContext(new Uri("http://odataServiceUri"));
var customer = new Customer { Name = "John Doe" };
var account = new Account { Balance = 100 };
context.AddObject("Customers", customer);
context.AddObject("Accounts", account);
context.SetLink(customer, "Account", account);
context.SaveChanges();