2012-02-10 3 views
2

я получаю «метод„присоединиться“не поддерживается» сообщение об ошибке в следующей LINQ запроса:Возможно ли это, используя Azure Tables?

tableServiceContext = new CustomTableServiceContext(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials); 
tableServiceContext.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(1)); 
var results = (from c in tableServiceContext.CreateQuery<ChannelEntry>("Channels").AsTableServiceQuery<ChannelEntry>() 
    join v in tableServiceContext.CreateQuery<VideoEntry>("Videos").AsTableServiceQuery<VideoEntry>() on c.PartitionKey equals v.ChannelID 
    join h in tableServiceContext.CreateQuery<HitEntry>("Hits").AsTableServiceQuery<HitEntry>() on v.PartitionKey equals h.VideoID 
    where c.RowKey.Equals(UserID) 
    group h by h.RowKey into g 
    select new BiggestFan { UserID = g.Key, Hits = g.Count() }).AsTableServiceQuery().Execute().OrderByDescending(b => b.Hits).Take(1); 

Если «присоединиться» не поддерживается в этом контексте то, что будет наиболее эффективным способом сделать мой запрос?

У меня есть каналы, которые составлены из видео, которые, в свою очередь, имеют Hits. Я пытаюсь найти самый большой поклонник (самые высокие удары) текущего пользователя.

Что было бы самым эффективным способом сделать этот тип этого без использования объединений? Должен ли я захватить все Каналы, а затем Видео, а затем Хит, как 3 отдельных вызова в хранилище таблиц, а затем сделать соединения после этого?

+0

Для дальнейшего использования здесь приведен список поддерживаемых/неподдерживаемых операторов Linq для хранения таблиц: http://msdn.microsoft.com/en-us/library/windowsazure/dd135725.aspx –

ответ

5

Да, вы не можете присоединиться. У вас здесь пара вариантов.

1) Несколько сканирований - пропустите пару операторов .ToArray() перед тем, как вы присоединитесь к тому, чтобы выполнить объединение в вашем приложении. Это не работает, но хранение таблиц довольно быстро. На самом деле это сводится к тому, сколько строк это приведет.

2) Денормализовать ваши таблицы так, чтобы у вас были ссылки на все ключи, которые вам нужны в одной таблице. Это позволит вам получить результаты в 1 запросе, но означает, что вся логика вставки/обновления должна быть обновлена.

+0

Как бы искать вариант 2 вы предложили денормализацию ...? Если у каналов есть видео, а у видео есть хиты, то у меня все еще есть 3 соответствующие таблицы, но все видеоизображения в таблице каналов, а также хранятся файлы HitID в таблице видео? –

+0

Да точно. Реплицируйте эти идентификаторы, чтобы вам не нужно было сразу подключаться к множеству таблиц. Я ожидаю, что таблица Hits - это та, которая будет обновляться так же, как вы запрашиваете на основе Hits, а таблица Hits - та, которая звучит так, будто у нее будет тысячи записей. Также я предполагаю, что ваш контент для «Хитов» по ​​существу «Write once» - другими словами, вы не обновляете запись Хит в таблице, что означает, что денормализованные данные легко реализовать, так как вам не нужно беспокоиться о чтобы ваши идентификаторы синхронизировались в будущих обновлениях. – DarkwingDuck

+0

Да, это имеет смысл, особенно когда речь заходит о написании. Очевидно, что дата и человек, которые попали в видео, должны быть известны и никогда не будут обновлены после этого. Ура! –

2

В вашем запросе есть 3 вещи, которые не поддерживаются приложением Azure Table Storage (AZT, моя аббревиатура, обычно не используется другими).

  1. присоединяется
  2. Группировка
  3. Агрегатные функции

Короткая версия, что если вы хотите запустить эффективный запрос в АЗТ, то вам нужно, чтобы запустить его против только одной таблицы и запроса к ключ раздела или ключ раздела и ключ строки.

Это не означает, что ваши базовые данные должны храниться только в одной таблице, вы можете сохранить структуру, которая у вас есть в настоящее время, но вам может понадобиться создать таблицу, которая в основном является индексом, который позволит вам получите информацию, которую вы хотите. Это может иметь структуру, подобную этой:

PartitionKey = ChannelUserId.PadWithLeadingZeros() + "-" + (int.MaxValue - NumberOfHits).PadWithLeadingZeros(); 
RowKey = Fan User Id; 

Ваш запрос будет выглядеть примерно так:

tableServiceContext = new CustomTableServiceContext(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials); 
tableServiceContext.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(1)); 
var results = (from i in tableServiceContext.CreateQuery<BiggestFansIndex>("BiggestFansIndex").AsTableServiceQuery<BiggestFansIndex>() 
    where i.PartitionKey.CompareTo(UserId.PaddedWithLeadingZeros()) >= 0 
     && i.PartitionKey.CompareTo((UserId + 1).PaddedWithLeadingZeros()) < 0 
    select i}).Take(1).Execute(); 

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

0

Настольное хранилище Azure не подходит для таких сложных запросов. Я бы предложил вам изучить некоторые базы данных документов без SQL, такие как CouchDB, MongoDB и RavenDB. Но если вы все еще хотите использовать его, вам потребуется денормализовать данные.

1

То, что говорили другие, о том, что вы не можете делать JOINs в Azure Tables, верно. Вы можете переместить его в SQL Azure, где JOIN работают так, как вы ожидаете, но это намного дороже и медленнее, чем таблицы Azure.Тем не менее, если вы придерживаться Azure таблиц:

Глядя на этом конкретном запросе, вы можете настроить ключ раздела для таблицы Hits нравится это:

Hits Таблица:
PartitionKey = UserId (владельца канала)
RowKey = Timestamp (или что-то другое уникальное)
UserId (пользователя, выполнившего удар)
ChannelID
VideoID
(и др которые вы хотите получить в таблице «Хиты»)

Как уже говорилось, вы не можете выполнять агрегацию по запросам хранения таблиц Azure, поэтому вам нужно вернуть все данные обратно в локальную память (вызывая Execute), тогда вы может выполнять агрегацию в памяти. Вот как извлечь данные из хранилища таблицы (этот запрос выполняется на сервере Azure Table Storage):

var allHits = 
    (
     from h in tableServiceContext.CreateQuery("Hits") 
     .AsTableServiceQuery() 
     where h.PartitionKey == CurrentUserId // The currently logged in user 
    ).Execute();

А потом вот как вы могли бы объединить его (этот запрос выполняется в локальной памяти):

var result = 
    (
     from h in allHits 
     group h by h.UserId into g // The User that performed the Hit 
     select new BiggestFan { UserID = g.Key, Hits = g.Count() } 
    ) 
    .OrderByDescending(b => b.Hits).FirstOrDefault();

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

Вы можете пойти дальше в денормализации данных и вычислить и сохранить различные итоговые значения, когда вам нужно запустить этот запрос Biggest-Fan, все, что вам нужно получить, - это различные предварительно рассчитанные итоговые значения.

Однако это только один запрос. При проектировании вашей структуры таблиц Azure вам необходимо рассмотреть все запросы, которые вы можете сделать против них, как часто они будут выполняться и сколько данных они будут работать. Затем вы можете найти лучшую структуру для своих данных в Azure Tables. Я бы рекомендовал не создавать таблицы Azure по одному запросу, так как в будущем вам, скорее всего, понадобятся дополнительные запросы.

Смежные вопросы