breezejs:非标量导航属性为只读 [英] breezejs: Nonscalar navigation properties are readonly

查看:139
本文介绍了breezejs:非标量导航属性为只读的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下元数据:

var entityTypeParent = {
    shortName: 'ParentItemType',
    namespace: 'MyNamespace',
    autoGeneratedKeyType: Identity,
    defaultResourceName: 'ParentItemTypes',

    dataProperties: {
        id: { dataType: DT.Int32, isPartOfKey: true }
    },

    navigationProperties: {
        users: {
            entityTypeName: 'User',
            isScalar: false,
            associationName: 'ParentItem_User'
        }
    }
};

var entityTypeUser = {
    shortName: 'User',
    namespace: 'MyNamespace',
    autoGeneratedKeyType: Identity,
    defaultResourceName: 'Users',

    dataProperties: {
        loginName: { dataType: DT.String, isPartOfKey: true },
        displayText: {},
        parentItemId: {
            dataType: DT.Int32
        }
    },

    navigationProperties: {
        agendaTask: {
            entityTypeName: 'ParentItemType',
            associationName: 'ParentItem_User',
            foreignKeyNames: ['parentItemId']
        }
    }
};

在UI中,我有一个控件(Kendo Multi-Select),该控件绑定到ParentItemType。 users属性(AngularJS绑定),该属性允许从列表中选择用户(使用breeze-kendo bridge和'webApiOData'适配器检索)。

In the UI I have a control (Kendo Multi-Select), which is bound to the ParentItemType.users property (AngularJS binding), which allows to select a user from a list (retrieved using breeze-kendo bridge and 'webApiOData' adapter).

选择用户会导致错误:非标量导航属性为只读-可以添加或删除实体,但不能更改集合。 setNpValue( https://github.com/ Breeze / breeze.js / blob / 397b2a02aa2173175c304eb1b37332f1656db6f5 / src / a35_defaultPropertyInterceptor.js#L287 )。

Selecting a user results in an "Error: Nonscalar navigation properties are readonly - entities can be added or removed but the collection may not be changed." exception in setNpValue (https://github.com/Breeze/breeze.js/blob/397b2a02aa2173175c304eb1b37332f1656db6f5/src/a35_defaultPropertyInterceptor.js#L287).

如果我将定义更改为isScalar eq true。我在 https://github.com /Breeze/breeze.js/blob/397b2a02aa2173175c304eb1b37332f1656db6f5/src/a35_defaultPropertyInterceptor.js#L298

If I change the definition to isScalar eq true. I get an exception at https://github.com/Breeze/breeze.js/blob/397b2a02aa2173175c304eb1b37332f1656db6f5/src/a35_defaultPropertyInterceptor.js#L298

context.newValue是一组用户实体。

context.newValue is an array of user entities.

这是我的元数据定义中的某个错误吗?其实我只想在我的ParentItem中有多个用户。

Is this an error somewhere in my metadata definition? Actually I just want to have multiple users in my ParentItem.

微风版本:1.5.4

Breeze Version: 1.5.4

推荐答案

否,这不是您的元数据定义中的错误。这是设计使然。非标量导航属性由Breeze管理。多选控件尝试获取值数组的所有权并将其替换为新数组。 Breeze不允许这样做,因为这将是灾难性的,并破坏您的实体。

No, that's not an error in your metadata definition. This is by design. Nonscalar navigation properties are managed by Breeze. Multi-select controls try to take ownership of the value array and replace it with a new array. Breeze won't allow that because it would be catastrophic and break your entities.

多选控件有些痛苦。您需要利用控件触发的事件来在用户进行选择时更新导航属性,并根据所选值手动创建关联实体,如果用户未选择它们,则将其删除。我没有KendoUI的示例,但是FWIW,这是DevExpress TagBox和以下实体模型的Angular2示例。

Multi-select controls are a bit of a pain. You need to leverage the events that the control triggers to update the navigation property when the user makes a selection and manually create the association entities from the selected values and delete them if the user unselects them. I don't have an example for KendoUI, but FWIW, here is an Angular2 example for the DevExpress TagBox and the following entity model.

Model(Subject)1<- -> n SubjectVehicleAssociation n <---> 1 LookupItem(Vehicle)

Model(Subject) 1<--->n SubjectVehicleAssociation n<--->1 LookupItem(Vehicle)

其中涉及一些工作。开箱即用的多选控件仅适用于基本数组。

There's a bit of work involved. The multi-select controls out of the box only work with basic arrays.

<dx-tag-box [dataSource]="adapter.dataSource"
        displayExpr="description"
        [value]="adapter.value"
        [searchEnabled]="true"
        (onSelectionChanged)="adapter.selectionChanged($event)">

</dx-tag-box>

在组件中创建适配器实例:

Adapter instance creation in the component:

let valueConverter: ITagBoxValueConverter<SubjectVehicleAssociation, { code: string, longValue: string }> = {
    match: (item: SubjectVehicleAssociation, lookupItem: { code: string, longValue: string }) => item.abbreviation === lookupItem.code,
    seed: (item: SubjectVehicleAssociation, lookupItem: { code: string, longValue: string }) => {
        item.description = lookupItem.longValue;
        item.abbreviation = lookupItem.code;
        return item;
    }
};
this.adapter = new TagBoxAdapter(SubjectVehicleAssociation,
    () => Promise.resolve(this.vehicleAssociationsLookup),
    this.modelSubjectVehicleRelation,
    'associations',
    valueConverter);

和适配器实现:

import DataSource from 'devextreme/data/data_source';
import * as _ from 'lodash';

import { Entity, EntityState } from 'breeze-client';

export interface ITagBoxValueConverter<T extends Entity, W> {

    match(value1: T, value2: W): boolean;

    seed(target: T, source: W): T;
}

export class TagBoxAdapter<T extends Entity, W> {

    dataSource: DataSource;

    constructor(private itemType: { new (): T },
        private data: (searchValue?: string, maxSearchResults?: number) => Promise<W[]>,
        private model: any,
        private valueExpr: string,
        private valueConverter: ITagBoxValueConverter<T, W>,
        private maxSearchResults: number = 20) {

        this.initializeDataSource();
    }

    get value(): T[] {
        return _.get<T[]>(this.model, this.valueExpr, []);
    }

    selectionChanged(e: { addedItems: T[], removedItems: T[] }) {

        e.addedItems.forEach(item => {
            if (item.entityAspect && item.entityAspect.entityState.isDeleted()) {
                // Real entity, needs to be resurrected
                item.entityAspect.rejectChanges();
            } else {
                // Placeholder entity, needs to be attached first
                this.model.entityAspect.entityManager.attachEntity(item, EntityState.Added);
                this.value.push(item);
            }
        })

        e.removedItems.forEach(item => {
            item.entityAspect.setDeleted();
        })

    };

    private initializeDataSource() {
        let sourceData = (searchValue: string, maxSearchResults: number) => {
            return this.data(searchValue, maxSearchResults).then((results) => {
                return results.map(dataItem => {
                    // Find existing association entity if exists
                    let item = _.find(this.value, item => this.valueConverter.match(item, dataItem));
                    if (item) return item;

                    // Not associated yet, return placeholder association entity
                    return this.valueConverter.seed(new this.itemType(), dataItem);
                })
            });
        };

        this.dataSource = new DataSource({
            // The LoadOptions interface is defined wrong it's lacking the search properties
            load: (loadOptions: any) => {
                let searchValue = loadOptions.searchValue ? loadOptions.searchValue : "";
                return sourceData(searchValue, this.maxSearchResults).then(data => data.filter(item => !_.intersection(this.value, [item]).length));
            },
            byKey: (key) => {
                return sourceData(key, 1).then((data) => _.find(data, key));
            }
        });
    }
}

这篇关于breezejs:非标量导航属性为只读的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆