views:

323

answers:

9

Using the following logic, what would the correct syntax be for a single LINQ query?

If Branch is Service, I want its parent, otherwise I want Branch.

Can you critique my attempt and let me know how I can improve it?

int branchId = 21;
var t = ctx.BranchInfos.Single(p => p.BranchID == branchId );
if (t.Type == BranchType.Service.ToString())
{
    t = ctx.BranchInfos.Single(p => p.BranchID == t.Parent);
}
A: 

I am quite sure you can do it with a single LINQ statement, but I am equaly sure that you should not do this. It will not improve the readabilty and hardly the performance.

var t = ctx.BranchInfos.Single(x =>
  (
    x.BranchID == branchID &&
    x.Type != BranchType.Service.ToSting()
  )
  ||
  (
    ctx.BranchInfos.Any(
      y.BranchID == branchID) &&
      y.Type == BranchType.Service.ToSting()) &&
    x.BranchID == ctx.BranchInfos.Single(
      y.BranchID == branchID) &&
      y.Type == BranchType.Service.ToSting()).ParentID
  )
);

Nice, isn't it? :D I still suggest not to use it. The first case is simple - if the item has the correct ID and and is not of type Service we have a match.

The second case is more tricky. We have to check if the item has the ID from the ParentID property of the item with the supplied ID but only if the item with the supplied ID is of type Service. Because we do not know if there is an item with the supplied ID and type Service when we check this, we must first check with Any() if there is such an item and rely on the conditional evaluation of the and.

Daniel Brückner
The first point is debatable and the latter is just silly. Two round-trips to the database is the same as one?
Adam Robinson
If it is in a loop somewhere, then you should strive for one round trip.If it executes once in a while, readability should be your priority.
Lars Mæhlum
Readability: Try to find an expression and there will probably be no room left for discussion. Performance: I am at Lars's point - you should really have a good reason to introduce such an unreadable construct. If you decide to use an OR mapper and may be lazy loading you have already made the decission that database accesses are not that performance critical.
Daniel Brückner
That's hardly a fair statement to say that the use of ORM is an automatic indication that database performance doesn't matter, nor does it mean that you should make arbitrary decisions that favor a rather myopic view of "readability". Readability means it should be reasonably obvious what the code does, and this doesn't really appear to be very ambiguous.
Adam Robinson
Especially in the case of an lazy loading ORM - for me this is realy a clear statement that you do not care if it takes one, five, or ten round trips to fetch something - may be a collection of a few entites. of course you will try to get the best out of the ORM and tweek some hot spots. But if database performance would be *realy* critical, you would not use an ORM. At least I would not.
Daniel Brückner
Bah, you're kidding. You can actually tune an ORM solution you know!? Plus, you can eager load entities, it's not all lazy loading.
RobS
@Daniel: That's a HUGE leap of logic to say that using an ORM is a "clear statement" that you don't care how many round trips you have. What sort of other-worldly logic, research, or anything else do you base that nonsensical assertion on?
Adam Robinson
I consider eager loading a form of tuning. You give away the comfort of having your entites loaded automatically by the ORM and you add explicit or implicit eager loading where requiered to get better performance. And I did not want to state that you do not care at all, but you do usually not care about one, two, or three round trips. why do you use an ORM? Because it simplifies your code, lets you concentrate on the core concerns, takes away technical details. It is just an form of abstraction.
Daniel Brückner
The same kind of abstraction you use when you write your application in C# and not assembler. And you pay for this convienence with lower performance. You accept that the system providing the abstraction will usually not give the performance of a hand optimized solution because it allows you to write much less complex code. But if you really care about performance you will switch from C# back to assembler or from an ORM to hand written SQL commands.
Daniel Brückner
That is what I wanted to express. Replacing this four clear lines of code with such an unreadable LINQ query is against the reason of using an ORM. If you really care about this single round trip and you have to use hacks or over complex code (for the task) to make it fit into the ORM, why not just give up the abstraction and resort to simple SQL commands?
Daniel Brückner
A: 
var t = ctx.BranchInfos.Single(
  p => (p.BranchID == branchId && p.Type != BranchType.Service.ToString) ||
       (p.BranchID == GetBranchParentId(branchId) && p.Type == BranchType.Service.ToString));

where GetBranchParentId is a function wich returns the BranchId of the Branch who's id is passes as a parameter.

But, I like your original code, so I wouldn't use a single query to get my data.

eKek0
Seems like a single rount-trip to the database is better than two.
Adam Robinson
This will eventually not work. You are assuming that the type of the parent is Service and the expression throws an exception if the type is not Service and there is a parent with type Service because you get two elements matching the predecate while SIngle() exspects one.
Daniel Brückner
A: 

I think you could do something like this:

var t = ctx.BranchInfos.FirstOrDefault(p => p.BranchID == branchId || p.BranchID == t.Parent);

Mike

Olivieri
This will not work - you use t.Parent before you assigned something to t.
Daniel Brückner
A: 

This will probably get you what you need unless I messed up the logic.

Edit: Different approach

var t = ctx.BranchInfos.Where(p.BranchID == branchId).First(p => p.Type == BranchType.Service.ToString() ? p.Parent : p);
Jeremy
Daniel Brückner
returns child id only
DotDot
Second try, let me know how it goes
Jeremy
looks good although i dont know why no one is using firstordefault / singleordefault.
Shawn Simon
A: 

This'll probably work but it's a bit awkward.

var t = ctx.BranchInfos.Where(p => p.BranchID == branchId)
    .Select(p => 
      p.Type != BranchType.Service.ToString() 
      ? p 
      : ctx.BranchInfos.Single(t => p.Parent == t.BranchId)).FirstOrDefault();
Jacob Proffitt
Best solution up now and I thought of it, too. But I doubt that LINQ to SQL or Entity can handle the conditional operator. Anyway +1
Daniel Brückner
Single does not Support Select
DotDot
Just replace the first Single() with Where() and add a Single() to the end.
Daniel Brückner
youd probably want firstordefault at the end
Shawn Simon
Good input, actually. Edited to include both Where and FirstOrDefault.
Jacob Proffitt
+2  A: 

I suggest that if this is only needed in one place then what you have now is reasonably clear and should be kept.

If you are doing this a lot then do something like:

public static BranchInfo BranchOrServiceParent(
    this IEnumerable<BranchInfo> input)
{ 
    var t = BranchInfos.Single(p => p.BranchID == branchId);
    if (t.Type == BranchType.Service.ToString())    
        t = input.BranchInfos.Single(p => p.BranchID == t.Parent);
    return t;
}

Then using it is as simple as:

int branchId = 21;
var t = ctx.BranchInfos.BranchOrServiceParent();

If you subsequently need to parameterize/change thing things you can in a clear fashion.

If you subsequently find that the two possible trips to the database are a performance issue then you can either try a complex Linq query or accept that this probably needs to actually be done by a stored procedure.

ShuggyCoUk
A: 

I believe the following is equivalent to your code sample. I have added some mock code to turn this into a self-contained example.

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Context ctx = new Context();
            ctx.BranchInfos.Add(new BranchInfo() { Type = "NonService", BranchID = 20, Parent = 0 });
            ctx.BranchInfos.Add(new BranchInfo() { Type = "Service", BranchID = 21, Parent = 20 });
            ctx.BranchInfos.Add(new BranchInfo() { Type = "NonService", BranchID = 30, Parent = 20 });

            int branchId = 21;

            var t = (from a in ctx.BranchInfos
                     where a.BranchID == branchId
                     select a.Type != BranchType.Service.ToString() ? a :
                     (from b in ctx.BranchInfos
                      where b.BranchID == a.Parent
                      select b).Single()).Single();

            Console.WriteLine(t.BranchID); // Prints 20
        }

        class Context
        {
            public List<BranchInfo> BranchInfos = new List<BranchInfo>();
        }

        class BranchInfo
        {
            public string Type;
            public int BranchID;
            public int Parent;
        }

        enum BranchType
        {
            Service = 0
        }
    }
}
binarycoder
needs 2 sql queries
DotDot
A: 
var t = ctx.BranchInfos.Where(p =>
 (
   p.BranchID == branchID &&
   p.Type != BranchType.Service.ToSting()
 )
 ||
 (
   p.Type == BranchType.Service.ToSting() &&
   ctx.BranchInfos.Where(p => p.BranchID == branchID).FirstOrDefault() != null &&
   p.BranchID == ctx.BranchInfos.Where(p => p.BranchID == branchID).FirstOrDefault().ParentID
 )).FirstOrDefault();

The logic here is: (Get me Branch By ID IF the type is of service) OR (get me the parent of a Branch where I know the the child ID if the branch type is Service)

ALSO:

Even though there's a subquery in there, it will evaluate to a single hit to the DB because you're using the same Datacontext in the subquery.

andy
You are assuming that the parent has type Service but I am not sure if this is justified.
Daniel Brückner
no. I'm saying (in the second OR condition): Just get me all Branches that have type Service. Out of those, get me the one where the ID matched the ParentID of the Branch that has ID = 21
andy
Crash if branch is child.
DotDot
hmm...thanks Rajiv, good catch. I've edited it, any better?
andy
Yes ... get me all items of type Service and then you pick the one with the id from ParentID of the branch with ID 21. So you imply that this step has the type Service. But there is no statement in the question saying that the parent of a non-service branch is of type service. Further you check Single() != null - Single() never returns null but throws an exception. Finally you are reusing the parameter name p in the subquery.
Daniel Brückner
Will crash if Branch is not of type Service and the parent is of type Service. In this case the condition will be true for both entities and Single() will throw an exception because the sequence contains more than one element.
Daniel Brückner
andy
Assume the branch with ID 21 is of type Serice, ParentID is 50, and the item with ID 50 is not of type Service. At some point p.BranchId will be 21. Because the type is Service the first case is false. In the second case it passes the test type == Service but fails p.BranchID == [...].ParentID. So we have no match.
Daniel Brückner
Later p.BranchId will be 50. The first case will fail because the ID does not match. In the second case it would pass p.BranchID == [...].ParentID but it does not reach this point because p.Type == BranchType.Service already fails. So we have no match, too.
Daniel Brückner
A: 

Provided BranchInfo.Parent is the same type as BranchInfo:

int branchID;
var branchOrParent = db.BranchInfos
    .Where(b => b.BranchID == branchID)
    .Select(b => b.Type == BranchType.Service.ToString() ? b.Parent : b)
    .FirstOrDefault();
Shawn Simon