How do I map an OData query against a DTO to another entity?

后端 未结 5 483
梦谈多话
梦谈多话 2020-12-05 01:30

My question is very similar to this one: How do I map an OData query against a DTO to an EF entity? I have a simple setup to test the ASP.NET Web API OData V4 $filter functi

相关标签:
5条回答
  • 2020-12-05 01:36

    I found a solution without using AutoMapper.

    The ProductsController now looks like this:

    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));
        }
    }
    

    The 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();
        }
    }
    
    0 讨论(0)
  • 2020-12-05 01:47

    To me the solution was just adding the DTO to the EDM config (v4):

    edmBuilder.EntitySet<Contact>("Contacts");
    edmBuilder.EntityType<ContactDto>();
    
    0 讨论(0)
  • 2020-12-05 01:49

    If you decided you want to use DTOs (which is definitely a good idea in my opinion), then use it...
    The $metadata should reflect the DTO's properties names and not of the EF entity, since this is what clients get and this is what the clients should send.
    It means you should change the Get endpoint to something like this:

    public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
    

    To avoid the coupling between ProductDTO and Product you can use AutoMapper to map between the classes for you. Also, if you use AutoMapper's Project method, you can cleanup you methods to somthing like:

    public IQueryable<ProductDTO> Get(ProductDTO dto)
    

    You can check Asp.net official demo for versioning, it heavily uses DTOs and AutoMapper, it will give you a good direction, just ignore the versioning if it doesn't interest you now.

    0 讨论(0)
  • 2020-12-05 01:49

    Patrick, you can fill a destinationvalue from a calculated sourceValue, like:

    Mapper.CreateMap<Customer, CustomerDTO>()
        .ForMember(dest => dest.InvoiceCount, opt =>
            opt.MapFrom(src => src.Invoices.Count()));
    

    I got this Example from: http://codethug.com/2015/02/13/web-api-deep-dive-dto-transformations-and-automapper-part-5-of-6/

    Arturo, you can use reverseMap on the CreateMap if it is not a complex mapping, to do a one-liner.

    0 讨论(0)
  • 2020-12-05 01:59

    Try using AutoMapper, you will need to add these references to your controller

    using AutoMapper;
    using AutoMapper.QueryableExtensions;
    

    Your method

    [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
    public IQueryable<ObjectDTO> Get()
    {
        return dbContext.Entities.ProjectTo<ObjectDTO>();
    }
    

    In your global

    protected void Application_Start()
    {
            //Usually in a diff class Mapping.ConfigureDataTransferObjects();
            Mapper.CreateMap<MyEntity, ObjectDTO>();
            Mapper.CreateMap<ObjectDTO, MyEntity>();
    }
    
    0 讨论(0)
提交回复
热议问题