How can I attach a result of two linq queries

Viewed 98

I have two LINQ's which is below

public IEnumerable<TurbineStatus> turStatus()
{

    var result1 = (from s in _db.masterData
                  group s by s.current_turbine_status into g
                  select new TurbineStatus
                  {
                      status = g.Key,
                      numberOfTurbines = g.Count()
                  }
                ).ToList().OrderByDescending(s => s.status);

    int  result12= _db.masterData.Select(s=>s.turbine_name).Count();
    
    return result;


}

here is my TurbineStatus class:

public string status { get; set; }
public int numberOfTurbines { get; set; }
  

The first query returns me turbines with their status.

The second one returns me the number of all turbines.

How can I attach them together?

For example: the result1 sample query result is("RUN":2,"STOP":4,...). That means that there are 4 turbines stopped and 2 turbines are running.

The second linq(result2) returns the number of all turbines, which is an integer.

3 Answers

You should rethink your class-structure. A single Status that knows about other instances of that class in order to count them breaks the Single-Repsoibility-Principle. So a Status is expected to be just a Status, not some aggregated information about other Status as well.

So I would recommend to make your Status just an enum:

enum TurbineStatus { OnRun, Stop }

Now group the turbines as you already do, but store the number of the items per status not in the status itself, but in a Dictionary, or even another class or - as in my code - in a tuple:

public IEnumerable<(TurbineStatus, int)> turStatus()
{
    var result = (from s in _db.masterData
              group s by s.current_turbine_status into g
              select (g.Key, numberOfTurbines = g.Count())
            ).ToList().OrderByDescending(s => s.status);

    return result;
}

or with a Dictionary:

public Dictionary<TurbineStatus, int> turStatus()
{
    var result = (from s in _db.masterData
                 group s by s.current_turbine_status into g)
                 .ToDictionary(x => x.Key, x => x.Value);
    return result;
}    

However be aware that you can´t - or shouldn´t - order a dictionary. If you need that use a SortedDictionary instead.

If you also want the count of all items within the entire database, you may introduce an out-parameter as @Fildor suggested to either method. Or add another query after calling the above method:

var turstatus = turstatus();
var totalCount = turStatus.Select(x => x.Item2).Sum(); // assuming the first (tuple-based) approach)
var totalCount = turStatus.Select(x => x.Value).Sum(); // this is for the dictionary-based approach

Disclaimer: As @HimBromBeere mentioned in comments, having a Status class, that actually "knows" how many instances of it there are is not so clean a design, so you may want to reconsider that.

That out of the way: "How do I return 2 separate things?"

  1. Return + out param
     // signature of the function would be like this:
     int GetTurbineNumbers( out IEnumerable<TurbineStatus> ) // again: reconsider that class

     // the other way round
     IEnumerable<TurbineStatus> GetTurbineNumbers( out int totalTurbines )

Where you set the out-param to your list result and simply return the total number (or the other way round).

  1. Create a result-class
class TurbineStatistics{
    public IEnumerable<TurbineStatus> {get; set;}
    public int Total {get; set;}
}

Create an instance, set the properties and return it.

  1. Return a Tuple
     (IEnumerable<TurbineStatus>, int) GetTurbineNumbers()
  1. Return a Dictionary
     IDictionary<string, int> GetTurbineNumbers()
     {
          // fill a dictionary with statusname, count and "total", count
          // return it
     }

Of course, instead of string you could also use a Status Enum as Key. It's just that that enum would have to have a "Total" item, which would again make it a little "dirty". What is a Status "Total" supposed to be. Of course, you could make a special enum "TurbineStatusStatisticItems" or something like that ... but I digress.

  1. See HimBromBeere's answer.

Just to name a few possibilities.

BUT Ask yourself "should I do this at all?". A function, that returns "2 things" kind of destroys the "do one thing" principle. So, I personally would either: Have two separate functions for Status-Count and one for Total. OR chose one of the methods that abstracts the result, so that they aren't "2 things" anymore but one set of statistical data.

There are two ways to do this

One way include the total in all the items in the list as follows:

public IEnumerable<TurbineStatus> turStatus()
{
    int  totalCount = _db.masterData.Select(s=>s.turbine_name).Count();

    var result1 = (from s in _db.masterData
                  group s by s.current_turbine_status into g
                  select new TurbineStatus
                  {
                      status = g.Key,
                      numberOfTurbines = g.Count(),
                      total = totalCount,
                  }
                ).ToList().OrderByDescending(s => s.status);

    
    return result;
}

OR

public class ResponseDto {
     IEnumerable<TurbineStatus> GroupData {get;set;}
     int TotalCount {get;set;}
}

...

public ResponseDto turStatus()
{
    int  totalCount = _db.masterData.Select(s=>s.turbine_name).Count();

    var result1 = (from s in _db.masterData
                  group s by s.current_turbine_status into g
                  select new TurbineStatus
                  {
                      status = g.Key,
                      numberOfTurbines = g.Count(),
                  }
                ).ToList().OrderByDescending(s => s.status);

    
    return new ResponseDto(){
         GroupData = result1,
         TotalCount = totalCount 
    };
}

OR with 1 Hit to DB

public ResponseDto turStatus()
    {
        var result1 = (from s in _db.masterData
                      group s by s.current_turbine_status into g
                      select new TurbineStatus
                      {
                          status = g.Key,
                          numberOfTurbines = g.Count(),
                      }
                    ).ToList().OrderByDescending(s => s.status);
       
        return new ResponseDto(){
             GroupData = result1,
             TotalCount = result1.Sum(t=> t.numberOfTurbines)
        };
    }
Related