Реляционная поддержка "многие ко многим" в 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.

Полезные ссылки

Другие вопросы по тегам