1

I have the following code inside my asp.net MVC web application:

var currentport = tms.TMSSwitchPorts
                     .Where(a => a.SwitchID == fromID)
                     .Select(a2 => a2.PortNumber)
                     .ToList();
            if (currentport.Any(tms.TMSSwitchPorts
                                   .Where(a => a.SwitchID == toID)
                                   .Select(a => a.PortNumber)
                                   .ToList()
                               )
               )
            {
              // do something
            }

but i am unable to use the .Any() in this way, although i am selecting the PortNumber field in both lists?

Can anyone advise?

Ben Aaronson
  • 6,955
  • 2
  • 23
  • 38
John John
  • 1
  • 72
  • 238
  • 501

4 Answers4

4

You can to this efficiently and entirely in the database using a simple Join:

var match = tms.TMSSwitchPorts.Where(a => a.SwitchID == fromID)
                              .Join(tms.TMSSwitchPorts.Where(a => a.SwitchID == toID),
                                    (a) => a.PortNumber,
                                    (b) => b.PortNumber,
                                    (a, b) => true).Any();

if (match) { ... }

The generated SQL should look like

SELECT 
    (CASE 
        WHEN EXISTS(
            SELECT NULL AS [EMPTY]
            FROM [TMSSwitchPorts] AS [t0]
            INNER JOIN [TMSSwitchPorts] AS [t1] ON [t0].[PortNumber] = [t1].[PortNumber]
            WHERE ([t0].[SwitchID] = @p0) AND ([t1].[SwitchID] = @p1)
            ) THEN 1
        ELSE 0
     END) AS [value]

so you don't have any data expect a single 1 or 0 moving over the wire and you don't have to clutter up your application memory.


The code

if (currentport.Any(tms.TMSSwitchPorts
                               .Where(a => a.SwitchID == toID)
                               .Select(a => a.PortNumber)
                               .ToList()
                           )

will not work because Any expects a predicate in the form of Func<T, bool>, but you pass it a List<Int> (assuming PortNumber is an int).

sloth
  • 99,095
  • 21
  • 171
  • 219
  • +1 This is the best solution if `tms` is a database query and not some other source. Don't want to edit in order to impose my style but I would love it if you moved the `Any()` to its own line :) – jwg May 22 '14 at 12:31
  • 1
    Yep, it wasn't made clear in the question that this was linq-to-sql (or -entities) or that minimizing data transfer from the db was an aim. With those things in mind, this is probably the preferred solution – Ben Aaronson May 22 '14 at 12:41
  • 1
    @BenAaronson Well, the question is tagged `entity-framework` :-) – sloth May 22 '14 at 12:43
  • @sloth Heh, well spotted – Ben Aaronson May 22 '14 at 12:46
1

Take the analogous list for SwitchID == toID.

var alreadyUsed = tms.TMSSwitchPorts
                     .Where(a => a.SwitchID == toID)
                     .Select(a2 => a2.PortNumber)
                     .ToList();

Then just check that nothing appears in both two lists.

if (currentPort.Intersect(alreadyUsed).Any())
{ // do something }

Explanation:

Any() doesn't work the way you think it does. By itself as used above, it checks for any elements in the container. As shown in @BenAaronson's answer (which is slightly better than this answer), it checks if an IEnumerable contains any element which for which the function argument returns true.

list1.Any(HasSomeProperty)

where HasSomeProperty is a function which takes an element of list1 and returns a bool; or more usually with a lambda:

list1.Any(x => SomePropertyHoldsFor(x))

Edit:

I said @BenAaronson's answer was better because it allows some 'short-circuiting' optimizations that I didn't think my solution had. These are mentioned in the comments to his answer. However, with some trial and error I found that Intersect does automatically make the same optimizations - specifically it caches the 'output' of one IEnumerable for comparison with each element of the other, rather than traverse it each time. So my solution is better, as Intersect does automatically what you have to think about a little bit for Ben's method ;) @sloth's answer involves a different automatic optimization, and is much is better for the database query using Queryable that the asker had in mind.

This answer explains how Intersect works - it is not really 'caching', but it only accesses each IEnumerable once.

Community
  • 1
  • 1
jwg
  • 5,547
  • 3
  • 43
  • 57
1

Based on jwg's comment, you can check to see if there is a match between the first set of ports (From) and the second set (to), using a Contains match:

var fromPorts = tms.TMSSwitchPorts
                 .Where(a => a.SwitchID == fromID)
                 .Select(a2 => a2.PortNumber);

if (tms.TMSSwitchPorts
       .Any(a => a.SwitchID == toID && 
                 fromPorts.Contains(a.PortNumber)))
StuartLC
  • 104,537
  • 17
  • 209
  • 285
0

If your aim is as jwg described in his comment, you could do:

var currentportFrom = tms.TMSSwitchPorts
                 .Where(a => a.SwitchID == fromID)
                 .Select(a2 => a2.PortNumber)
                 .ToList();
var currentportTo = tms.TMSSwitchPorts
                 .Where(a => a.SwitchID == fromID)
                 .Select(a2 => a2.PortNumber)
                 .ToList();

if(currentportFrom.Any(cp => currentportTo.Contains(cp))
{
    //do something
}
Ben Aaronson
  • 6,955
  • 2
  • 23
  • 38
  • 1
    but in this way i will be retrieving all the records from the DB , what i was trying to do is to issue Any() on two Ienumerables so that once any conflict occur to stop the procession ... – John John May 22 '14 at 12:18
  • This is a good solution. If you are really worried about efficiency, you should do `ToList` for `currentportTo`, so that that data is fetched only once and kept in memory, but *don't* do `ToList` for `currentportFrom`, so that the `Any()` stops as soon as a conflicting element is found. – jwg May 22 '14 at 12:23
  • @jwg I think what he wants is that each item is checked as soon as it's fetched from the database so you may not even finish enumerating the database a single time – Ben Aaronson May 22 '14 at 12:24
  • @BenAaronson If that is possible (depending on what `tms` is), it would work by doing it as I described. – jwg May 22 '14 at 12:26
  • It would be even better to make `currentportTo` a `HashSet` or some other container for which we can check membership much quicker than a `List`. – jwg May 22 '14 at 12:27
  • @jwg But wouldn't the `ToList` on `currentportTo` necessitate loading all the data from the database once? What about not calling `ToList` on either? – Ben Aaronson May 22 '14 at 12:27
  • @BenAaronson not calling `ToList` on either would be more optimal if the first element of `From` matches an element of `To` - because it would stop fetching `To` after that element - but worse in all other cases - because it would refetch the whole of `To` again and again for each element of `From`. I'm assuming the second case is more likely. – jwg May 22 '14 at 12:35
  • @jwg Really? LINQ-to-SQL doesn't optimize that? – Ben Aaronson May 22 '14 at 12:39
  • Not unless it's a lot cleverer than I thought it was - I might well be wrong. It seems that your method with one `ToList` and @sloth's cover both the ways it could be optimized so it would be surprising if one or other was done by implicitly. – jwg May 22 '14 at 12:50