У меня есть два класса: Vehicle
и OwnershipRecord
, и ни одна из них не может быть сохранена в базе данных без другой. A Vehicle
должен иметь не менее одного OwnershipRecord
, а OwnershipRecord
должен быть связан с Vehicle
. В противном случае это не имеет смысла.Как я могу использовать POST с навигационными свойствами, используя Microsoft.OData.Client и Web Api
Использование генератора кода клиента Api и OData v4 Я не выяснил способ сериализации обоих объектов и их POST. Кажется, мне нужно ПОСТАВИТЬ автомобиль, а затем добавить OwnershipRecord или опубликовать OwnershipRecord, а затем добавить автомобиль, что невозможно.
DataServiceContext.AddObject
обеспечивает следующее:
Объект помещается в трекинга множестве DataServiceContext в состоянии Добавлено. DataServiceContext попытается вставить объект по HTTP POST при следующем вызове SaveChanges. Этот метод не добавляет объекты, связанные с указанным объектом, в DataServiceContext. Каждый объект должен быть добавлен через отдельный вызов AddObject.
Метод не проверяет, что набор сущностей указано в службе данных, связанной с DataServiceContext или что добавленный объект имеет требуемые свойства, необходимые для добавления указанного набора сущностей ..
Таким образом, все свойства навигации имеют значение null при передаче. Поэтому, когда я добавляю OwnershipRecord
s к новому автомобилю, а затем звоните Container.AddToVehicles(newVehicle)
, метод POST на VehiclesController
отображает 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
с ГЭТ, я должен быть в состоянии использовать что-то похожее на expand
для POST
Из того, что я пытался с пакетом, он по-прежнему пытается сохранить каждый отдельно. Мне нужен OwnershipRecord, прикрепленный к серверной стороне транспортного средства, поэтому один вызов db.SaveChanges() будет работать –
Вы пытаетесь использовать набор изменений, запрос в том же наборе изменений сохраняется в одном вызове, а каждый запрос и набор изменений в пакетном запросе обрабатывается последовательно по протоколу. Вы можете обратиться к http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete. html # _Change_Sets, чтобы увидеть пример запроса. – Vincent
Я думаю, что мой комментарий был неясным, проблема в валидации. Если я использую 'container.SaveChanges (SaveChangesOptions.BatchWithSingleChangeset)', команда отправляет сообщение для транспортного средства, которое потерпит неудачу, и сообщение для OwnershipRecord, которое потерпит неудачу. Мне нужно на стороне сервера, чтобы получить оба объекта, связать их, а затем сервер может успешно вызвать SaveChanges() –