2

Given the following

class MyClass
{
    private Expression<Func<SomeEntity, int>> _orderBy;

    public MyClass(Expression<Func<SomeEntity, int>> orderBy)
    {
        _orderBy = orderBy;
    }

    public List<SomeEntity> Fetch()
    {
        return DbContext.Set<SomeEntity>().OrderBy(_orderBy).ToList();
    }
}

// A function that creates an orderBy expression
Expression<Func<SomeEntity, int>> SomeFunction()
{
    // r is a local variable for the sake of simplicity,
    // actually it is a global static variable.
    Random r = new Random();
    int seed = r.nextInt();

    // s.SomeProperty XOR seed, a simple random sorting method
    return s => (s.SomeProperty & ~seed) | (~s.SomeProperty & seed);
}

And the executing code

var myClass = new MyClass( SomeFunction() );

List<SomeEntity> someList = myClass.Fetch();
Thread.Sleep(100000);
List<SomeEntity> anotherList = myClass.Fetch();

Each time Fetch() is called, a randomly sorted list must be returned. The problem is, seed = r.nextInt() won't be called each time I call Fetch(). How do I ensure that a new seed is generated every time Fetch() is called?

Kirk Broadhurst
  • 27,836
  • 16
  • 104
  • 169
mostruash
  • 4,169
  • 1
  • 23
  • 40
  • Is your question "How do I ensure r.nextInt() gets called every time I call Fetch()?" or something else? After seeing these comments I'm confused as to what you're trying to accomplish. – Steve Aug 14 '13 at 17:47
  • 1
    @Steve It is "How do I ensure that a new seed is generated every time Fetch() is called?". – mostruash Aug 14 '13 at 17:49
  • possible duplicate of [Randomize a List in C#](http://stackoverflow.com/questions/273313/randomize-a-listt-in-c-sharp) – Tim S. Aug 14 '13 at 17:54
  • @TimS. not at all, please look over the question itself, not just the title. – Scott Chamberlain Aug 14 '13 at 17:56
  • @TimS Title may not be the best out there, I'm welcome to edits. – mostruash Aug 14 '13 at 17:59
  • The end result is to take a list (whether it came from EF or not is irrelevant since you do a `ToList()`) of items and get it in a random order, isn't it? Maybe trying to do it with an `OrderBy` was natural because you already had `MyClass` or some other reason, but I still don't see what's really different. – Tim S. Aug 14 '13 at 17:59
  • @TimS This is not the real code and in the real code, there are other linq methods being called after `OrderBy()` and before `ToList()`. – mostruash Aug 14 '13 at 18:01
  • @mostruash what sort of things are between those two method calls? (maybe edit your question to add it, I think it's a significant detail) – Tim S. Aug 14 '13 at 18:03
  • 1
    @TimS This is a huge business application and there are lots of parameters that pushes me to find a solution to this specific problem. I cannot put the whole logic of my application repository. – mostruash Aug 14 '13 at 18:10
  • 1
    @mostruash I've retitled your question in an attempt to have people answer the actual question rather than answer the title without reading the body. – Kirk Broadhurst Aug 14 '13 at 18:17

5 Answers5

2

Simply move the call to r.Next() into your delegate. The following code (a bit dumbed down from your code since I didn't want to have to actually order anything) returns different numbers each time. Sorry for the poor variable names...

FYI, the variable r gets elevated out from the method SomeFunction() onto the heap, and will stick around and be used any time the Expression> gets called. See This question and the link in the answer for more information on closures.

EDIT: I've moved the random number generation to its own class that seems to work...

internal class MyClass {
    private Expression<Func<string, int>> _aProperty;

    public MyClass(Expression<Func<string, int>> aProperty) {
        _aProperty = aProperty;
    }

    public int Fetch() {
        var something = _aProperty.Compile();
        return something("doesn't matter");
    }
}

public class DoubleUp {
    private Random r = new Random();
    private int currentValue;
    public int A { get { return currentValue; } }
    public int B { get { int tmp = currentValue; currentValue = r.Next(); return tmp; } }

    public DoubleUp() {
        currentValue = r.Next();
    }
}

internal class Program {

    private static Expression<Func<string, int>> SomeFunction() {
        DoubleUp d = new DoubleUp();
        return s => (d.A - d.B);
    }

    private static void Main(string[] args) {
        var myClass = new MyClass(SomeFunction());
        Console.WriteLine(myClass.Fetch());
        Console.WriteLine(myClass.Fetch());
        Console.WriteLine(myClass.Fetch());
        Console.ReadLine();
    }
}
Community
  • 1
  • 1
Steve
  • 6,334
  • 4
  • 39
  • 67
  • 1
    I think the only way this would work is if you can call r.Next() once, since a statement block can't be converted to an Expression. – Steve Aug 14 '13 at 18:05
  • Would there be anyway to use `Guid.NewGuid` method to make this work (as it translates to `NEWID()` in MSSQL)? Warning: there are 2 `seed`s in the delegate, using 2 `Guid.NewGuid` would generate distinct guids, which is not what I intend to do. – mostruash Aug 14 '13 at 18:06
  • I honestly don't know, I think I've reached my limit on how much I might be able to help you, if this really is any help at all :) – Steve Aug 14 '13 at 18:08
  • I appreciate it Steve. – mostruash Aug 14 '13 at 18:11
  • This works. I can't confirm that it will work with Entity Framework, but I would expect it *should* given that the delegate is a bona-fide Expression. – Kirk Broadhurst Aug 14 '13 at 18:13
  • @KirkBroadhurst it does not, it was the first thing I tried when I tried create my solution, you get a `NotSupportedException` with the message "*LINQ to Entities does not recognize the method 'Int32 Next()' method, and this method cannot be translated into a store expression.*" – Scott Chamberlain Aug 14 '13 at 18:19
  • I'm about to significantly change my code sample, it might work, please try. – Steve Aug 14 '13 at 18:27
  • `d.B` is actually a shortcut for `d.GetB()` and in `GetB()`, `r.Next()` is called. So I think that EF won't be able to translate it again. I will try it after I try the Factory suggestion made by Scott Chamberlain. – mostruash Aug 14 '13 at 18:34
  • 1
    It does work, I am surprised. However you should know that EF has some limitations that just `Compile()` would not catch. However I turned it in to a real example using EF and it did work. Mind if I edit your code to the code I tested with to show it worked with EF? – Scott Chamberlain Aug 14 '13 at 18:35
  • I am wondering if there is a possibility that the get methods for A and B might be called in the wrong order, resulting in two different values instead of one? – Steve Aug 14 '13 at 18:43
  • I rolled back the change, I did not account for the fact that it was called twice. – Scott Chamberlain Aug 14 '13 at 18:48
  • It works fine, Scott. I didn't include any reference to LINQ to Entities at all. – Kirk Broadhurst Aug 14 '13 at 18:57
  • @Scott, if your answer does work, feel free to post it as another answer so you can get credit! – Steve Aug 14 '13 at 18:58
0

One possible solution is pass in a factory function that generates the order by functions instead of the function itself, now it generates a new Expression<Func<SomeEntity, int>> every time you call Fetch() each with a new seed.

class MyClass
{
    Func<Expression<Func<SomeEntity, int>>> _orderByFactory;

    public MyClass(Func<Expression<Func<SomeEntity, int>>> orderByFactory)
    {
        _orderByFactory = orderByFactory;
    }


    public List<SomeEntity> Fetch()
    {
        var orderBy = _orderByFactory();

        return DbContext.Set<SomeEntity>().OrderBy(orderBy).ToList();
    }
}

//...

// Moved r out of the function as it needs to be outside for it to work.
static Random r = new Random();

static Expression<Func<SomeEntity, int>> SomeFunction()
{
    int seed = r.Next();

    // s.SomeProperty XOR seed, a simple random sorting method
    return s => (s.SomeProperty & ~seed) | (~s.SomeProperty & seed);
}

//...

var myClass = new MyClass(SomeFunction); //<--Notice the removed parenthesis
List<SomeEntity> someList = myClass.Fetch();
Thread.Sleep(100000);
List<SomeEntity> anotherList = myClass.Fetch();
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
0

Make the expression a Func that you call to get a new Orderby each time Fetch is called:

class MyClass
{
    private Func<Expression<Func<SomeEntity, int>>> _orderByFactory;

    public MyClass(Func<Expression<Func<SomeEntity, int>>> orderByFactory)
    {
        _orderByFactory = orderByFactory;
    }

    public List<SomeEntity> Fetch()
    {
       var orderBy = _orderByFactory();
       return DbContext.Set<SomeEntity>().OrderBy(orderBy).ToList();
    }
}

And the call becomes

var myClass = new MyClass( () => SomeFunction() );
List<SomeEntity> someList = myClass.Fetch();
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
0

The problem is you only call SomeFunction once to set the seed. And you can't reset the seed within the Expression because an expression cannot contain a function body.

An alternative would be to use 4 bytes of a new Guid as a pseudo-random number generator:

// A function that creates an orderBy expression
Expression<Func<int, int>> SomeFunction()
{
    // Take 4 bytes from a new Guid and turn it into a 32-bit integer
    return s=> BitConverter.ToInt32(Guid.NewGuid().ToByteArray(),0);
}
D Stanley
  • 149,601
  • 11
  • 178
  • 240
0

Given your problem statement:

Each time Fetch() is called, a randomly sorted list must be returned.

It seems to me you're over-thinking things. Why on earth wouldn't you just do something simple like:

class MyClass
{
  private static Random rng = new Random() ;

  public List<SomeEntity> Fetch()
  {
    return DbContext.Set<SomeEntity>()
                    .Cast<SomeEntity>()
                    .OrderBy( x => rng.Next() )
                    .ToList()
                    ;
  }

}
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • Because that are other things that you do not know. A very minor example would be, I need to be able to produce the same ordering in the future. – mostruash Aug 14 '13 at 18:26