一对多导航属性不起作用

时间:2015-06-19 02:22:39

标签: breeze asp.net-web-api

我正在使用breezejs v.1.5.4与OData Web Api控制器(如果它有所不同,则使用AngularJS v.1.4.0)。

我有以下模型(简化):

public partial class Job
{
    ...

    [Required]
    [StringLength(128)]
    [Index("IX_WorkDoneById")]
    [Display(Name = "Work Done By Id")]
    public string WorkDoneById { get; set; }

    [ForeignKey("WorkDoneById")]
    public virtual User WorkDoneBy { get; set; }
}

[DataContract]
public partial class User : IdentityUser
{
    ...

    [Key]
    [StringLength(128)]
    [Display(Name = "Id")]
    [DataMember]
    public override string Id
    {
        get
        {
            return base.Id;
        }
        set
        {
            base.Id = value;
        }
    }

    [InverseProperty("WorkDoneBy")]
    [DataMember]
    public virtual ICollection<Job> Jobs { get; set; }
}

当尝试获取Job信息并展开WorkDoneBy时,它可以正常工作并获取用户信息(即用户绑定到作业)。当我尝试将Jobs与用户关联时,我得到一个空数组。我检查了网络,Jobs与服务器响应一起传输,但未附加到用户实例。

我的JS查询是这样的:

var query = new breeze.EntityQuery()
            .from("Users")
            .expand("Jobs")
            .where(new breeze.Predicate("Id", "eq", "Some long Guid"));

有什么建议吗?

更新1

我也在使用datajs v.1.1.3和odata服务适配器。

以下是元数据:

{
"metadataVersion": "1.0.5",
"namingConvention": "noChange",
"localQueryComparisonOptions": "caseInsensitiveSQL",
"dataServices": [
    {
        "serviceName": "odata/",
        "adapterName": "odata",
        "uriBuilderName": "odata",
        "hasServerMetadata": true,
        "jsonResultsAdapter": "OData_default",
        "useJsonp": false
    }
],
"structuralTypes": [
    {
        "shortName": "Job", 
        "namespace": "MyApp.Models",
        "autoGeneratedKeyType": "None", 
        "defaultResourceName": "Jobs", 
        "dataProperties": [
            { 
                "name": "JobId", 
                "dataType": "Guid", 
                "isNullable": false, 
                "defaultValue": "00000000-0000-0000-0000-000000000000", 
                "isPartOfKey": true, 
                "validators": [{ "name": "required" }, { "name": "guid" }] 
            }, 
            { 
                "name": "WorkDoneById", 
                "dataType": "String", 
                "isNullable": false, 
                "defaultValue": "", 
                "validators": [{ "name": "required" }, { "name": "string" }] 
            }
        ],
        "navigationProperties": [
            { 
                "name": "WorkDoneBy", 
                "entityTypeName": "User:#MyApp.Models", 
                "isScalar": true, 
                "associationName": "MyApp_Models_Job_WorkDoneBy_MyApp_Models_User_WorkDoneByPartner" 
            }
        ]
    },
    {
        "shortName": "User", 
        "namespace": "MyApp.Models",
        "autoGeneratedKeyType": "None", 
        "defaultResourceName": "Users", 
        "dataProperties": [
            { 
                "name": "Id", 
                "dataType": "String", 
                "isNullable": false, 
                "defaultValue": "", 
                "isPartOfKey": true, 
                "validators": [{ "name": "required" }, { "name": "string" }] 
            }
        ], 
        "navigationProperties": [
            { 
                "name": "Jobs", 
                "entityTypeName": "Job:#MyApp.Models", 
                "isScalar": false, 
                "associationName": "MyApp_Models_User_Jobs_MyApp_Models_Job_JobsPartner" 
            }
        ]
    }
],
"resourceEntityTypeMap":
{
    "Jobs": "Job:#MyApp.Models",
    "Users": "User:#MyApp.Models"
}
}

这是微风配置:

var dataService = new breeze.DataService({
    adapterName: "odata",
    hasServerMetadata: false,  // don't ask the server for metadata 
    serviceName: "odata",
    uriBuilderName: "odata",
});

// create the metadataStore 
var metadataStore = new breeze.MetadataStore();

// initialize the store from the application's metadata variable
metadataStore.importMetadata(Models.metaData);

// Apply additional functions and properties to the models
metadataStore.registerEntityTypeCtor("Job", Models.Job);
metadataStore.registerEntityTypeCtor("User", Models.User);

// Initializes entity manager.
this.entityManager = new breeze.EntityManager(
    { dataService: dataService, metadataStore: metadataStore }
);

更新2

从服务器odata/$metadata生成的元数据:

<edmx:Edmx Version="1.0">
  <edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0">
    <Schema Namespace="MyApp.Models">
      <EntityType Name="Job">
        <Key>
          <PropertyRef Name="JobId"/>
        </Key>
        <Property Name="JobId" Type="Edm.Guid" Nullable="false"/>
        <Property Name="WorkDoneById" Type="Edm.String" Nullable="false"/>
        <NavigationProperty Name="WorkDoneBy" Relationship="MyApp.Models.MyApp_Models_Job_WorkDoneBy_MyApp_Models_User_WorkDoneByPartner" ToRole="WorkDoneBy" FromRole="WorkDoneByPartner"/>
      </EntityType>
      <EntityType Name="User">
        <Key>
          <PropertyRef Name="Id"/>
        </Key>
        <Property Name="Id" Type="Edm.String" Nullable="false"/>
        <NavigationProperty Name="Jobs" Relationship="MyApp.Models.MyApp_Models_User_Jobs_MyApp_Models_Job_JobsPartner" ToRole="Jobs" FromRole="JobsPartner"/>
      </EntityType>
      <Association Name="MyApp_Models_Job_WorkDoneBy_MyApp_Models_User_WorkDoneByPartner">
        <End Type="MyApp.Models.User" Role="WorkDoneBy" Multiplicity="0..1"/>
        <End Type="MyApp.Models.Job" Role="WorkDoneByPartner" Multiplicity="0..1"/>
      </Association>
      <Association Name="MyApp_Models_User_Jobs_MyApp_Models_Job_JobsPartner">
        <End Type="MyApp.Models.Job" Role="Jobs" Multiplicity="*"/>
        <End Type="MyApp.Models.User" Role="JobsPartner" Multiplicity="0..1"/>
      </Association>
    </Schema>
    <Schema Namespace="Default">
      <EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
        <EntitySet Name="Jobs" EntityType="MyApp.Models.Job"/>
        <EntitySet Name="Users" EntityType="MyApp.Models.User"/>
        <AssociationSet Name="MyApp_Models_Job_WorkDoneBy_MyApp_Models_User_WorkDoneByPartnerSet" Association="MyApp.Models.MyApp_Models_Job_WorkDoneBy_MyApp_Models_User_WorkDoneByPartner">
          <End Role="WorkDoneByPartner" EntitySet="Jobs"/>
          <End Role="WorkDoneBy" EntitySet="Users"/>
        </AssociationSet>
        <AssociationSet Name="MyApp_Models_User_Jobs_MyApp_Models_Job_JobsPartnerSet" Association="MyApp.Models.MyApp_Models_User_Jobs_MyApp_Models_Job_JobsPartner">
          <End Role="JobsPartner" EntitySet="Users"/>
          <End Role="Jobs" EntitySet="Jobs"/>
        </AssociationSet>
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

2 个答案:

答案 0 :(得分:2)

根据您的配置代码,您似乎正在使用本地定义的元数据

  

虽然它有点令人困惑,因为您问题中前面显示的元数据对象附加到一个显示"hasServerMetadata": true的数据服务。我不知道这个内部数据服务实际上很重要。

我不是肯定的,但我怀疑问题是你的关联名称;它们对于两个导航属性是不同的:

    // Job
    ...
    "navigationProperties": [
        { 
            "name": "WorkDoneBy", 
            ...
            "associationName": "MyApp_Models_Job_WorkDoneBy_MyApp_Models_User_WorkDoneByPartner" 
        }
    ]

     // User
     ...
    "navigationProperties": [
        { 
            "name": "Jobs", 
             ...
            "associationName": "MyApp_Models_User_Jobs_MyApp_Models_Job_JobsPartner" 
        }
    ]

如果您希望Breeze配对这些属性,associationName必须是相同的字符串值。价值本身并不重要;只是关系的两个属性结尾具有相同的associationName这一事实。这就是Breeze了解这些属性是如何交配的。

尝试一些好的和简短的方法来解释潜在的关系...像"User_Jobs""Job.WorkDoneBy_User.Jobs"

更新#1

如何获得这些不同的associationName值,这是一个谜。

在评论中,我要求您查看来自OData源的原始元数据。

以下是“ODataBreezejsSample”中的示例,该示例从OData v.3源获取元数据。

OData nuget包是"Microsoft.AspNet.WebApi.OData" version="5.2.2"。该示例使用EdmBuilderas explained in the documentation

有两种类型 - TodoListTodoItem - 具有一对多的关系。相关的原始元数据XML是:

// TodoList
<NavigationProperty Name="TodoItems" Relationship="ODataBreezejsSample.Models.TodoList_TodoItems" ... />
// TodoItem
<NavigationProperty Name="TodoList" Relationship="ODataBreezejsSample.Models.TodoList_TodoItems" ... />

请注意,它们具有相同的Relationship名称:“ODataBreezejsSample.Models.TodoList_TodoItems”

然后,我检查Breeze从此XML生成的客户端元数据中的相应导航属性。两个属性共享“{TodoList_TodoItems”的associationName ...等于剥离命名空间的Relationship名称。

您查询的是哪种OData源(以及OData版本)?您使用EdmBuilder类来生成元数据吗?

更新#2

所以你正在使用"Microsoft ASP.NET Web API 2.2 for OData v4.0" v.5.6 nuget包!这意味着你正在使用OData v.4。

PITA

这是对v.5.5.x(最后一个OData v.3 nuget包)的巨大改变。

  

他们怎么能做出那么大的飞跃并重新使用版本号,特别是主要的版本数字?这令人难以置信。

     

为了真正混淆事物,现在有两个nuget包名称略有不同:

           

您是否碰巧注意到v.3包名称中间的“WebApi”?我一开始没有。

坏消息是,他们对v.4的实施打破了一切......再次......包括元数据。而且......再次......他们没有遵循OData规范,尤其是元数据中的w / r / t导航属性。

因此,Breeze尚未使用Web API OData v.4元数据......还有其他问题。

我们正在解决Microsoft OData团队的问题。在那之前,你可以选择等我们或者回到OData v.3。

同样重要:我们用于OData v.1-3的 datajs 第三方客户端JavaScript库不适用于OData v.4。与其他人一样,您必须切换到olingo库。可能是olingo库在合理化导航属性元数据方面发挥了建设性作用。我不知道,目前还没有关于这个问题的专家。

是的......这是一团糟。

答案 1 :(得分:0)

而不是查询你可以在管理器上使用fetchEntityByKey。这将按键获取实体。

function getAJobDetail(){
         return manager.fetchEntityByKey(
                          "Jobs", "Some long Guid", true)
                       .then(fetchSucceeded)
                       .fail(queryFailed);
                function fetchSucceeded(data) {
                    var s = data.entity;}
       function queryFailed(error) {

                var msg = '[datacontext.js] Error retrieving data. ' + error.message;
                //logError(msg, error);
                throw error;
                }
}

注意:只有当您的某个Guid键必须是主键时,此方法才有效,否则您必须使用谓词并比较字段然后查询微风。