Как сопоставить запрос OData с DTO на другой объект?
Мой вопрос очень похож на этот: Как сопоставить запрос OData с DTO объекту EF?
У меня есть простая настройка для проверки функциональности фильтра ASP.NET Web API OData V4 $. То, что я хотел бы сделать, это "псевдоним" некоторых свойств ProductDTO для соответствия свойствам объекта Product. Пользователь будет вызывать ProductController, например, со следующим запросом:
GET продукты? $filter = DisplayName eq 'test
Класс продукта:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Level { get; set; }
public Product()
{ }
}
Класс ProductDTO:
public class ProductDTO
{
public int Id { get; set; }
public string DisplayName { get; set; }
public int DisplayLevel { get; set; }
public ProductDTO(Product product)
{
this.DisplayName = product.Name;
this.DisplayLevel = product.Level;
}
}
The ProductController:
public class ProductsController : ApiController
{
public IEnumerable<ProductDTO> Get(ODataQueryOptions<Product> q)
{
IQueryable<Product> products = this._products.AsQueryable();
if (q.Filter != null) products = q.Filter.ApplyTo(this._products.AsQueryable(), new ODataQuerySettings()) as IQueryable<Product>;
return products.Select(p => new ProductDTO(p));
}
}
Конечно, я получаю следующее исключение:
Не удалось найти свойство с именем "DisplayName" в типе "TestAPI.Models.Product"
Я попытался использовать недавно введенную функцию сглаживания, добавив следующие строки в WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
…
IEdmModel model = GetModel();
config.MapODataServiceRoute("*", "*", model);
}
private static IEdmModel GetModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
EntitySetConfiguration<Product> products = builder.EntitySet<Product>("Product");
products.EntityType.Property(p => p.Name).Name = "DisplayName";
products.EntityType.Property(p => p.Level).Name = "DisplayLevel";
return builder.GetEdmModel();
}
}
Я полагаю, что я неправильно использовал функцию сглаживания, потому что выбрано то же исключение, как описано выше. Если я вызываю следующий запрос, он работает, но этого я не пытаюсь достичь:
GET продукты? $filter = Имя eq 'test
Update:
Я согласен с gdoron, конечная точка Get
должна выглядеть так:
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
Но это должно быть разрешено без AutoMapper?
Ответы
Ответ 1
Я нашел решение без использования AutoMapper.
Теперь ProductController выглядит следующим образом:
public class ProductsController : ApiController
{
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
{
IQueryable<Product> products = this._products.AsQueryable();
IEdmModel model = GetModel();
IEdmType type = model.FindDeclaredType("TestAPI.Models.Product");
IEdmNavigationSource source = model.FindDeclaredEntitySet("Products");
ODataQueryOptionParser parser = new ODataQueryOptionParser(model, type, source, new Dictionary<string, string> { { "$filter", q.Filter.RawValue } });
ODataQueryContext context = new ODataQueryContext(model, typeof(Product), q.Context.Path);
FilterQueryOption filter = new FilterQueryOption(q.Filter.RawValue, context, parser);
if (filter != null) products = filter.ApplyTo(products, new ODataQuerySettings()) as IQueryable<Product>;
return products.Select(p => new ProductDTO(p));
}
}
WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
…
IEdmModel model = GetModel();
config.MapODataServiceRoute("*", "*", model);
}
private static IEdmModel GetModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
EntitySetConfiguration<Product> product = builder.EntitySet<Product>("Products");
product.EntityType.Name = "Product";
product.EntityType.Namespace = "TestAPI.Models";
product.EntityType.Property(p => p.Name).Name = "DisplayName";
product.EntityType.Property(p => p.Level).Name = "DisplayLevel";
return builder.GetEdmModel();
}
}
Ответ 2
Если вы решили, что хотите использовать DTO (что, на мой взгляд, является хорошей идеей), используйте его...
$metadata
должен отражать имена свойств DTO, а не сущности EF, так как это то, что получают клиенты, и это то, что должны отправлять клиенты.
Это означает, что вы должны изменить конечную точку Get
на что-то вроде этого:
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
Чтобы избежать связи между ProductDTO
и Product
, вы можете использовать AutoMapper для сопоставления между классами для вас. Кроме того, если вы используете метод AutoMapper Project
, вы можете очистить ваши методы примерно так:
public IQueryable<ProductDTO> Get(ProductDTO dto)
Вы можете проверить официальную демоверсию Asp.net для управления версиями, она интенсивно использует DTO и AutoMapper, она даст вам хорошее руководство, просто игнорируйте управление версиями, если оно вас сейчас не интересует.
Ответ 3
Попробуйте использовать AutoMapper, вам нужно будет добавить эти ссылки на ваш контроллер
using AutoMapper;
using AutoMapper.QueryableExtensions;
Ваш метод
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<ObjectDTO> Get()
{
return dbContext.Entities.ProjectTo<ObjectDTO>();
}
В вашем глобальном
protected void Application_Start()
{
//Usually in a diff class Mapping.ConfigureDataTransferObjects();
Mapper.CreateMap<MyEntity, ObjectDTO>();
Mapper.CreateMap<ObjectDTO, MyEntity>();
}
Ответ 4
Patrick, вы можете заполнить значение destinationvalue из вычисленного sourceValue, например:
Mapper.CreateMap<Customer, CustomerDTO>()
.ForMember(dest => dest.InvoiceCount, opt =>
opt.MapFrom(src => src.Invoices.Count()));
Я получил этот пример из: http://codethug.com/2015/02/13/web-api-deep-dive-dto-transformations-and-automapper-part-5-of-6/
Артуро, вы можете использовать обратную карту в CreateMap, если это не сложное отображение, чтобы сделать однострочный.
Ответ 5
Для меня решение было просто добавить DTO к конфигурации EDM (v4):
edmBuilder.EntitySet<Contact>("Contacts");
edmBuilder.EntityType<ContactDto>();