Реляционная поддержка "многие ко многим" в JSData
Есть ли способ определить отношение многие ко многим в JSData?
Например, у меня есть эти 3 таблицы:
файл entityFile
На "сущности" я хочу иметь отношение, называемое "файлами", которое соединяет ЧЕРЕЗ entityFile.
1 ответ
Хороший вопрос. Типичное отношение "многие ко многим" - это просто два отношения "один ко многим":
Одна из наиболее важных деталей в любой реализации: где хранится информация о взаимоотношениях? Ответ на этот вопрос определяет, как можно получить доступ к отношениям субъекта. Давайте рассмотрим несколько вариантов.
Предпосылка:
A
имеет много B
B
имеет много A
Опция 1
Информация об отношениях хранится в случаях A
,
В этом сценарии, когда у вас есть экземпляр A
вы можете найти связанные с ним экземпляры B
, потому что идентификаторы связанных B
экземпляры хранятся наA
, Это также означает, что если у вас есть только экземпляр B
, единственный способ найти все экземпляры A
что B
экземпляр относится к будет искать все экземпляры A
для тех, чьи b_ids
поле содержитid
из B
пример.
Пример
var Player = store.defineResource({
name: 'player',
relations: {
hasMany: {
team: {
// JSData will setup a "teams" property accessor on
// instances of player which searches the store for
// that player's teams
localField: 'teams',
localKeys: 'team_ids'
}
}
}
})
var Team = store.defineResource({
name: 'team',
relations: {
hasMany: {
player: {
localField: 'players',
// Since relationship information is stored
// on the player, in order to retrieve a
// team's players we have to do a O(n^2)
// search through all the player instances
foreignKeys: 'team_ids'
}
}
}
})
Теперь давайте посмотрим на это в действии:
var player = Player.inject({
id: 1,
team_ids: [3, 4]
})
// The player's teams aren't in the store yet
player.teams // [ ]
var player2 = Player.inject({
id: 2,
team_ids: [4, 5],
teams: [
{
id: 4
},
{
id: 5
}
]
})
// See the property accessor in action
player2.teams // [{ id: 4 }, { id: 5 }]
// One of player one's teams is in the store now
player.teams // [{ id: 4 }]
// Access the relation from the reverse direction
var team4 = Team.get(4) // { id: 4 }
// The property accessor makes a O(n^2) search of the store because
// the relationship information isn't stored on the team
team4.players // [{ id: 1, team_ids: [3, 4] }, { id: 2, team_ids: [4, 5] }]
Давайте загрузим отношение из персистентного слоя:
// To get an authoritative list of player one's
// teams we ask our persistence layer.
// Using the HTTP adapter, this might make a request like this:
// GET /team?where={"id":{"in":[3,4]}} (this would be url encoded)
//
// This method call makes this call internally:
// Team.findAll({ where: { id: { 'in': player.team_ids } } })
player.DSLoadRelations(['team']).then(function (player) {
// The adapter responded with an array of teams, which
// got injected into the datastore.
// The property accessor picks up the newly injected team3
player.teams // [{ id: 3 }, { id: 4 }]
var team3 = Team.get(3)
// Retrieve all of team3's players.
// Using the HTTP adapter, this might make a request like this:
// // GET /player?where={"team_ids":{"contains":3}} (this would be url encoded)
//
// This method call makes this call internally:
// Player.findAll({ where: { team_ids: { 'contains': team3.id } } })
return team3.DSLoadRelations(['player'])
})
Если вы используете адаптер HTTP, то ваш сервер должен проанализировать строку запроса и ответить правильными данными. Если вы используете какой-либо из других адаптеров, то адаптер уже знает, как вернуть правильные данные. Использование JSData на внешнем и внутреннем интерфейсах делает это слишком простым.
Вариант 2
Информация об отношениях хранится в случаях B
,
Это только обратный вариант 1.
Вариант 3
"A
имеет много B
"информация об отношениях хранится в экземплярах A
, а также "B
имеет много A
"информация об отношениях хранится в случаях B
,
Это всего лишь вариант 1, за исключением того, что теперь он работает в обоих направлениях.
Преимущество этого подхода заключается в том, что вы можете получить доступ к отношениям в обоих направлениях без необходимости использовать foreignKeys
вариант. Недостатком этого подхода является то, что при изменении отношений приходится изменять данные в нескольких местах.
Вариант 4
Информация об отношениях хранится в сводной (соединительной) таблице.
A
имеет много C
а также C
принадлежит A
где фактическая информация о взаимоотношениях хранится в C
,
B
имеет много C
а также C
принадлежит B
где фактическая информация о взаимоотношениях хранится в C
,
Пример:
var Player = store.defineResource({
name: 'player',
relations: {
hasMany: {
membership: {
localField: 'memberships',
// relationship information is stored on the membership
foreignKey: 'player_id'
}
}
}
})
var Team = store.defineResource({
name: 'team',
relations: {
hasMany: {
membership: {
localField: 'memberships',
// relationship information is stored on the membership
foreignKey: 'team_id'
}
}
}
})
И центральный ресурс:
var Membership = store.defineResource({
name: 'membership',
relations: {
belongsTo: {
player: {
localField: 'player',
// relationship information is stored on the membership
localKey: 'player_id'
},
team: {
localField: 'team',
// relationship information is stored on the membership
localKey: 'team_id'
}
}
}
})
Теперь давайте посмотрим на это в действии:
var player = Player.inject({ id: 1 })
var player2 = Player.inject({ id: 2 })
var team3 = Team.inject({ id: 3 })
var team4 = Team.inject({ id: 4 })
var team4 = Team.inject({ id: 5 })
player.memberships // [ ]
player2.memberships // [ ]
team3.memberships // [ ]
team4.memberships // [ ]
team5.memberships // [ ]
Обратите внимание, что на данный момент мы не можем получить доступ к каким-либо отношениям
// The relationships stored in our pivot table
var memberships = Membership.inject([
{
id: 997,
player_id: 1,
// player one is on team three
team_id: 3
},
{
id: 998,
player_id: 1,
// player one is also on team four
team_id: 4
},
{
id: 999,
player_id: 2,
// team four also has player 2
team_id: 4
},
{
id: 1000,
player_id: 2,
// player 2 is also on team 5
team_id: 5
}
])
Теперь у нас есть информация о членстве
player.memberships // [{ id: 997, ... }, { id: 998, ... }]
player2.memberships // [{ id: 998, ... }, { id: 999, ... }]
team3.memberships // [{ id: 997, ... }]
team4.memberships // [{ id: 998, ... }, { id: 999, ... }]
team5.memberships // [{ id: 1000, ... }]
Теперь немного неудобно отправлять данные сводной таблицы на ваш интерфейс и требовать, чтобы ваш JavaScript разбирал отношения. Для этого вам понадобятся вспомогательные методы:
var Player = store.defineResource({
name: 'player',
relations: {...},
computed: {
teams: {
get: function () {
return store.filter('membership', {
player_id: this.id
}).map(function (membership) {
return store.get('team', membership.team_id)
})
}
}
},
// Instance methods
methods: {
getTeams: function () {
return Player.getTeams(this.id)
}
}
// Static Class Methods
getTeams: function (id) {
return this.loadRelations(id, ['membership']).then(function (memberships) {
return store.findAll('team', {
where: {
id: {
'in': memberships.map(function (membership) {
return membership.team_id
})
}
}
})
})
}
})
Я позволю вам выяснить аналогичные методы для ресурса Team.
Если вы не хотите вдаваться в проблемы вспомогательных методов, вы можете просто внедрить их в бэкэнд, чтобы сделать вашу сводную таблицу невидимой для внешнего интерфейса и сделать ваши отношения "многие ко многим" более похожими на вариант 1, 2. или 3.
Полезные ссылки