我正在尝试用3个数据集填充我的Breeze缓存,为每个实体集创建一个单独的异步WebApi请求:
唯一的两个关系是:
我服务MetaData来描述这些关系
问题是:
Breeze是否需要将Person(原则)加载到Cars和SpeedingTickets(dependents)之前的缓存中,以成功建立相关的外键关系? 或者它可以首先处理孤立的dependents加载,并在最后加载Person(原则)时动态地建立关联吗?
编辑:似乎订单无关紧要
EF关系:
modelBuilder.Entity<Person>().HasKey(p => p.Id);
modelBuilder.Entity<Person>()
.HasMany(p => p.Tickets)
.WithRequired(t => t.Person)
.HasForeignKey(t => t.PersonId);
modelBuilder.Entity<Person>()
.HasMany(p => p.Cars)
.WithRequired(c=>c.Person)
.HasForeignKey(c=>c.PersonId);
DataContext:检索人员,汽车,门票(每个远程和本地)的方法
var DataContext = function() {
function configureBreezeManager() {
breeze.NamingConvention.camelCase.setAsDefault();
return new breeze.EntityManager("http://localhost:7071/breeze/data/");;
}
var EntityQuery = breeze.EntityQuery,
manager = configureBreezeManager();
var
QUERY_ALL_PERSONS = EntityQuery.from('People'),
QUERY_ALL_CARS = EntityQuery.from('Cars'),
QUERY_ALL_TICKETS = EntityQuery.from('Tickets');
var
querySuccess = function(result) {
console.log("Fetched: " + result.query.resourceName);
},
queryFail = function(jqXhr, textStatus) {
console.log("failed " + textStatus);
};
var
getPersons = function() {
return manager.executeQuery(QUERY_ALL_PERSONS)
.then(querySuccess).fail(queryFail);
},
getCars = function() {
return manager.executeQuery(QUERY_ALL_CARS)
.then(querySuccess).fail(queryFail);
},
getTickets = function() {
return manager.executeQuery(QUERY_ALL_TICKETS)
.then(querySuccess).fail(queryFail);
},
//LOCALS
getPersonsLocal = function() {
return manager.executeQueryLocally(QUERY_ALL_PERSONS);
},
getCarsLocal = function() {
return manager.executeQueryLocally(QUERY_ALL_CARS);
},
getTicketsLocal = function() {
return manager.executeQueryLocally(QUERY_ALL_TICKETS);
};
return {
getPersons: getPersons,
getCars: getCars,
getTickets: getTickets,
getPersonsLocal: getPersonsLocal,
getCarsLocal: getCarsLocal,
getTicketsLocal: getTicketsLocal
};
};
控制台输出功能:
function logLocalData() {
var people = dc.getPersonsLocal(),
cars = dc.getCarsLocal(),
tickets = dc.getTicketsLocal();
var pLen = people.length,
cLen = cars.length,
tLen = tickets.length;
for (var p = 0 ; p < pLen; p++)
console.log(people[p].name + " has " + people[p].cars.length + " car and " + people[p].tickets.length + " ticket");
for (var t = 0 ; t < tLen; t++)
console.log("ticket " + tickets[t].id + " belongs to person: " + (tickets[t].person));
for (var c = 0 ; c < cLen; c++)
console.log("car " + cars[c].id + " belongs to person: " + (cars[c].person));
}
以下是5个测试,每个测试加载以不同顺序链接的实体,最后一个测试将它们加载到异步中
TEST1:(OK)人们在家属之前加载(微风正确地关联所有3个实体) - 所需的结果
var dc = new DataContext();
dc.getPersons()
.then(dc.getCars)
.then(dc.getTickets)
.then(function () {
logLocalData();
})
Fetched: People Fetched: Cars Fetched: Tickets Joe has 1 car and 1 ticket Sam has 1 car and 1 ticket Bob has 1 car and 1 ticket ticket 0 belongs to person: [object Object] ticket 1 belongs to person: [object Object] ticket 2 belongs to person: [object Object] car 0 belongs to person: [object Object] car 1 belongs to person: [object Object] car 2 belongs to person: [object Object]
TEST 2:(FAIL)PEOPLE按照顺序加载后依次为:CARS,TICKETS,PEOPLE(注意TICKET&gt; PERSON关联 null ,但CAR&gt; PERSON没问题)
dc.getCars()
.then(dc.getTickets)
.then(dc.getPersons)
.then(function () {
logLocalData();
})
Fetched: Cars Fetched: Tickets Fetched: People Joe has 1 car and 0 ticket Sam has 1 car and 0 ticket Bob has 1 car and 0 ticket ticket 0 belongs to person: null ticket 1 belongs to person: null ticket 2 belongs to person: null car 0 belongs to person: [object Object] car 1 belongs to person: [object Object] car 2 belongs to person: [object Object]
TEST 3:(FAIL)PEOPLE按顺序装入受抚养人:TICKETS,CARS,PEOPLE(注意CAR> PERSON关联 null ,但是TICKET&gt; PERSON没问题) - 与TEST 2 *
相反dc.getTickets()
.then(dc.getCars)
.then(dc.getPersons)
.then(function () {
logLocalData();
})
Fetched: Tickets Fetched: Cars Fetched: People Joe has 0 car and 1 ticket Sam has 0 car and 1 ticket Bob has 0 car and 1 ticket ticket 0 belongs to person: [object Object] ticket 1 belongs to person: [object Object] ticket 2 belongs to person: [object Object] car 0 belongs to person: null car 1 belongs to person: null car 2 belongs to person: null
TEST 4:(OK)PEOPLE按顺序加载2ND:TICKETS,PEOPLE,CARS(微风正确关联所有3个实体)
dc.getCars()
.then(dc.getPersons)
.then(dc.getTickets)
.then(function () {
logLocalData();
})
Output is same as TEST 1
测试5:(失败)使用 Q.all 异步加载所有设置本来是我的首选方法,但它会根据服务器响应时间产生不可预测的结果每次通话。
Q.all([ dc.getTickets(), dc.getCars(), dc.getPersons()])
.then(function () {
logLocalData();
}).catch(function () { console.log("error"); })
Output produces random results (could be any from TEST 1, 2, 3) based on server response time for each call.
结论
重申问题
这是预期的行为吗? Breeze是否需要在汽车和门票(家属)之前加载到缓存中的人(原则),以成功建立相关的外键关系?或者我的代码有问题吗?
这是一个简化的例子,我有更多的实体集加载到SPA中,并且显然不需要跟踪加载顺序和保持同步。
我决定完全把注意力从Q.all / promises和查询执行上移开,因为这似乎把焦点从潜在问题上移开了。我还将数据最小化为三个实体类型中的每一个的一个对象。
所以在这里,我留下了以下内容:
测试6:(确定)通知呼叫顺序 - 创建人员第一
mgr.metadataStore.fetchMetadata("http://localhost:7071/breeze/data/")
.then(function() {
mgr.createEntity('Person', { id: 1, name: 'Jim' });
mgr.createEntity('Car', { id: 20, name: 'car', personId: 1 });
mgr.createEntity('Ticket', { id: 30, personId: 1 });
}).catch(function () { console.log("error"); })
.done(function () {logLocalData();})
一切都很好了
Jim(1) has 1 car and 1 ticket ticket 30 belongs to person: [object Object] car 20 belongs to person: [object Object]
测试7:(失败)通知调用顺序 - 人员创建最后,汽车对象未连接到人员
mgr.metadataStore.fetchMetadata("http://localhost:7071/breeze/data/")
.then(function() {
mgr.createEntity('Ticket', { id: 30, personId: 1 });
mgr.createEntity('Car', { id: 20, name: 'car', personId: 1 });
mgr.createEntity('Person', { id: 1, name: 'Jim' });
}).catch(function () { console.log("error"); })
.done(function () {logLocalData();})
汽车对象未连接到人
Jim(1) has 0 car and 1 ticket ticket 30 belongs to person: [object Object] car 20 belongs to person: null
问题出在哪里?
我手工创建了元数据,使用breeze的元数据助手非常简单。
好的,这是我的One脚本,参考 breeze &amp; breeze.metadata-helper ,仅此而已,问题依然存在:
/********** METADATA GENERATOR **********/
var MyMetaDataGenerator = function() {
var keyGen = breeze.AutoGeneratedKeyType.Identity,
namespace = 'genob.Model',
helper = new breeze.config.MetadataHelper(namespace, keyGen);
var DT = breeze.DataType, ID = DT.Int32;
function createMetadataStore(serviceName) {
var addType = function(type) { helper.addTypeToStore(store, type); };
var store;
store = new breeze.MetadataStore({
namingConvention: breeze.NamingConvention.camelCase
});
helper.addDataService(store, serviceName);
addPerson();
addCar();
addTicket();
return store;
function addPerson() {
addType({
name: 'Person',
dataProperties: {
id: { type: ID },
name: { max: 50, nullOk: false }
},
navigationProperties: {
cars: { type: 'Car', hasMany: true },
tickets: { type: 'Ticket', hasMany: true }
}
});
}
function addCar() {
addType({
name: 'Car',
dataProperties: {
id: { type: ID },
name: { max: 50, nullOk: false },
personId: { type: ID, nullOk: false },
},
navigationProperties: {
person: 'Person',
}
});
}
function addTicket() {
addType({
name: 'Ticket',
dataProperties: {
id: { type: ID },
name: { max: 50, nullOk: false },
personId: { type: ID, nullOk: false },
},
navigationProperties: {
person: 'Person',
}
});
}
}
return {
createMetadataStore: createMetadataStore
};
};
/********** DATA CONTEXT **********/
var DataContext = function () {
var
EntityQuery = breeze.EntityQuery,
manager = configureBreezeManager();
function configureBreezeManager() {
var serviceName = "a/b",
metaDataCreator = MyMetaDataGenerator(),
store = metaDataCreator.createMetadataStore(serviceName),
mgr = new breeze.EntityManager({
serviceName: serviceName,
metadataStore: store
});
return mgr;
}
var getManager = function() {
return manager;
};
var
getPersonsLocal = function() {
return manager.executeQueryLocally(EntityQuery.from('Persons'));
},
getCarsLocal = function() {
return manager.executeQueryLocally(EntityQuery.from('Cars'));
},
getTicketsLocal = function() {
return manager.executeQueryLocally(EntityQuery.from('Tickets'));
};
return {
getManager: getManager,
getPersonsLocal: getPersonsLocal,
getCarsLocal: getCarsLocal,
getTicketsLocal: getTicketsLocal,
};
};
///////********** MY APP **********//////////
var App = function() {
var dc = new DataContext();
function logLocalData() {
var people = dc.getPersonsLocal(),
cars = dc.getCarsLocal(),
tickets = dc.getTicketsLocal();
var pLen = people.length, cLen = cars.length, tLen = tickets.length;
for (var p = 0; p < pLen; p++)
console.log(people[p].name + "(" + people[p].id + ") has " + people[p].cars.length + " car and " + people[p].tickets.length + " ticket");
for (var t = 0; t < tLen; t++)
console.log("ticket " + tickets[t].id + " belongs to person: " + (tickets[t].person));
for (var c = 0; c < cLen; c++)
console.log("car " + cars[c].id + " belongs to person: " + (cars[c].person));
}
var run = function() {
var mgr = dc.getManager();
mgr.createEntity('Ticket', { id: 30, personId: 1 });
mgr.createEntity('Car', { id: 20, personId: 1 });
mgr.createEntity('Person', { id: 1, name: 'Jim' });
logLocalData();
};
return {
run: run
};
};
App().run();
输出:注意CAR&gt; PERSON关系没有连线,(注意:如果我们先加载CAR,然后是TICKET,那么TICKET&gt; PERSON没有连线,第二个从属关系永远不会被连线)。
Jim(1) has 0 car and 1 ticket ticket 30 belongs to person: [object Object] car 20 belongs to person: null
但是,如果我们切换订单以插入第一个人:
mgr.createEntity('Person', { id: 1, name: 'Jim' });
mgr.createEntity('Ticket', { id: 30, personId: 1 });
mgr.createEntity('Car', { id: 20, personId: 1 });
输出:连接所有关系(一切正常)
Jim(1) has 1 car and 1 ticket ticket 30 belongs to person: [object Object] car 20 belongs to person: [object Object]
问题出在哪里?
我已经消除了不可能的事情,只留下了不可能的事情,那么真相是什么呢?
答案 0 :(得分:0)
看起来问题不是Person必须最后,但是有两个没有反向属性的关联只能清理第一个。我从未使用过metadataGenerator,但即使不使用它也存在问题。
检查小提琴是否有重复,并将最靠近底部的创建汽车呼叫移动到创建实体呼叫列表中的任何其他位置。
直到它可以被解决,你可以先调用getPeople()。executeQuery(),然后在返回的promise上调用另外两个。
我认为您的问题在于您没有正确使用Q.all()
,但如果没有看到正在调用它的内容以及它是如何工作的话,很难说清楚。
function getData() {
var promise = Q.all([ dc.getTickets(), dc.getCars(), dc.getPersons()]);
return promise.then(querySuccess);
function querySuccess() {
// Check that everything has been returned
}
}
只要您的所有三个电话都返回一个承诺,它应该等到所有电话都完成后再继续。
答案 1 :(得分:0)
Breeze不应该关心实体到达的顺序。如果你等到它们全部到达,它们都应该连线。确实有些不妥。
你的代码看起来很好,直到我偶然发现了记者函数logLocalData
。
您正在使用迭代OBJECT属性的JavaScript for
。
for (var p in people)
console.log(people[p].name + " has " + people[p].cars.length +
" car and " + people[p].tickets.length + " ticket");
在你的情况下这种作品(否则你不会看到任何东西)。而且此错误无法解释您的结果。但这是不正确的。您希望for
遍历ELEMENTS OF ANARRAY。
for (var p, len=people.length; p < len; p++)
console.log(people[p].name + " has " + people[p].cars.length +
" car and " + people[p].tickets.length + " ticket");
或ES5数组的forEach
方法
people.forEach(function(p) {
console.log(p.name + " has " + p.cars.length +
" car and " + p.tickets.length + " ticket");
});
请尝试将catch
添加到Q.all
,然后告诉我们您的看法。
因为这是一种常见的情况,我添加了一个传递demonstration test to the DocCode sample。它使用Knockout,但这无关紧要。也许你会发现一些有启发性的东西。
/*********************************************************
* When run separate queries for Employee, Orders, EmployeeTerritories.
* Breeze will wire up their relationships.
*
* These next tests are a response to the SO question
* http://stackoverflow.com/questions/24001496/breeze-js-priming-loading-and-caching-data-via-asynchronous-requests
*
* The tests verify the entity wiring after all queries have finished.
*
* Note that Employee has many Orders and many EmployeeTerritories
* which matches the SO question's model structure
*********************************************************/
asyncTest("Can run parallel queries and breeze will wire relationships", 5, function () {
var em = newEm();
var queries = make_Emp_Orders_EmpTerritories_Query(em);
var all = [
queries.eQuery.execute(),
queries.etQuery.execute(),
queries.oQuery.execute()
];
Q.all(all)
.then(function () { check_Emp_Orders_EmpTerritories(queries); })
.catch(handleFail).finally(start);
});
asyncTest("Can chain queries, dependent first, and breeze will wire relationships", 5, function () {
var em = newEm();
var queries = make_Emp_Orders_EmpTerritories_Query(em);
// Run dependent entity queries first starting with Orders
queries.oQuery.execute()
// then EmployeeTerritories
.then(function () { return queries.etQuery.execute(); })
// lastly the principal (Employee) query
.then(function () { return queries.eQuery.execute(); })
// now assert that everything is wired up
.then(function () { check_Emp_Orders_EmpTerritories(queries); })
.catch(handleFail).finally(start);
});
function make_Emp_Orders_EmpTerritories_Query(em) {
var eQuery = EntityQuery.from("Employees").using(em)
.where('EmployeeID', '==', 1); // trim to just Nancy
var etQuery = EntityQuery.from("EmployeeTerritories").using(em);
var oQuery = EntityQuery.from("Orders").using(em)
.where('EmployeeID', '==', 1); // trim to just Nancy's orders
return {
eQuery: eQuery,
etQuery: etQuery,
oQuery: oQuery
};
}
function check_Emp_Orders_EmpTerritories(queries) {
var emps = queries.eQuery.executeLocally();
var empTerritories = queries.etQuery.executeLocally();
var orders = queries.oQuery.executeLocally();
equal(emps.length, 1, "should have one Employee (Nancy)");
notEqual(orders.length, 0, "should have Orders");
notEqual(empTerritories.length, 0, "should have EmployeeTerritories");
var e1 = emps[0];
var e1Name = e1.FirstName();
var e1OrdersLen = e1.Orders().length;
var e1EmpTerritoriesLen = e1.EmployeeTerritories().length;
notEqual(e1OrdersLen, 0,
"'{0}' has {1} Orders".format(e1Name, e1OrdersLen));
notEqual(e1EmpTerritoriesLen, 0,
"'{0}' has {1} EmployeeTerritories".format(e1Name, e1EmpTerritoriesLen));
};
请注意,断言会练习导航属性并从缓存中提取值。
随意处理链式承诺的顺序。没关系。