如何在代码中定义(可选)导航属性(?$ expand =)?

时间:2014-12-19 21:31:11

标签: asp.net-web-api odata entity-framework-6 asp.net-web-api-odata odata-v4

首先是解释和要点,然后是问题。所以:

假设我首先在EF(6.1.1)数据库中定义了一个视图AccountView(edmx),以便代码生成的类是

//This class is generated from a view by EF (edmx)...
public partial class AccountView
{
    public System.Guid Id { get; set; }
    public int CompanyId { get; set; }
}

然后我在与

相同的命名空间(Entities)中创建一个部分类
[MetadataType(typeof(AccounViewMetaData))]
public partial class AccounView
{
    //This is added here explicitly. AccountView itself exposes just
    //a naked key, CompanyId.
    public virtual Company Company { get; set; }

    //This is just in case...
    public class AccounViewDomainMetaData
    {
        //This is to add a navigation property to the OData $metadata. How to do this
        //in WebApiConfig? See as follows...
        [ForeignKey("Company")]
        public int CompanyId { get; set; }
    }
}

//This is an EF generated class one from an edmx..-
public partial class Company
{
    public Company()
    {            
    }

    public int CompanyID { get; set; }
    public string Name { get; set; }
}  

然后在WebApiConfig我写,或尝试,在OData v4(最新的,6.9.0.0系列与WebApi,最新的也是如此)中的内容如下

builder.EntitySet<Entities.Company>("Companies");

var accountSet = builder.EntitySet<Entities.AccountView>("Accounts");
accountSet.EntityType.HasKey(i => i.Id); //EF has hard time recognizing primary keys on database first views...

//TODO: How should I define the required binding so that I could "?$expand=Company"
//as such as ``http://example.com/service/Accounts?$expand=Company``
//accountSet.HasRequiredBinding(i => i.CompanyId, typeof(Entities.Company));

结果$metadata就像

<schema>
    <EntityType Name="Company">
        <Key>
            <PropertyRef Name="CompanyID"/>
        </Key>
        <Property Name="CompanyID" Type="Edm.Int32" Nullable="false"/>
        <Property Name="Name" Type="Edm.String"/>
     </EntityType>

    <EntityType Name="AccountView">
        <Key>
            <PropertyRef Name="Id"/>
        </Key>
        <Property Name="Id" Type="Edm.Guid" Nullable="false"/>
        <Property Name="CompanyId" Type="Edm.Int32" Nullable="false"/>
        <NavigationProperty Name="Company" Type="Entities.Company" Nullable="false"/>
    </EntityType>
</Schema>

问题:正如TODO评论中所述,我该如何定义

  • 导航属性,以便我可以致电http://example.com/Accounts?$expand=Companyhttp://example.com/Accounts?$Companies

现在,如果我打电话

,它就可以了
  • http://example.com/Accounts
  • http://example.com/Accounts(someId)

然后,如果我做了类似

的事情
  • http://example.com/Accounts?$expand=Companies
  • http://example.com/Accounts?$expand=Company
  • http://example.com/Accounts(someId)?$expand=Company

我受到 HTTP 400 (?$ expand =公司)或 HTTP 500 (?$ expand =公司)的欢迎。

我已经成功创建了Containment关系。看起来,它需要具有由ID定义的根实体,而我想向根提供“GET”并且可选地扩展为“子列表”对象(因此这个问题关于?$ expand =)

现在案例是为一个实体做了一个非可选的expand,但我怀疑接下来我想要的是一个实体列表(或者就这个特定的公司而言)例子)但我怎么能实现这些场景?如何解决扩展到第一个永远存在的子对象的情况?

我的控制器定义如下

public class AccountsController: ODataController
{
    [EnableQuery]
    public IHttpActionResult Get()
    {
        try
        {
            //This context here is a EF entities model (generated from an edmx)...
            return Ok(Context.AccountView);
        }
        catch(Exception ex)
        {
            return InternalServerError(); 
        }
    }

    [EnableQuery]
    public IHttpActionResult Get([FromODataUri]Guid key)
    {
        try
        {
            return Ok(SingleResult.Create(Context.AccountView.Where(a => a.Id == key)));
        }
        catch (Exception ex)            
        {                
            return InternalServerError(ex); 
        }
    }
}

我基本上要做的是首先向数据库添加更多元数据,edmx,视图并模仿文章Using $select, $expand, and $value in ASP.NET Web API 2 OData。到目前为止没有成功......

&lt;编辑1:可以说,情节变浓了。 HTTP 500错误来自一个内部异常(我不得不打破破解到托管框架异常)

  

EntityFramework.dll中出现'System.NotSupportedException'类型的第一次机会异常

     

其他信息:LINQ to Entities不支持指定的类型成员“公司”。仅支持初始化程序,实体成员和实体导航属性。

所以,是的,AccountView是一个视图,它与Company表没有直接的外键关系(它恰好是一个表,但也可能是一个视图)。我怎么去加一个呢?我虽然添加元数据已经完成了这个技巧,但$metadata信息证明了这一点。 OData不是只在数据库中写一个INNER JOIN吗?我在这里错过了什么吗? ...这似乎与我添加Company引用的方式有关,EF不喜欢这样。它看起来我应该只将它添加到OData模型......

&lt;编辑2:如果我将AccountView CompanyId更改为{似乎没有任何区别(至少不符合我的设置) {1}}以匹配CompanyID表中定义的大小。

&lt; edit3:我问了另一个相关的问题How to add complex properties on a model built with ODataConventionModelBuilder from an EF model

2 个答案:

答案 0 :(得分:1)

尝试$ expand而不是&amp; expand。

答案 1 :(得分:0)

看起来我得到了一个回答相关的另一个帖子,并从另一个论坛推动。

关于异常,实际上,它是由查询命中数据库并尝试检索那里没有定义的列引起的。有关更多信息:

然后关于代码和答案,另一个问题是Expanding navigation properties with ODataQueryOptions。这里稍微增加的代码如下

public async Task<IHttpActionResult> Get(ODataQueryOptions<T> options)
{
    IQueryable<T> tempQuery = Context.AccountView;
    IQueryable<T result = tempQuery;

    if(options.SelectExpand != null)
    {
        if(options.Filter != null)
        {
            tempQuery = options.Filter.ApplyTo(tempQuery, new ODataQuerySettings()) as IQueryable<T>;   
        }
         /* Other options that should go to the DB level... */

        //The results need to be materialized, or otherwise EF throws a
        //NotSupportedException due to columns and properties that aren't in the DB...
        result = (await tempQuery.ToListAsync()).AsQueryable();

        //Do here the queries that can't be hit straight by the queries. E.g.
        //navigation properties not defined in EF views (that can't be augmented)...
        //E.g. get the company here.

        //This is needed to that OData formatter knows to render navigation links too.
        Request.ODataProperties().SelectExpandClause = options.SelectExpand.SelectExpandClause;
}

return Ok(result);