views:

3016

answers:

7

Hi,

How do i return matching entities in a random order?
Just to be clear this is Entity Framework stuff and LINQ to Entities.

(air code)

IEnumerable<MyEntity> results = from en in context.MyEntity
                                where en.type == myTypeVar
                                orderby ?????
                                select en;

Thanks

Edit:
I tried adding this to the context:

public Guid Random()
{
    return new Guid();
}

And using this query:

IEnumerable<MyEntity> results = from en in context.MyEntity
                                where en.type == myTypeVar
                                orderby context.Random()
                                select en;

But i got this error:

System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Guid Random()' method, and this method cannot be translated into a store expression..

Edit (Current code):

IEnumerable<MyEntity> results = (from en in context.MyEntity
                                 where en.type == myTypeVar
                                 orderby context.Random()
                                 select en).AsEnumerable();
+7  A: 

A simple way of doing this is to order by Guid.newGuid() but then the ordering happens on the client side. You may be able to persuade EF to do something random on the server side, but that's not necessarily simple.

Ordering is O(n log n) of course. You could instead load everything into a list and then shuffle the list, which is only O(n). That will take a little bit more code though :)

EDIT: To make the ordering happen on the .NET side instead of in EF, you need AsEnumerable:

IEnumerable<MyEntity> results = context.MyEntity
                                       .Where(en => en.type == myTypeVar)
                                       .AsEnumerable()
                                       .OrderBy(en => context.Random());
Jon Skeet
Hi Jon, I tried this, but couldn't get it working - see my edit. Thanks Nath
DeletedAccount
I don't see a .ToEnumerable() am I missing a namespace?
DeletedAccount
Doh. AsEnumerable.
Jon Skeet
Hi Jon, I still get the same error as above with that change. Also the .OrderBy(context => context.Random()); line doesn't work as I get an error about redifining the context. I'll update my q with my latest query. Thanks
DeletedAccount
I'm so sorry - this is what comes of trying to answer in tiny bits of free time. The AsEnumerable() should be fine, but the lambda expression was broken. Try it now.
Jon Skeet
No worries, your help is appreciated. :o) I'm just commenting out my changes from Toro's answer and I'm giving it a go.
DeletedAccount
It builds and my unit tests seem to run ok. Thanks!
DeletedAccount
doesn't seem to give a random result tho.
DeletedAccount
+1 for IEnumberable ;)
Jibberish
@Jibberish: Oops - fixed. You can take back the upvote :)
Jon Skeet
+6  A: 

The simple solution would be creating an array (or a List<T>) and than randomize its indexes.

EDIT:

static IEnumerable<T> Randomize<T>(this IEnumerable<T> source) {
  var array = source.ToArray();
  // randomize indexes (several approaches are possible)
  return array;
}

EDIT: Personally, I find the answer of Jon Skeet is more elegant:

var results = from ... in ... where ... orderby Guid.NewGuid() select ...

And sure, you can take a random number generator instead of Guid.NewGuid().

Michael Damatov
Hi toro, Sorry, I'm not seeing what method to use on a List<T>, could you elaborate? thanks.
DeletedAccount
There isn't a framework method to do this. I suggest http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
mquander
Thanks mquander, that's what I was after.
DeletedAccount
+1  A: 

How about this:


    var randomizer = new Random();
    var results = from en in context.MyEntity
                  where en.type == myTypeVar
         let rand = randomizer.Next()
         orderby rand
         select en;

Klinger
I get a similar error to the Guid method in my edit: LINQ to Entities does not recognize the method 'Int32 Next()' method, and this method cannot be translated into a store expression..
DeletedAccount
The AsEnumerable operator posted on Jon's answer should solve the issue.
Klinger
still the same error with .AsEnumberable() added.
DeletedAccount
The following code works on my environment: var randomizer = new Random(); var result = context.MyEntity .Where(en => en.type == myTypeVar) .AsEnumerable() .OrderBy(en => randomizer.Next());
Klinger
yes, it seems to work that way round, but not in the from en in context... format.
DeletedAccount
A: 

Toro's answer is the one I would use, but rather like this:

static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
  var list = source.ToList();
  var newList = new List<T>();

  while (source.Count > 0)
  {
     //choose random one and MOVE it from list to newList
  }

  return newList;
}
Migol
There's no need to create two lists - you can just swap elements in the list in a shuffle style way. This needs to be done with a bit of care, but it's better (IMO) than creating another copy for no reason.
Jon Skeet
You can, but it would make code less readable. IMHO this way is better, because it's more clear. Remember that we operate mainly on references, not values so there isn't much memory cost except for the list itself.
Migol
+1  A: 

The solutions provided here execute on the client. If you want something that executes on the server, here is a solution for LINQ to SQL that you can convert to Entity Framework.

Fabrice
A: 

Here is a nice way of doing this (mainly for people Googling).

You can also add .Take(n) on the end to only retrieve a set number.

model.CreateQuery<MyEntity>(   
    @"select value source.entity  
      from (select entity, SqlServer.NewID() as rand  
            from Products as entity 
            where entity.type == myTypeVar) as source  
            order by source.rand");
Jamie