Breeze EntityQuery возвращает отсутствующие данные в EntityTypes

Я настраиваю SPA, используя Angular и Breeze. Я следил за учебником Джона Папы по горячим полотенцам с сайта множественного числа. У меня странная проблема, думаю, которая может быть вызвана моими метаданными? Но в конце концов, я не совсем уверен....

Во-первых, мой API работает со стеком LAMP, поэтому я не использую EF. Я создал конечную точку метаданных, которая, как мне кажется, дает мне правильную структуру, которая мне нужна. Я использую breeze.angular.q.js, чтобы упростить свои сопоставления из Q до $q

ресурс: API/v1/метаданные

{
    "metadataVersion": "1.0.5",
    "dataServices": [
        {
            "serviceName": "api/v1/",
            "hasServerMetadata": true,
            "jsonResultsAdapter": "webApi_default",
            "useJsonp": false
        }
    ],
    "structuralTypes": [
        {
            "shortName": "tracks",
            "namespace": "MyNamespace",
            "dataProperties": [
                {
                    "name": "id",
                    "nameOnServer": "id",
                    "maxLength": 36,
                    "validators": [],
                    "dataType": "Guid",
                    "isPartOfKey": true
                },
                {
                    "name": "title",
                    "nameOnServer": "title",
                    "maxLength": 255,
                    "validators": [],
                    "dataType": "String"
                },
                {
                    "name": "description",
                    "nameOnServer": "description",
                    "maxLength": 0,
                    "validators": [],
                    "dataType": "String"
                }
            ]
        }
    ]
}

пример возвращаемых данных API выглядит следующим образом:

ресурс: API/v1/треки

{
    "data": [
        {
            "id": "495f21d6-adfc-40b6-a41c-fc93d9275e24",
            "title": "harum",
            "description": "Error doloribus ipsam et sunt fugiat."
        },
        {
            "id": "d7b141d2-6523-4777-8b5a-3d47cc23a0fe",
            "title": "necessitatibus",
            "description": "Voluptatem odit nulla maiores minima eius et."
        }
    ],
    "embeds": [
        "courses"
    ]
}

Теперь со всем моим кодом я фактически возвращаю правильные данные из своего API. Я привел примеры с сайта breeze в виде нескольких хороших лакомых кусочков, которые я нашел здесь, на SO (понравился этот вопрос и отличный ответ от палаты). Увы, не повезло. По сути, что происходит, когда я пытаюсь перебрать свои результаты в моей модели представления, которые возвращаются из моего запроса бриза, я получаю угловую ошибку Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: t in vm.tracks, Duplicate key: object:00I

Вызов происходит в функции внутри моего контекста данных. Данные, возвращенные в моем обратном вызове обещания querySucceeded, не связаны правильно.

datacontext.js

...
function getTrackPartials() {
    ...
    return EntityQuery.from(entityNames.track)
        .toType(entityNames.track)
        .using(manager).execute()
        .then(querySucceeded, _queryFailed);

    function querySucceeded(data) {
        console.log(data);  // <--  Log out to see what is returned
        tracks = data.results;
        _areTracksLoaded(true)
        log('Retrieved [Track Partials] from remote data source', tracks.length, true);
        return tracks;
    }
}

Если бы я вывел эти данные на консоль, я бы получил это (все $$hashKey одинаковы, а идентификатор, заголовок и описание равны NULL. Но я получаю правильное количество результатов, а это не так). t совпадение - если я настраиваю количество результатов, которые я должен получить, оно каждый раз правильно совпадает).

querySucceededОбратный вызов обещания

Теперь, поскольку мои данные возвращаются немного другими, я использовал пример Эдмондса и создал custom JsonResultsAdapter, чтобы я мог "массировать" данные. На данный момент это очень элементарно, так как я просто пытаюсь заставить это работать. Что действительно сбивает меня с толку, так это то, что если я выведу параметр node из функции visitNode в JsonResultsAdapter, у него будут правильные данные....????

entityManagerFactory.js

(function () {
    'use strict';

    var serviceId = 'entityManagerFactory';
    angular.module('app').factory(serviceId, ['config', emFactory]);

    function emFactory(config) {
        breeze.config.initializeAdapterInstance('modelLibrary', 'backingStore', true);
        breeze.NamingConvention.camelCase.setAsDefault();

        var serviceName = config.remoteServiceName;
        var metadataStore = new breeze.MetadataStore();

        var provider = {
            metadataStore: metadataStore,
            newManager: newManager
        };

        var jsonResultsAdapter = new breeze.JsonResultsAdapter({
            name: "Tracks",
            extractResults: function(json) {
                console.log(json.results.data);  // <--  Log out to see what is returned
                return json.results.data;
            },
            visitNode: function(node, mappingContext, nodeContext) {
                console.log(node);  // <--  Log out to see what is returned
                return {
                    entityType: 'tracks',
                    nodeId: node.id
                };
            }
        });

        var dataService = new breeze.DataService({
            serviceName: serviceName,
            jsonResultsAdapter: jsonResultsAdapter
        });

        return provider;

        function newManager() {
            var mgr = new breeze.EntityManager({
                dataService: dataService,
                metadataStore: metadataStore
            });

            return mgr
        }
    }
})();

Вот мое возвращаемое значение из моей функции JsonResultsAdapter::extractResults JsonResultsAdapter extractResults return value

Вот узел из моей функции JsonResultsAdapter::visitNode Параметр узла JsonResultsAdapter visitNode

Любая помощь будет оценена по достоинству. Как я уже сказал, я не совсем уверен, где происходит ошибка? Но если бы мне пришлось угадывать, я бы сказал, что между моим EntityQuery, использующим мой менеджер, и JsonResultsAdapter есть некоторая связь, которая может быть вызвана плохими метаданными, которые я сгенерировал.

** ОБНОВЛЕНИЕ **

Итак, я прошелся по коду бриза, чтобы выяснить, где я теряю свои данные, и смог выяснить, что происходит, и способ это исправить. Однако я не уверен, что это лучший способ справиться с этим.

Я должен упомянуть, что я использовал Bower для установки breeze — и, сделав это, я выбрал пакет bower-breeze-angular git://github.com/eggers/bower-breeze-angular.git, а не стандартный breeze breeze git://github.com/IdeaBlade/Breeze.git, который раздут с примерами и другими данными, которые я не хотел упаковывать в свой репозиторий.

Короче говоря, после того, как мой обратный вызов JsonResultsAdapter::visitnode вернулся, ему нужно "объединить" мои данные, у меня проблема с entityKey, возвращаемый моим узлом, не совпадает. Это связано с тем, что rawValueFn из моего mappingContext ищет nameOnServer, который, как я думал, установил в своих метаданных с моего сервера, но каким-то образом, когда я выхожу из системы, свойство данных изменилось по сравнению с тем, что я установил.

Это один dp, вышедший из системы, если вы посмотрите вверх в моем вызове ресурсов метаданных, я специально установил это на «id». Как это изменилось на Id? Вот что вызывает у меня головную боль!

DataProperty с измененным именемOnServer Key

Я могу обойти это, обновив мою функцию rawValueFn в моем mapContext в моем JsonResultsAdapter, и все будет работать, но это похоже на «хак». Я также пробовал играть с «NamingConvention», но это тоже не работает.

Вот мой обновленный JsonFactory, который заставляет его работать

    var jsonResultsAdapter = new breeze.JsonResultsAdapter({
        name: "Tracks",
        extractResults: function(json) {
            return json.results.data;
        },
        visitNode: function(node, mappingContext, nodeContext) {

            // Had to adjust this so it would lowercase and correctly match
            mappingContext.rawValueFn = function(rawEntity, dp) {
                name = dp.name;
                name.substring(0, 1).toLowerCase() + name.substring(1);
                return rawEntity[name];
            }

            return {
                entityType: 'tracks'
            };
        }   
    }); 

person veilig    schedule 13.02.2014    source источник
comment
Если вы еще этого не сделали, установите точку останова в отладчике Chrome внутри функции extractResults. Убедитесь, что json.results.data это то, что вы думаете. Функция extractResults должна возвращать массив.   -  person Steve Schmitt    schedule 13.02.2014
comment
Спасибо, Стив, я обновил свой вопрос и включил фотографию того, что я на самом деле возвращаю из функции extractResults. Он внизу, ближе к низу поста. На самом деле я возвращаю массив из двух объектов, и прямо под этим изображением вы можете видеть, когда я visitNode получаю правильный объект с правильными данными... не уверен, почему он не объединяется правильно при возврате из этой функции?   -  person veilig    schedule 13.02.2014
comment
Да, все ваши данные выглядят правильно - и, как вы предположили, проблема может быть в метаданных. Вам не нужно указывать и имя, и nameOnServer для каждого свойства; Breeze использует NamingConvention для переключения с одного на другое. Убедитесь, что вы НЕ используете NamingConvention.camelCase — это заставит Breeze использовать первую букву заглавной буквы при переключении с имен клиентов на имена серверов.   -  person Steve Schmitt    schedule 13.02.2014
comment
Я посмотрел, как работают NamingConventions, и попытался протестировать все доступные варианты, но по какой-то причине ни один из них не повлиял на результат — возможно, я делал это неправильно. возможно, стоит еще раз взглянуть на это, так как я менял / тестировал так много разных вещей. спасибо за вашу помощь кстати.   -  person veilig    schedule 13.02.2014


Ответы (1)


Вау, это длинный вопрос.

Во-первых, я рекомендую просмотреть метаданные вручную раздел, в котором описывается гораздо более простой способ определения ваших метаданных... с помощью помощника по метаданным Breeze Labs. Это сократит много утомительного и сделает его намного более ясным для чтения и понимания.

Во-вторых, НЕ указывайте jsonResultsAdapter в своих метаданных. Мне кажется, что вы привязываете свои метаданные к адаптеру WebAPI, когда на самом деле хотите использовать другой. НЕ указывайте namingConvention в своих метаданных, так как это превзойдет все, что вы установили в другом месте. И учитывая, что вы не получаете метаданные с сервера, параметр hasServerMetadata должен быть равен false, если вы вообще его устанавливаете (чего делать не следует).

В-третьих, придерживайтесь имени на стороне клиента и забудьте о nameOnServer. NamingConvention в любом случае сокрушит это.

В-четвертых, если (как кажется) имена свойств на стороне клиента и на стороне сервера имеют ОБЕ верблюжьи регистры, НЕ изменяйте NamingConvention значение по умолчанию! Вы не хотите никаких переводов. По умолчанию перевод не выполняется.

Если я прав, НЕ меняйте NamingConvention на camelCase! Соглашение camelCase говорит Breeze, что сервер использует PascalCase, поэтому переведите мои имена свойств CamelCase на стороне клиента в имена Pascal на сервере. Если я правильно понимаю, вы не хотите, чтобы идентификатор на стороне клиента стал идентификатором на стороне сервера... что и произойдет. Вот почему (я полагаю) вы видите Id как nameOnServer.

В-пятых, в JsonResultsAdapter имена узлов соответствуют JSON с вашего сервера и, следовательно, являются именами на стороне сервера. Держите их такими. NamingConvention преобразует их в имена на стороне клиента при преобразовании значений свойств узла в значения свойств объекта. На самом деле вы потеряете данные, если по ошибке используете клиентские имена на узлах.

Есть ли у вас необходимость изменять имена свойств и значения в JSON, когда он поступает с сервера? Если нет, не связывайтесь с этими именами в своем методе visitNode. Все, что вам нужно сделать, это убедиться, что вы определили правильный EntityType для узла и вернули его в результате.

В-шестых, я почти уверен, что свойство entityType результата visitNode должно быть фактическим EntityType, НЕ именем типа, как вы показали в своем примере. Вы не можете сказать

return {
   entityType: 'tracks',
};

Вы должны дать ему реальный тип (я почти уверен)

return {
   entityType: trackType,
};

Посмотрите на другие адаптеры Breeze (например, адаптер веб-API). Он получает EntityType из MetadataStore.

Седьмое Почему вы устанавливаете nodeId? Я не говорю, что вы ошибаетесь. Но вы должны знать, почему. NodeId используется для восстановления графа объектов, когда одна и та же сущность появляется в полезной нагрузке несколько раз. Это полезно только тогда, когда сопровождается nodeRefId в каком-то другом узле, который указывает на значение nodeId. На этом этапе, когда у вас есть только один тип сущности, а не отношения, установка nodeId ничего не дает. Позже это произойдет ... но только если вы установите значение, которое имеет смысл.

Я думаю, вы устанавливаете для nodeId значение первичного ключа Track. Разве это не то, что node.id в вашем случае? Если я прав, не делайте этого. nodeId НЕ является PK вашего объекта. Это маркер для сериализации графов сущностей с помощью циклов.

Вау, мой длинный ответ

Боюсь, вы немного перестарались. Написание адаптера dataService или jsonResultsAdapter — это не задача для новичков в Breeze. Если вы собираетесь туда, пожалуйста, изучите существующие адаптеры и неукоснительно следуйте им. Знайте, почему они делают то, что делают, а не пускают пыль в глаза.

Надеюсь, я дал здесь несколько подсказок.

Я подозреваю, что это намного проще, чем вы это делаете. Некоторые ключевые мысли:

  • Убедитесь, что вы не меняете NamingConvention, если вам действительно не нужно изменить написание имен свойств с клиент-сервер.

  • Задайте для entityType в вашем JsonResultsAdapter значение EntityType, а не имя типа.

  • Не переписывайте простые функции, такие как rawValueFn; вы сломаете Бриз, и вы не будете знать, как и почему.

person Ward    schedule 14.02.2014
comment
спасибо за всю проницательность! наконец заработало. на самом деле namingConvention": "camelCase" проник в метаданные сервера. как только я удалил это и немного поиграл с метаданными, все, казалось, встало на свои места! - person veilig; 22.02.2014
comment
БИНГО! Кто-то еще столкнулся с этим (или, может быть, это были вы), что заставило меня уточнить в двух местах документации Breeze: Отладка результатов запроса и Осторожно: в Соглашении об именах. - person Ward; 23.02.2014
comment
Начиная с версии 1.4.17 BreezeJS (и, возможно, ранее) он отлично работает, возвращая строку (полное имя типа сущности) для свойства entityType объекта, возвращаемого visitNode. - person Claus Conrad; 16.10.2014