web-dev-qa-db-ja.com

DTOに対するODataクエリを別のエンティティにマッピングするにはどうすればよいですか?

私の質問はこれと非常によく似ています: DTOに対するODataクエリをEFエンティティにマップするにはどうすればよいですか? ASP.NET Web API OData V4 $ filter機能をテストするための簡単なセットアップがあります。私がやりたいのは、ProductDTOのいくつかのプロパティを「エイリアス」して、Productエンティティのプロパティと一致させることです。たとえば、ユーザーは次のリクエストでProductsControllerを呼び出します。

製品を入手しますか?$ filter = DisplayNameeq‘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;
    }
}

ProductsController:

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

もちろん、次の例外が発生します。

タイプ「TestAPI.Models.Product」で「DisplayName」という名前のプロパティが見つかりませんでした

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

上記と同じ例外がスローされるため、エイリアシング機能を誤って使用していると思います。次のリクエストを呼び出すと機能しますが、これは私が達成しようとしていることではありません。

製品を入手しますか?$ filter = Nameeq‘test ’

更新:

Gdoronに同意します。Getエンドポイントは次のようになります。

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

しかし、これはAutoMapperなしで解決できるはずですか?

13
niklr

AutoMapperを使用せずに解決策を見つけました。

ProductsControllerは次のようになります。

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();
    }
}
15
niklr

DTOを使用することにした場合(私の意見では間違いなく良い考えです)、それを使用してください...
$metadataは、EFエンティティではなく、DTOのプロパティ名を反映する必要があります。これは、クライアントが取得するものであり、クライアントが送信する必要があるものだからです。
これは、Getエンドポイントを次のように変更する必要があることを意味します。

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

ProductDTOProductの間の結合を回避するには、 AutoMapper を使用してクラス間をマッピングします。また、AutoMapperのProjectメソッドを使用する場合は、次のような方法でメソッドをクリーンアップできます。

public IQueryable<ProductDTO> Get(ProductDTO dto)

あなたはチェックすることができます バージョン管理のためのAsp.net公式デモ 、それはDTOとAutoMapperを多用します、それはあなたに良い方向性を与えます、それが今あなたに興味がないならバージョン管理を無視してください。

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>();
}
2
Arturo Soto

私にとっての解決策は、EDM構成(v4)にDTOを追加することでした。

edmBuilder.EntitySet<Contact>("Contacts");
edmBuilder.EntityType<ContactDto>();
0
Shimmy

パトリック、次のように、計算されたsourceValueからdestinationvalueを入力できます。

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/ ==

Arturo、複雑なマッピングでない場合は、CreateMapでreverseMapを使用して、ワンライナーを実行できます。

0
Jonas Granlund