0

My question is illustrated by code sample below. How do I place part of the lambda elsewhere ?

How do I call the 'Foo' from my 'GetGurus()' method ? I want the LINQ to translate it to 1 statement.

public enum GuruLevel
 {
    NotSet,
    Goku,
    SuperSayan
}

 private IEnumerable<PersonInfo> GetGurus()
 {
    using (var context = new CRMContext())
    {
        var persons = context.Person
            .Where(p => p.Experience > 10)
            .OrderBy(p => p.DateOfBirth)
            .Select(p => new PersonInfo())
            {
                StackOverFlowName = p.StackOverFlowName,
                Experience = p.Experience,
                GuruStatus = Foo //(p.Experience > 9000) ? GuruLevel.SuperSayan : GuruLevel.Goku
            }
        return persons.ToList();
    } 
 }

public static System.Linq.Expressions.Expression<Func<Person, GuruLevel>> Foo
{
    get
    {
        return bar => (bar.Experience > 9000) ? GuruLevel.SuperSayan : GuruLevel.Goku;
    }
}
jeroenh
  • 26,362
  • 10
  • 73
  • 104
  • http://stackoverflow.com/questions/27324641/reusable-calculations-for-linq-projections-in-entity-framework-code-first – jeroenh Nov 24 '16 at 12:43
  • I am reading your link, it seems this is exactly what I need. Although it seems quite complex to implement :( – Tony_KiloPapaMikeGolf Nov 24 '16 at 13:06
  • 1
    Too bad you did not post an answer, because you gave the best answer! I installed [DelegateDecompiler](https://www.nuget.org/packages/DelegateDecompiler/). and it works ! – Tony_KiloPapaMikeGolf Nov 24 '16 at 13:23
  • in fact, this question turns out to be a duplicate of the question I referred to. Ideally it should be closed because of that. – jeroenh Nov 24 '16 at 13:36
  • Our end-users are also very happy now! The performance of our projects queries have boosted beyond expectations! I am loving it! – Tony_KiloPapaMikeGolf Nov 24 '16 at 14:46

3 Answers3

1

This logic is the responsibility of PersonInfo class. This code is usually placed in the constructor or factory class and it's fully compliant with the Single Responsibility Principle.

class PersonInfo
{
    public string StackOverFlowName { get; set; }
    public int Experience { get; set; }
    public GuruLevel GuruStatus { get; set; }

    public void PersonInfo(Person p)
    {
      StackOverFlowName = p.StackOverFlowName;
      Experience = p.Experience;
      GuruStatus = p.Experience > 9000 ? GuruLevel.SuperSayan : GuruLevel.Goku;
    }
}

Then change your service method to:

private IEnumerable<PersonInfo> GetGurus()
{
   using (var context = new CRMContext())
   {
       var persons = context.Person
           .Where(p => p.Experience > 10)
           .OrderBy(p => p.DateOfBirth)
           .ToList()
           .Select(p => new PersonInfo(p))
       return persons.ToList();
   } 
}

Or move responsibility to factory:

public class PersonInfoFactory
{
    public PersonInfo Create(Person person)
    {
        return new PersonInfo
        {
            StackOverFlowName = p.StackOverFlowName,
            Experience = p.Experience,
            GuruStatus = ExperienceBasedStatus(p.Experience)
        };
    }

    private GuruLevel ExperienceBasedStatus(int experience) => experience > 9000 ? GuruLevel.SuperSayan : GuruLevel.Goku;
}
Pawel Maga
  • 5,428
  • 3
  • 38
  • 62
  • If `CrmContext` is EF6, this will crash in runtime during query translation. Constructors with parameters are not supported. – Impworks Nov 24 '16 at 12:38
  • I like it, but the question was about splitting the lambda. Still +1 – bixarrio Nov 24 '16 at 12:38
  • @Impworks that's true, he may execute this query before initialization. – Pawel Maga Nov 24 '16 at 12:41
  • @bixarrio I'm trying to avoid answers which generate more code smell samples. If author knows `Expression` class, I assume that he is able to transform this property into a method. – Pawel Maga Nov 24 '16 at 12:43
  • In this simplified code sample this can be done. In the real world 'GuruLevel' is calculated on many more fields not available in PersonInfo, but available in Person. – Tony_KiloPapaMikeGolf Nov 24 '16 at 12:44
  • @Tony_KiloPapaMikeGolf you have public access to `Person` class, because it's passed as a parameter in constructor. If you have complex logic you should create Factory class for it. – Pawel Maga Nov 24 '16 at 12:45
1

It's not a direct answer for your question, but it relates to the reason you asks.

I assume, you are working with some kind of DB, so you cant use regular method as they wont be known by the context of DB.

For example, you actually would be able to execute private method that doing something with data, but you cant use custom types or classes. In you example 'GuruLevel' would nt be recognized by DB.

I would suggest not to try to do a conversion on the side of DB. Instead retrieve all the data you need in the format of DB and do the conversion on the application side.

using (var context = new CRMContext())
{
    var persons = context.Person
        .Where(p => p.Experience > 10)
        .OrderBy(p => p.DateOfBirth)
        .Select(p => new {p.StackOverFlowName, p.Experience }) //or whole object, or other fields if needed
        .ToList() //executes query, all following code will be in a context of application not DB
        .Select(p => new PersonInfo())
        {
            StackOverFlowName = p.StackOverFlowName,
            Experience = p.Experience,
            GuruStatus = GetGuruStatus(p.Experience)
        }
    return persons.ToList();
}

private GuruLevel GetGuruLevel(int exp)
{
    return exp > 9000 ? GuruLevel.SuperSayan : GuruLevel.Goku
}
Pavel Luzhetskiy
  • 789
  • 1
  • 8
  • 26
0

Change this;

public static Func<Person, GuruLevel> Foo
{
    get
    {
        return bar => (bar.Experience > 9000) ? GuruLevel.SuperSayan : GuruLevel.Goku;
    }
}

then;

private IEnumerable<PersonInfo> GetGurus()
{
    using (var context = new CRMContext())
    {
        var persons = context.Person
            .Where(p => p.Experience > 10)
            .OrderBy(p => p.DateOfBirth)
            .Select(p => new PersonInfo())
            {
                StackOverFlowName = p.StackOverFlowName,
                Experience = p.Experience,
                GuruStatus = Foo(p)
            }
        return persons.ToList();
    } 
}

Just a note; this is a strange way of doing it. You could just have made Foo a normal function and it would've been fine;

public GuruLevel Foo(Person bar)
{
    return bar.Experience > 9000 ? GuruLevel.SuperSayan : GuruLevel.Goku;
}
...
GuruStatus = Foo(p)
bixarrio
  • 373
  • 1
  • 10
  • Thank you for trying, but this results in: '**The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.**' – Tony_KiloPapaMikeGolf Nov 24 '16 at 12:36
  • 1
    @Tony_KiloPapaMikeGolf the answer would work in linq-to-objects; you didn't specify you're working in the context of EF – jeroenh Nov 24 '16 at 12:40
  • Didn't pay attention. I did not know it was EF. Perhaps my edit will work 'cos its a standard function. Not sure. I don't work with EF – bixarrio Nov 24 '16 at 12:41