从 Linq 到 Sql 的随机行

当我有一个条件(例如某个字段必须为 true)时,使用 Linq to SQL 检索随机行的最佳(和最快)方法是什么?

72144 次浏览

编辑: 我刚刚才注意到这是 LINQtoSQL,而不是 LINQtoObjects。用 Marc 的代码让数据库为你做这件事。我把这个答案留在这里,作为 LINQ to Objects 的一个潜在兴趣点。

Strangely enough, you don't actually need to get the count. You do, however, need to fetch every element unless you get the count.

您可以做的是保留“当前”值和当前计数的概念。当获取下一个值时,取一个随机数,并将“当前”替换为“新”,其概率为1/n,其中 n 为计数。

因此,当读取第一个值时,一直都是将其设置为“当前”值。当您读取第二个值时,您将 也许吧设置为当前值(概率1/2)。当你读取第三个值,你 也许吧使得当前值(概率1/3)等。当你用完了数据,当前值是你读取的所有数据中的一个随机值,具有统一的概率。

要将其应用于条件,只需忽略任何不符合条件的内容。最简单的方法是先应用 Where 子句,从“匹配”序列开始。

这里有一个快速的实现。我 好好想想它的好..。

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;
}

您可以在数据库中通过使用假的 UDF 来实现这一点; 在分部类中,向数据上下文添加一个方法:

partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}

然后仅使用 order by ctx.Random(); 这将在 SQL-Server 上执行由 NEWID()提供的随机排序。

var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();

注意,这只适用于中小型表; 对于大型表,它会对服务器的性能产生影响,而且找到行数(Count) ,然后随机选择一个(Skip/First)会更有效率。


点算方法:

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

有效实现的一种方法是向数据 Shuffle中添加一列,该列由一个随机 int (在创建每条记录时)填充。

以随机顺序访问表的部分查询是..。

Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);

这将在数据库中执行一个 XOR 操作,并根据该 XOR 的结果进行排序。

优点:-

  1. 高效: SQL 处理 订购,不需要把整个 桌子
  2. 可重复性: (适用于 测试)-可以使用相同的随机 seed to generate the same random 秩序

这是我的家庭自动化系统用来随机播放列表的方法。它每天选择一个新的种子,在一天中给出一个一致的顺序(允许容易暂停/恢复功能) ,但每个新的一天新的看法每个播放列表。

实体框架的另一个示例:

var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();

这对 LINQtoSQL 不起作用。 OrderBy被简单地删除了。

如果获取随机行的目的是采样,那么我已经非常简短地讨论了微软研究团队 Larson 等人的一个很好的方法 给你,他们使用物化视图为 Sql Server 开发了一个采样框架。还有一个链接到实际的文件。

如果你想从表中得到例如 var count = 16随机行,你可以写

var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);

这里我用的是 E.F 表格是 Dbset

我对 DataTable进行了随机函数查询:

var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);

来到这里想知道如何获得一些随机页面从他们的一小部分,这样每个用户得到一些不同的随机3页。

这是我的最终解决方案,使用 LINQ 对 Sharepoint 2010中的页面列表进行查询。它在 Visual Basic 中,抱歉: p

Dim Aleatorio As New Random()


Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3

也许在查询大量结果之前应该先进行一些分析,但这对我的目的来说是完美的

在 LINQPad 中使用 LINQtoSQL 作为 C # 语句

IEnumerable<Customer> customers = this.ExecuteQuery<Customer>(@"SELECT top 10 * from [Customers] order by newid()");
customers.Dump();

生成的 SQL 是

SELECT top 10 * from [Customers] order by newid()

下面的示例将调用源来检索一个计数,然后对数字介于0和 n 之间的源应用一个跳过表达式。第二个方法将通过使用随机对象(它将对内存中的所有内容进行排序)来应用顺序,并选择传递给方法调用的数字。

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);
}
}

我使用这种方法对随机新闻及其工作进行精细化处理;)

    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;
}

如果你使用 LINQPad,切换到 C# program模式,这样做:

void Main()
{
YourTable.OrderBy(v => Random()).FirstOrDefault.Dump();
}


[Function(Name = "NEWID", IsComposable = true)]
public Guid Random()
{
throw new NotImplementedException();
}
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);

Select random 2 row

为马克 · 格拉维尔的解决方案添砖加瓦。如果您不使用 datacontext 类本身(因为您以某种方式代理它,例如为了测试目的伪造数据上下文) ,您不能直接使用定义的 UDF: 它不会被编译成 SQL,因为您没有在实际数据上下文类的子类或部分类中使用它。

解决这个问题的一个办法是在代理中创建一个随机化函数,然后向它提供您想要被随机化的查询:

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());
}
}

Here is how you'd use it in your code:

var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);

为了完整起见,下面是如何在 FAKE 数据上下文(在内存实体中使用)中实现这一点:

public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}