使用OData查询而不暴露ORM模型?

6 c# entity-framework odata asp.net-web-api

在我的web api 2项目中,如果我想对某些属性的查询使用OData库(看起来很棒且非常诱人),那将迫使客户端知道我的数据库模型的确切属性.这是一个好习惯吗?有没有办法避免这种解耦?

适用于以下型号:

public class LetterEntity
    {
        public int Id {get; set;}

        public string Title {get; set;}

        public string Content {get; set;}

        public string Source {get; set;}

        public DateTime SendingTime {get; set;} 

        public string AnotherWierdString {get; set;
        ...
    }

    public class LetterDTO
    {
        public int Id {get; set;}

        public string Title {get; set;}

        public string LetterContent {get; set;}

        public string Source {get; set;}

        public DateTime SendingTime {get; set;} 
    }

    public class LetterInsideFolderDTO 
    {
        public string Title {get; set;}

        public string Source {get; set;}
    }


public class LettersController : ApiController
{
    // In this approach method, I hate the fact that a LetterEntity must be used for the query.
    [HttpGet]
    [Route("api/letters")]
    [EnableQuery]
    public IQueryable<LetterInsideFolderDTO> Get(ODataQueryOptions<LetterEntity> query) 
    { 
        IQueryable<Letter> letters = db.Letters;

        var afterQuery = query.ApplyTo(letters)

        IQueryable<LetterInsideFolderDTO> dtos = afterQuery.ProjectTo<LetterInsideFolderDTO>(afterQuery)

        return dtos;
    }


    // Is there a way to do something like the following?:  
    [HttpGet]
    [Route("api/letters")]
    [EnableQuery]
    public IQueryable<LetterInsideFolderDTO> Get(ODataQueryOptions<LetterDTO> query) 
    { 
        IQueryable<Letter> letters = db.Letters;

        // Convert the query to work on the entities somehow? Should I use a mapping between LetterDTO to LetterEntity?
        // I only have a map from LetterEntity to LetterDTO
        var afterQuery = query.ApplyTo(letters)

        IQueryable<LetterInsideFolderDTO> dtos = afterQuery.ProjectTo<LetterInsideFolderDTO>(afterQuery)

        return dtos;
    }
}
Run Code Online (Sandbox Code Playgroud)

由于目前我直接在客户端查询中使用Entity模型,因此客户端和服务器之间存在强大的耦合.例如,如果我想查询并获取Content字段中包含"abc"的所有字母,我需要路由到以下内容:

api/letters/?$filter=contains(Content,'abc')
Run Code Online (Sandbox Code Playgroud)

如果明天我决定将该属性从"内容"更改为"LetterContent",则所有客户端代码都将被破坏.

我怎么能超越它?

谢谢!

编辑:

请给我一个具体的例子,我还不明白HATEOAS是什么(如果这有助于我解决这个问题),文档服务如何帮助我?如果我决定改变我的EDM模型,它仍然会强制客户改变他们的代码?

nik*_*ita 0

如果我想使用 OData 库(看起来很棒并且非常诱人)来查询某些属性,这将迫使客户端知道我的数据库模型的确切属性。这是一个好的做法吗?

这取决于“将迫使客户端了解确切的属性”是什么意思。有两种方法:

  1. 使用由 datasvcutil 自动生成的代理。事实上,这将迫使客户端知道确切的属性,因为它们是在客户端中硬编码的。当服务器端更改时,客户端将被破坏 - 客户端/服务器紧密耦合。一般来说,这很糟糕,但通常如果您需要快速完成某件事 - datasvcutil 就是您的工具。

  2. 学习您的客户阅读服务文档,以便他可以动态地决定他可以查询哪些资源。您拥有所需的一切 - 服务文档、元数据。

请记住,OData 是基于 REST 架构构建的,它具有通过一组约束实现的一系列优点。几个限制是可寻址性和 HATEOAS。

可寻址性意味着每个资源都必须有其地址。

HATEOAS意味着在任何给定时刻,基于表示当前资源的超媒体的客户端必须拥有他需要的所有信息来决定下一步要传输到哪里。

为了知道要在哪里转乘,客户必须

  1. 知道如何在他可能经过的数据流中找到资源(URL)。就像您获取带有文本和 URL 的数据一样 - 客户端必须知道如何查找 URL。URL 可能有不同的含义 - 就像用于 CRUD 操作的多个 URL。

  2. 通过资源获取数据。一般来说,客户端必须知道如何开始查询服务

第一点是通过配置文件解决的。配置文件 - 允许客户了解与资源表示相关的附加语义 ( https://www.rfc-editor.org/rfc/rfc6906 )。考虑一下 OData 文档。对于 OData,您的客户端必须知道 OData 中的数据是通过 Atom 或 Json 格式表示的。客户必须了解构建 OData 服务查询、获取特定记录等的原则。

如果客户端调用 OData 根地址 - 像 ...OData.svc 这样的东西,他将获得他可以查询的所有资源的列表(服务文档)。这样第二点就解决了。

您可以进一步通过 $metadata 后缀获取元数据。这将为您提供资源的所有属性。