Qual é a melhor (e mais rápida) maneira de recuperar uma linha aleatória usando Linq para SQL quando tenho uma condição, por exemplo, algum campo deve ser verdadeiro?
Qual é a melhor (e mais rápida) maneira de recuperar uma linha aleatória usando Linq para SQL quando tenho uma condição, por exemplo, algum campo deve ser verdadeiro?
Respostas:
Você pode fazer isso no banco de dados, usando um UDF falso; em uma classe parcial, adicione um método ao contexto de dados:
partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}
Então apenas order by ctx.Random()
; isso fará uma ordem aleatória no SQL-Server, cortesia de NEWID()
. ie
var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();
Observe que isso só é adequado para tabelas de tamanho pequeno a médio; para tabelas grandes, isso terá um impacto no desempenho do servidor e será mais eficiente encontrar o número de linhas ( Count
) e, em seguida, escolher uma ao acaso ( Skip/First
).
para abordagem de contagem:
var qry = from row in ctx.Customers
where row.IsActive
select row;
int count = qry.Count(); // 1st round-trip
int index = new Random().Next(count);
Customer cust = qry.Skip(index).FirstOrDefault(); // 2nd round-trip
Outro exemplo para Entity Framework:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
Isso não funciona com LINQ to SQL. O OrderBy
está simplesmente sendo descartado.
EDIT: Acabei de notar que este é LINQ to SQL, não LINQ to Objects. Use o código de Marc para que o banco de dados faça isso por você. Deixei esta resposta aqui como um ponto de interesse potencial para LINQ to Objects.
Estranhamente, você realmente não precisa fazer a contagem. No entanto, você precisa buscar todos os elementos, a menos que obtenha a contagem.
O que você pode fazer é manter a ideia de um valor "atual" e a contagem atual. Ao buscar o próximo valor, pegue um número aleatório e substitua o "atual" por "novo" com uma probabilidade de 1 / n, onde n é a contagem.
Portanto, quando você lê o primeiro valor, sempre o torna o valor "atual". Quando você lê o segundo valor, pode torná-lo o valor atual (probabilidade 1/2). Quando você lê o terceiro valor, pode torná-lo o valor atual (probabilidade 1/3) etc. Quando você fica sem dados, o valor atual é aleatório de todos os que você lê, com probabilidade uniforme.
Para aplicar isso com uma condição, simplesmente ignore qualquer coisa que não atenda à condição. A maneira mais fácil de fazer isso é considerar apenas a sequência de "correspondência" para começar, aplicando primeiro uma cláusula Where.
Aqui está uma implementação rápida. Eu acho que está tudo bem ...
public static T RandomElement<T>(this IEnumerable<T> source,
Random rng)
{
T current = default(T);
int count = 0;
foreach (T element in source)
{
count++;
if (rng.Next(count) == 0)
{
current = element;
}
}
if (count == 0)
{
throw new InvalidOperationException("Sequence was empty");
}
return current;
}
current
irá sempre ser definido para o primeiro elemento. Na segunda iteração, há uma mudança de 50% que será definido para o segundo elemento. Na terceira iteração, há 33% de chance de ser definido para o terceiro elemento. Adicionar uma instrução break significaria que você sempre sairia após ler o primeiro elemento, tornando-o nem um pouco aleatório.
Uma maneira de fazer isso com eficiência é adicionar uma coluna aos seus dados Shuffle
que é preenchida com um int aleatório (conforme cada registro é criado).
A consulta parcial para acessar a tabela em ordem aleatória é ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Isso faz uma operação XOR no banco de dados e ordena pelos resultados desse XOR.
Vantagens: -
Esta é a abordagem usada pelo meu sistema de automação residencial para randomizar playlists. Ele pega uma nova semente a cada dia, dando uma ordem consistente durante o dia (permitindo recursos fáceis de pausar / retomar), mas uma nova visão de cada lista de reprodução a cada novo dia.
result = result.OrderBy(s => s.Shuffle ^ seed);
(ou seja, não há necessidade de implementar o XOR por meio dos operadores ~, & e |).
se você quiser obter, por exemplo var count = 16
linhas aleatórias da tabela, você pode escrever
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
aqui eu usei EF, e a Tabela é um Dbset
Se o objetivo de obter linhas aleatórias é amostrar, falei brevemente aqui sobre uma boa abordagem de Larson et al., Equipe de pesquisa da Microsoft, onde desenvolveram uma estrutura de amostragem para Sql Server usando visualizações materializadas. Também existe um link para o artigo em si.
List<string> lst = new List<string>();
lst.Add("Apple");
lst.Add("Guva");
lst.Add("Graps");
lst.Add("PineApple");
lst.Add("Orange");
lst.Add("Mango");
var customers = lst.OrderBy(c => Guid.NewGuid()).FirstOrDefault();
Explicação: Ao inserir o guid (que é aleatório), a ordem com orderby seria aleatória.
Vim aqui querendo saber como obter algumas páginas aleatórias de um pequeno número delas, de modo que cada usuário receba algumas 3 páginas aleatórias diferentes.
Esta é minha solução final, trabalhando em consultas com LINQ em uma lista de páginas no Sharepoint 2010. Está no Visual Basic, desculpe: p
Dim Aleatorio As New Random()
Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3
Provavelmente deveria obter algum perfil antes de consultar um grande número de resultados, mas é perfeito para o meu propósito
Eu tenho uma consulta de função aleatória contra DataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
O exemplo abaixo chamará a fonte para recuperar uma contagem e, em seguida, aplicará uma expressão de salto na fonte com um número entre 0 e n. O segundo método aplicará a ordem usando o objeto aleatório (que ordenará tudo na memória) e selecionará o número passado na chamada do método.
public static class IEnumerable
{
static Random rng = new Random((int)DateTime.Now.Ticks);
public static T RandomElement<T>(this IEnumerable<T> source)
{
T current = default(T);
int c = source.Count();
int r = rng.Next(c);
current = source.Skip(r).First();
return current;
}
public static IEnumerable<T> RandomElements<T>(this IEnumerable<T> source, int number)
{
return source.OrderBy(r => rng.Next()).Take(number);
}
}
eu uso este método para obter notícias aleatórias e seu funcionamento;
public string LoadRandomNews(int maxNews)
{
string temp = "";
using (var db = new DataClassesDataContext())
{
var newsCount = (from p in db.Tbl_DynamicContents
where p.TimeFoPublish.Value.Date <= DateTime.Now
select p).Count();
int i;
if (newsCount < maxNews)
i = newsCount;
else i = maxNews;
var r = new Random();
var lastNumber = new List<int>();
for (; i > 0; i--)
{
int currentNumber = r.Next(0, newsCount);
if (!lastNumber.Contains(currentNumber))
{ lastNumber.Add(currentNumber); }
else
{
while (true)
{
currentNumber = r.Next(0, newsCount);
if (!lastNumber.Contains(currentNumber))
{
lastNumber.Add(currentNumber);
break;
}
}
}
if (currentNumber == newsCount)
currentNumber--;
var news = (from p in db.Tbl_DynamicContents
orderby p.ID descending
where p.TimeFoPublish.Value.Date <= DateTime.Now
select p).Skip(currentNumber).Take(1).Single();
temp +=
string.Format("<div class=\"divRandomNews\"><img src=\"files/1364193007_news.png\" class=\"randomNewsImg\" />" +
"<a class=\"randomNews\" href=\"News.aspx?id={0}\" target=\"_blank\">{1}</a></div>",
news.ID, news.Title);
}
}
return temp;
}
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);
Selecione 2 linhas aleatórias
Para adicionar à solução de Marc Gravell. Se você não estiver trabalhando com a classe do datacontext em si (porque você o faz proxy de alguma forma, por exemplo, para falsificar o datacontext para fins de teste), você não pode usar o UDF definido diretamente: ele não será compilado para SQL porque você não o está usando em um subclasse ou classe parcial de sua classe de contexto de dados reais.
Uma solução alternativa para esse problema é criar uma função Randomize em seu proxy, alimentando-o com a consulta que você deseja que seja randomizada:
public class DataContextProxy : IDataContext
{
private readonly DataContext _context;
public DataContextProxy(DataContext context)
{
_context = context;
}
// Snipped irrelevant code
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => _context.Random());
}
}
Aqui está como você o usaria em seu código:
var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);
Para ser completo, esta é a forma de implementar isso no datacontext FAKE (que usa em entidades de memória):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}