当我有条件(例如某些字段必须为真)时,使用Linq to SQL检索随机行的最佳(最快)方法是什么?
当我有条件(例如某些字段必须为真)时,使用Linq to SQL检索随机行的最佳(最快)方法是什么?
Answers:
您可以通过使用伪造的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
实体框架的另一个示例:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
这不适用于LINQ to SQL。在OrderBy
简单地被丢弃。
编辑:我只注意到这是LINQ to SQL,而不是LINQ to Objects。使用Marc的代码获取数据库来为您完成此任务。我在这里留下了这个答案,作为LINQ to Objects的潜在兴趣点。
奇怪的是,您实际上并不需要计数。但是,您确实需要获取每个元素,除非获得计数。
您可以做的是保留“当前”值和当前计数的概念。当您获取下一个值时,请取一个随机数,然后以“ / n”的概率将“ current”替换为“ new”,其中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;
}
current
将始终设置为第一个元素。在第二次迭代中,有50%的更改将其设置为第二个元素。在第三次迭代中,有33%的机会将其设置为第三个元素。添加break语句意味着您将始终在读取第一个元素后退出,从而使其根本不是随机的。
一种有效实现的方法是在您的数据Shuffle
中添加一列,该列填充有一个随机的int(在创建每条记录时)。
以随机顺序访问表的部分查询为...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
这将对数据库执行XOR操作,并根据该XOR的结果进行排序。
优点:-
这是我的家庭自动化系统用来随机化播放列表的方法。它每天选择一个新种子,在一天中提供一致的顺序(允许轻松的暂停/恢复功能),但每天新查看每个播放列表。
result = result.OrderBy(s => s.Shuffle ^ seed);
即,无需通过〜,&和|运算符实现XOR)。
如果var count = 16
要从表中获取例如随机行,可以编写
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
在这里我使用了EF,而表是一个Dbset
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();
说明:通过插入guid(是随机的),具有orderby的订单将是随机的。
我有针对DataTable
s的随机函数查询:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
下面的示例将调用源以检索计数,然后在源上应用一个介于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;
}
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);
选择随机2行
添加到Marc Gravell的解决方案中。如果您不使用datacontext类本身(因为以某种方式代理它,例如为了测试目的而伪造datacontext),则不能直接使用定义的UDF:因为您未在数据库中使用它,所以它不会被编译为SQL。实际数据上下文类的子类或部分类。
解决此问题的方法是在代理中创建一个Randomize函数,并向其提供要随机化的查询:
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());
}
}
这是在代码中使用它的方式:
var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);
为了完整起见,这是在FAKE数据上下文(在内存实体中使用)中实现此方法的方法:
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}