1

I want to make my code short and simple using linq.

I have a list that contains leaveDates and every leaveDates contain number of leavelist.

Something like this:

{ leaves_date = {07-05-2018 18:30:00}, LeaveList = {System.Collections.Generic.List<TimeClock.Model.LeaveManagementModel>} }
{ leaves_date = {08-05-2018 18:30:00}, LeaveList = {System.Collections.Generic.List<TimeClock.Model.LeaveManagementModel>} }
{ leaves_date = {21-05-2018 18:30:00}, LeaveList = {System.Collections.Generic.List<TimeClock.Model.LeaveManagementModel>} }

leaveList contains UserId, LeaveType, Status fields

Now all I want is to count the number of leavedates per user who's status is 1 and leave type != 3

I have already tried using a for loop, but I want to do it with linq.

Here is my code with the for loop:

for (var i = 0; i < leavesresult.Count; i++) {
    for (var a = 0; a < leavesresult[i].LeaveList.Count; a++) {
        if (leavesresult[i].LeaveList[a].status == 1.ToString() && leavesresult[i].LeaveList[a].leave_type != 3.ToString()) {
            var compair1 = leavesresult[i].LeaveList[a].user_id;
            var compair2 = attendancelist.Any(z = >z.user_id == leavesresult[i].LeaveList[a].user_id);

            if (attendancelist.Any(z = >z.user_id == leavesresult[i].LeaveList[a].user_id)) {
                int index = attendancelist.FindIndex(y = >y.user_id == leavesresult[i].LeaveList[a].user_id);

                if (leavesresult[i].LeaveList[a].check_halfday == 1) {
                    attendancelist[index].days = attendancelist[index].days
                }
                else {
                    attendancelist[index].days = attendancelist[index].days + 1;
                }
            }
            else {
                if (leavesresult[i].LeaveList[a].check_halfday == 1) {
                    attendancelist.Add(new AttendanceModel {
                        user_id = leavesresult[i].LeaveList[a].user_id,
                        days = 0.5
                    });
                }
                else {
                    attendancelist.Add(new AttendanceModel {
                        user_id = leavesresult[i].LeaveList[a].user_id,
                        days = 1
                    });
                }
            }
        }
    }
}
5
  • Have you tried any LINQ queries? Commented May 21, 2018 at 13:45
  • tried but not succeed Commented May 21, 2018 at 13:46
  • @ChitraNandpal Please show us what you tried. In the question, not in a comment. Commented May 21, 2018 at 13:48
  • Step 1, if you don't mind and IMO is to break down the nest into separate logical calls. This way each call has it's own purpose and makes the code more readable. Doing this alone often gives you insight on how it can be simplified right away. It also makes it easier to convert into linq or edit later without breaking steps of the iteration. My personal goal, when writing any nested code, is to make sure the boiler plate logic flows into as only 1 loop, if else, etc in a single method. Make sure to name each method accordingly and use local methods when it makes sense. Commented May 21, 2018 at 13:48
  • 1
    Upvoting this question, sole reason is most caring answer written by Eric Lippert. Commented May 22, 2018 at 5:46

3 Answers 3

18

I could give you the query and you would learn nothing. Instead learn how to do this transformation yourself. The trick is to not try to do it all at once. Rather, we make a series of small, obviously correct transformations each one of which gets us closer to our goal.

Start by rewriting the inner for loop as a foreach:

for (var i = 0; i < leavesresult.Count; i++) 
{
  foreach (var leavelist in leavesresult[i].LeaveList) 
  {
    if (leavelist.status == 1.ToString() && leavelist.leave_type != 3.ToString()) 
    {
      var compair1 = leavelist.user_id;
      var compair2 = attendancelist.Any(z => z.user_id == leavelist.user_id);
      if (attendancelist.Any(z => z.user_id == leavelist.user_id)) 
      {
        int index = attendancelist.FindIndex(y => y.user_id == leavelist.user_id);  
        if (leavelist.check_halfday == 1) 
          attendancelist[index].days = attendancelist[index].days
        else 
          attendancelist[index].days = attendancelist[index].days + 1;
      }
      else 
      {
        if (leavelist.check_halfday == 1) 
          attendancelist.Add(
            new AttendanceModel {user_id = leavelist.user_id, days = 0.5});
        else 
          attendancelist.Add(
            new AttendanceModel {user_id = leavelist.user_id, days = 1});
      }
    }
  }
}

Already your code is about 100 times easier to read with that change.

Now we notice a few things:

    if (leavelist.status == 1.ToString() && leavelist.leave_type != 3.ToString()) 

That is a crazy way to write this check. Rewrite it into a sensible check.

      var compair1 = leavelist.user_id;
      var compair2 = attendancelist.Any(z => z.user_id == leavelist.user_id);

Neither of these variables are ever read, and their initializers are useless. Delete the second one. Rename the first one to user_id.

        if (leavelist.check_halfday == 1) 
          attendancelist[index].days = attendancelist[index].days
        else 
          attendancelist[index].days = attendancelist[index].days + 1;

The consequence makes no sense. Rewrite this.

OK, we now have

for (var i = 0; i < leavesresult.Count; i++) 
{
  foreach (var leavelist in leavesresult[i].LeaveList) 
  {
    if (leavelist.status == "1" && leavelist.leave_type != "3") 
    {
      var user_id= leavelist.user_id;
      if (attendancelist.Any(z => z.user_id == leavelist.user_id)) 
      {
        int index = attendancelist.FindIndex(y => y.user_id == leavelist.user_id);  
        if (leavelist.check_halfday != 1) 
          attendancelist[index].days = attendancelist[index].days + 1;
      }
      else 
      {
        if (leavelist.check_halfday == 1) 
          attendancelist.Add(
            new AttendanceModel {user_id = leavelist.user_id, days = 0.5});
        else 
          attendancelist.Add(
            new AttendanceModel {user_id = leavelist.user_id, days = 1});
      }
    }
  }
}

Use the helper variable throughout:

for (var i = 0; i < leavesresult.Count; i++) 
{
  foreach (var leavelist in leavesresult[i].LeaveList) 
  {
    if (leavelist.status == "1" && leavelist.leave_type != "3") 
    {
      var user_id = leavelist.user_id;
      if (attendancelist.Any(z => z.user_id == user_id)) 
      {
        int index = attendancelist.FindIndex(y => y.user_id == user_id);  
        if (leavelist.check_halfday != 1) 
          attendancelist[index].days = attendancelist[index].days + 1;
      }
      else 
      {
        if (leavelist.check_halfday == 1) 
          attendancelist.Add(
            new AttendanceModel {user_id = user_id, days = 0.5});
        else 
          attendancelist.Add(
            new AttendanceModel {user_id = user_id, days = 1});
      }
    }
  }
}

We realize that the Any and the FindIndex are doing the same thing. Eliminate one of them:

for (var i = 0; i < leavesresult.Count; i++) 
{
  foreach (var leavelist in leavesresult[i].LeaveList) 
  {
    if (leavelist.status == "1" && leavelist.leave_type != "3") 
    {
      var user_id = leavelist.user_id;
      int index = attendancelist.FindIndex(y => y.user_id == user_id);
      if (index != -1) 
      {
        if (leavelist.check_halfday != 1) 
          attendancelist[index].days = attendancelist[index].days + 1;
      }
      else 
      {
        if (leavelist.check_halfday == 1) 
          attendancelist.Add(
            new AttendanceModel {user_id = user_id, days = 0.5});
        else 
          attendancelist.Add(
            new AttendanceModel {user_id = user_id, days = 1});
      }
    }
  }
}

We notice that we are duplicating code in the final if-else. The only difference is days:

for (var i = 0; i < leavesresult.Count; i++) 
{
  foreach (var leavelist in leavesresult[i].LeaveList) 
  {
    if (leavelist.status == "1" && leavelist.leave_type != "3") 
    {
      var user_id = leavelist.user_id;
      int index = attendancelist.FindIndex(y => y.user_id == user_id);
      if (index != -1) 
      {
        if (leavelist.check_halfday != 1) 
          attendancelist[index].days = attendancelist[index].days + 1;
      }
      else 
      {
        double days = leavelist.check_halfday == 1 ? 0.5 : 1;
        attendancelist.Add(new AttendanceModel {user_id = user_id, days = days});
      }
    }
  }
}

Now your code is 1000x easier to read than it was before. Keep going! Rewrite the outer loop as a foreach:

foreach (var lr in leavesresult) 
{
  foreach (var leavelist in lr.LeaveList) 
  {
    if (leavelist.status == "1" && leavelist.leave_type != "3") 
    {
      var user_id = leavelist.user_id;
      int index = attendancelist.FindIndex(y => y.user_id == user_id);
      if (index != -1) 
      {
        if (leavelist.check_halfday != 1) 
          attendancelist[index].days = attendancelist[index].days + 1;
      }
      else 
      {
        double days = leavelist.check_halfday == 1 ? 0.5 : 1;
        attendancelist.Add(new AttendanceModel {user_id = user_id, days = days});
      }
    }
  }
}

And we notice a couple more things: we can put check_halfday into an explanatory variable, and eliminate days. And we can simplify the increment:

foreach (var lr in leavesresult) 
{
  foreach (var leavelist in lr.LeaveList) 
  {
    if (leavelist.status == "1" && leavelist.leave_type != "3") 
    {
      var user_id = leavelist.user_id;
      int index = attendancelist.FindIndex(y => y.user_id == user_id);
      bool halfday= leavelist.check_halfday == 1;
      if (index != -1) 
      {
        if (!halfday) 
          attendancelist[index].days += 1;
      }
      else 
      {
        attendancelist.Add(new AttendanceModel {user_id = user_id, days = halfday ? 0.5 : 1});
      }
    }
  }
}

Now we begin transforming this to a query. The key thing to understand is that mutations must not go in queries. Mutations only go into loops, never queries. Queries ask questions, they do not perform mutations.

You have a mutation of attendancelist, so that's got to stay in a loop. But we can move all the query logic out of the loop by recognizing that the nested foreach with a test inside the inner loop is equivalent to:

var query = from lr in leaveresult
            from ll in lr.LeaveList
            where ll.status == "1"
            where ll.leave_type != "3"
            select ll;

Excellent. Now we can use that in our foreach:

foreach(var ll in query) 
{
  var index = attendancelist.FindIndex(y => y.user_id == ll.user_id);
  var halfday = ll.check_halfday == 1;
  if (index != -1) 
  {
    if (!halfday) 
      attendancelist[index].days += 1;
  }
  else 
  {
    attendancelist.Add(
      new AttendanceModel {user_id = ll.user_id, days = halfday? 0.5 : 1 });
  }
}

Now that we have the loop in this extremely simple form, we notice that we can re-order the if to simplify it:

foreach(var ll in query) 
{
  var index = attendancelist.FindIndex(y => y.user_id == ll.user_id);
  var halfday = ll.check_halfday == 1;
  if (index == -1) 
    attendancelist.Add(
      new AttendanceModel {user_id = ll.user_id, days = halfday? 0.5 : 1 });
  else if (!halfday) 
    attendancelist[index].days += 1;
}

And we're done. All the computation is done by the query, all the mutations are done by the foreach, as it should be. And your loop body is now a single, extremely clear conditional statement.


This answer is to answer your question, which was how to convert an existing bunch of hard-to-read loops into an easy-to-read query. But it would be better still to write a query that clearly expressed the business logic you're trying to implement, and I don't know what that is. Create your LINQ queries so that they make it easy to understand what is happening at the business level.

In this case what I suspect you are doing is maintaining a per-user count of days, to be updated based on the leave lists. So let's write that!

// dict[user_id] is the accumulated leave.
var dict = new Dictionary<int, double>();
var query = from lr in leaveresult
            from ll in lr.LeaveList
            where ll.status == "1"
            where ll.leave_type != "3"
            select ll;
foreach(var ll in query) 
{
  var halfday = ll.check_halfday == 1;
  if (!dict.ContainsKey(ll.user_id)) 
    dict[ll.user_id] = halfday? 0.5 : 1;
  else if (!halfday) 
    dict[ll.user_id] = dict[ll.user_id] + 1;
}

That seems like a nicer way to represent this than a list that you are constantly having to search.

Once we are at this point we can then recognize that what you are really doing is computing a per-user sum! The answer by JamieC shows that you can use the Aggregate helper method to compute a per-user sum.

But again, this is based on the assumption that you have built this whole mechanism to compute that sum. Again: design your code so that it clearly implements the business process in the jargon of that process. If what you're doing is computing that sum, boy, does that ever not show up in your original code. Strive to make it clearer what your code is doing.

Sign up to request clarification or add additional context in comments.

Comments

1

This is basically 1 line of linq with a groupby, I'm not sure ill get it spot on with 1 try, but something along the lines of:

var attendancelist = leavesresult
               .SelectMany(a => a.LeaveList) // flatten the list
               .Where(a => a.status == "1" && a.type != "3") // pick the right items 
               .GroupBy(a => a.user_id) // group by users
               .Select(g => new AttendanceModel(){ // project the model
                    user_id = g.Key,
                    days = g.Aggregate(0, (a,b) => a + (b.check_halfday == 1 ? 0.5 : 1))
               })
               .ToList();

Let me know any issues, and i'll try to fix as necessary.


edit1: Assuming AttendanceModel.days is an int you need to decide what to do as it is calculating a float.

Perhaps something like:

...
days = (int)Math.Ceiling(g.Aggregate(0, (a,b) => a + (b.check_halfday == 1 ? 0.5 : 1)))
...

7 Comments

i dont have any field like days i want to count days on leave_date vise
@ChitraNandpal my bad, I misread that bit. See update - is that closer to what you're trying to do?
here check_halfday is int so if value == 1 then 0.5 or 1
one last problem is there it is giveing error like can not convert double into int:)
I suspect AttendanceModel.days is an int. So what do you want to happen when that has a value 1.5 or something (as can happen). Do you want to round up, round down? This is done with either Math.Ceil or Math.Floor and a cast to int around the Aggregate line
|
-1

Not a linq version but used foreach to simplify and make it more readable

    var userLeaves = new Dictionary<int, double>();
    foreach( var lr in leavesresult)
    {
        foreach (var leave in lr.LeaveList) 
        {
            if (leave.Status == "1" && leave.LeaveType != "3") 
            {
                var leaveDay = leave.check_halfday ==1 ? 0.5 : 1;
                if (userLeaves.ContainsKey(leave.UserID))
                    userLeaves[leave.UserID] = userLeaves[leave.UserID] + leaveDay;
                else
                    userLeaves.Add(leave.UserID, leaveDay);
            }
        }
    }

1 Comment

It is always helpful to add comments mentioning what you think is wrong while down voting. I have shown logic how to do, and not included each line of the code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.