Update table to not IN condition - linq

I have a strongly typed list called "attendeeList" that contains all active attendees:
List<tb_Attendees> attendeeList
public partial class tb_Attendees
{
public int AttendeeId { get; set; }
public int MeetingId { get; set; }
public string AttendeeName { get; set; }
public bool IsActive { get; set; }
.....
I need update the following table:
DataContext.tb_Attendees
such that that for a given MeetingId, for all AttendeeIds are not in the list, it will update those records to IsActive = false. Basically I am doing a NOT IN.
I want to do something to the effect:
DataContext.tb_Attendees.Where(p => p.MeetingId == meetingId && !attendeeList.Contains(p => p.AttendId) ).ToList().ForEach(x => x.IsActive = false);
DataContext.SaveChanges();
First of all, is this correct? I get an error saying cannot convert from int to Model.tb_Attendees. Do I need to have the list as in int list or any way to accomplish this.

You're close, but it sounds like you need a list of IDs, not a list of attendees:
var attendeeIDList = attendeeList.Select(a => a.AttendId);
DataContext.tb_Attendees
.Where(p => p.MeetingId == meetingId
&& !attendeeIDList.Contains(p => p.AttendId) )
.ToList()
.ForEach(x => x.IsActive = false);
I would also note that the ForEach is just syntactic sugar and would be functionally equivalent to:
foreach(var x in
DataContext.tb_Attendees
.Where(p => p.MeetingId == meetingId
&& !attendeeIDList.Contains(p => p.AttendId) ))
{
x.IsActive = false;
}

Related

SQL query with Group By and Having Clause in LINQ structure

how do I write this query in LINQ (c# EF6)?
Can someone please help me here - I am new to Entity Framework Structure - So bit hard for me to use different clauses
SELECT
sum(remainingamount) TotalSiteCreditAmount,
max(expirationutcdatetime) MaxExpiryDate
FROM
WholesaleCredits
WHERE
ExpirationUTCDateTime > Getdate()
GROUP BY
registeredcustomerid,
siteid
HAVING
registeredcustomerid = :registeredCustomerId
AND siteid = :siteId
Tried below thing as of now :
var data = context.WholesaleCredit
.Where(x => x.ExpirationUTCDateTime > DateTime.Now)
.GroupBy (x => x.RegisteredCustomerId)
Entity Used in code:
public partial class WholesaleCredits
{
public virtual int Id { get; set; }
public virtual decimal CreditAmount { get; set; }
public virtual decimal RemainingAmount { get; set; }
public virtual Site Site { get; set; }
public virtual DateTime GeneratedUTCDateTime { get; set; }
public virtual DateTime ExpirationUTCDateTime { get; set; }
public virtual int RegisteredCustomerId { get; set; }
}
You do not need HAVING here and Grouping should be provided by constant, because you have filter on grouping keys:
var data = context.WholesaleCredit
.Where(x => x.ExpirationUTCDateTime > DateTime.Now && x.RegisteredCustomerId == registeredCustomerId && x.Site.Id == siteid)
.GroupBy(x => 1)
.Select(g => new
{
TotalSiteCreditAmount = g.Sum(x => x.RemainingAmount),
MaxExpiryDate = g.Max(x => x.ExpirationUTCDateTime)
})
.First();

How and where to use AddRange() method

I want to display related data from second table with each value in first table
i have tried this query
public ActionResult Index()
{
List<EmployeeAtt> empWithDate = new List<EmployeeAtt>();
var employeelist = _context.TblEmployee.ToList();
foreach (var employee in employeelist)
{
var employeeAtt = _context.AttendanceTable
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
Date = g.Key,
Emp_name = employee.EmployeeName,
InTime = g.Any(e => e.ScanType == "I") ? g.Where(e =>
e.ScanType == "I").Min(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent",
OutTime = g.Any(e => e.ScanType == "O") ? g.Where(e =>
e.ScanType == "O").Max(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent"
});
empWithDate.AddRange(employeeAtt);
}
return View(empWithDate);
}
Here is my attendance Table
AttendanceTable
Results
I want to display the shortest time with "I" Column value against each employee and last time with "O" Column value as out time. I think i am not using AddRange() at proper place. Where it should go then?
public partial class TblEmployee
{
public TblEmployee()
{
AttendanceTable = new HashSet<AttendanceTable>();
}
public int EmpId { get; set; }
public string EmployeeName { get; set; }
public virtual ICollection<AttendanceTable> AttendanceTable { get; set; }
}
public partial class AttendanceTable
{
public int Id { get; set; }
public int AttendanceId { get; set; }
public int EmployeeId { get; set; }
public string ScanType { get; set; }
public DateTime DateAndTime { get; set; }
public virtual TblEmployee Employee { get; set; }
}
The actual problem is not related to AddRange(), you need a where clause before GroupBy() to limit attendances (before grouping) to only records related to that specific employee, e.g.
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
...
Depended on your model, it is better to use some kind of ID instead of EmployeeName for comparison if possible.
Also you can use SelectMany() instead of for loop and AddRange() to combine the results into a single list. like this:
List<EmployeeAtt> empWithDate = _context.TblEmployee.ToList()
.SelectMany(employee =>
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
...
})
);
...

Adding where and sum to lambda expression

I have the below Linq query:
var qry = from Output in db.Outputs
join ShiftHours in db.ShiftHourses on Output.ShiftHour equals ShiftHours.ShiftHour
join ShiftData in db.ShiftDatas on Output.ShiftID equals ShiftData.ShiftID
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new ProgressData()
{
CPM = ShiftData.CPM,
Target = ShiftData.Target,
CurrentOutput = db.Outputs.Sum(x=>x.Quantity),
PercentOfTarget = (db.Outputs.Sum(x=>x.Quantity) / ShiftData.Target) * 100
};
It is almost doing what I want but as it stands, the CurrentOutput lambda expression is returning the sum of the entire Quantity column of the Output table as I am unsure how to add in a 'Where' clause as well as the sum function (and hence the PercentOfTarget is also incorrect).
The where clause needs to be the same as the first where clause (date and line are parameters passed to the method):
where ShiftData.ShiftDate == date && ShiftData.Line == line
Can anyone help?
EDIT: Clarification of CurrentOutput.
In the 'Output' table there can be multiple records for a given 'ShiftData.ShiftDate' and 'ShiftData.Line' combination so I would like to calculate a sum of the 'Output' table 'Quantity' column values for a specified 'ShiftDate' and 'Line'
EDIT: Further clarification
This is some sample data from the Output table (OutputID is an auto-increment PK):
public class Output
{
[Key]
public int OutputId { get; set; }
public int ShiftID { get; set; }
public int Quantity { get; set; }
public int ShiftHour { get; set; }
public virtual ShiftData ShiftData { get; set; }
}
This is some sample data from the ShiftData table (ShiftID is an auto-increment PK, there will be more than one record for each date as further line numbers are added):
public class ShiftData
{
[Key]
public int ShiftID { get; set; }
public DateTime ShiftDate { get; set; }
public string Line { get; set; }
public int CPM { get; set; }
public double Target { get; set; }
}
So using the above sample data, I am trying to populate a ProgressData object:
public class ProgressData
{
public int CPM { get; set; }
public double Target { get; set; }
public int CurrentOutput { get; set; }
public double PercentOfTarget { get; set; }
}
Based on the sample data, I would expect my ProgressData object created for line 1 on 13/2/2014 to be populated as such:
CPM = 5, Target = 200, CurrentOutput = 120, PercentOfTarget = 60
You can try to do group join for that purpose :
var qry = from ShiftData in db.ShiftDatas
join Output in db.Outputs on ShiftData.ShiftID equals Output.ShiftID
into ShiftGroup
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new ProgressData()
{
CPM = ShiftData.CPM,
Target = ShiftData.Target,
CurrentOutput = ShiftGroup.Sum(x=>x.Quantity),
PercentOfTarget = (ShiftGroup.Sum(x=>x.Quantity) / ShiftData.Target) * 100
};
Another thing, I can't see why you need to do join with ShiftHours here, since none of it's property used in select statement.
Just as #har07 posted I managed to get it working using the below. I am posting this for reference as it does answer the original question but I'm going to try and use #har07's code as it's tidier than mine.
var qry = (from Output in db.Outputs
join ShiftHours in db.ShiftHourses on Output.ShiftHour equals ShiftHours.ShiftHour
join ShiftData in db.ShiftDatas on Output.ShiftID equals ShiftData.ShiftID
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new
{
ShiftData.ShiftDate,
ShiftData.Line,
ShiftData.CPM,
ShiftData.Target,
Output.Quantity
}).ToList();
var progress = qry.GroupBy(l => l.ShiftDate).Select(g => new ProgressData()
{
CPM = g.Where(c => c.ShiftDate == date && c.Line == line).Select(c => c.CPM).FirstOrDefault(),
Target = g.Where(c => c.ShiftDate == date && c.Line == line).Select(c => c.Target).FirstOrDefault(),
CurrentOutput = g.Where(c => c.ShiftDate == date && c.Line == line).Sum(c => c.Quantity),
PercentOfTarget = g.Where(c => c.ShiftDate == date && c.Line == line).Sum(c => (c.Quantity / c.Target) * 100)
});
return progress.FirstOrDefault();

how to practically assign repeating objects from groups

I am having a difficult time finding a proper Linq query to utilize the group output.
I want to populate an existing students List where Student class has 2 properties ID and and int[] Repeats array (can be a list too) to keep how many times they took any of the 4 lectures (L101,L201,L202,L203). So if student takes L101 twice, L202 and L203 once, and but didn't take L201 this should be {2,0,1,1,}
class Student{
public string ID{get;set;}
public int[] Repeats{get;set;} //int[0]->L101, int[1]->L201...
}
In my main class I do this basic operation for this task:
foreach (var student in students)
{
var countL101 = from s in rawData
where student.Id==s.Id & s.Lecture =="L101"
select; //do for each lecture
student.Repeats = new int[4];
student.Repeats[0] = countL101.Count(); //do for each lecture
}
This works; but I wonder how do you make it practically using Linq in case where there are 100s of lectures?
I am using Lamba Expressions rather than query syntax. Then assuming rawData is IEnumerable<T> where T looks something like...
class DataRow
{
/// <summary>
/// Id of Student taking lecture
/// </summary>
public string Id { get; set; }
public string Lecture { get; set;}
}
Then you could do something like...
var lectures = rawData.Select(x => x.Lecture).Distinct().ToList();
int i = 0;
lectures.ForEach(l =>
{
students.ForEach(s =>
{
if (s.Repeats == null)
s.Repeats = new int[lectures.Count];
s.Repeats[i] = rawData.Count(x => x.Id == s.Id && x.Lecture == l);
});
i++;
});
Now if Repeats could just be of type IList<int> instead of int[] then...
var lectures = rawData.Select(x => x.Lecture).Distinct().ToList();
lectures.ForEach(l =>
{
students.ForEach(s =>
{
if (s.Repeats == null)
s.Repeats = new List<int>();
s.Repeats.Add(rawData.Count(x => x.Id == s.Id && x.Lecture == l));
});
});
Things are further simplified if Repeats could just be instantiated to a new List<int> in the Student constructor...
class Student
{
public Student()
{
Repeats = new List<int>();
}
public string Id { get; set; }
public IList<int> Repeats { get; private set; }
}
Then you can do it in one line...
rawData.Select(x => x.Lecture).Distinct().ToList()
.ForEach(l =>
{
students.ForEach(s =>
{
s.Repeats.Add(rawData.Count(x => x.Id == s.Id && x.Lecture == l));
});
});

Entity Framework, MVC 3, OrderBy in LINQ To Entities

I've got the following query:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series
.Select(c => c.Comics
.Select(col => col.Collection)))
.SingleOrDefault();
This works great, although I now need to order the Comics by a property called 'ReadingOrder'.
I've tried:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series.Select(c => c.Comics.OrderBy(o => o.ReadingOrder)
.Select(col => col.Collection)))
.SingleOrDefault();
But this results in the following error:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties. Parameter name: path
Any ideas what this error means?
Thanks in advance
EDIT:
My models:
public class Page
{
public int PageId { get; set; }
public string Title { get; set; }
public ICollection<Series> Series { get; set; }
}
public class Series
{
public int SeriesId { get; set; }
public int PageId { get; set; }
public string Title { get; set; }
public Page Page { get; set; }
public ICollection<Comic> Comics { get; set; }
}
public class Comic
{
public int ComicId { get; set; }
public string Title { get; set; }
public int ReadingOrder { get; set; }
public string Subtitle { get; set; }
public int CollectionId { get; set; }
public Collection Collection { get; set; }
}
public class Collection
{
public int CollectionId { get; set; }
public string Title { get; set; }
public ICollection<Comic> Comics { get; set; }
}
The exception "...Include path expression must refer to a navigation property..." basically complains that c.Comics.OrderBy is not a navigation property. (It's a legitimate complaint, I think.)
Actually it's not supported by EF to apply sorting (and also filtering) in eager loading statements (Include).
So, what can you do?
Option 1:
Sort in memory after you have loaded the entity:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series.Select(c => c.Comics
.Select(col => col.Collection)))
.SingleOrDefault();
if (model.Page != null)
{
foreach (var series in model.Page.Series)
series.Comics = series.Comics.OrderBy(c => c.ReadingOrder).ToList();
}
Ugly, but because you are loading apparently only a single Page object by id it's possibly faster (LINQ to Objects in memory) than the following options (if Series and Comics collections are not extraordinarily long).
Option 2:
Break down the query in parts which mix eager and explicite loading:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series) // only Series collection is included
.SingleOrDefault();
if (model.Page != null)
{
foreach (var series in model.Page.Series)
db.Entry(series).Collection(s => s.Comics).Query()
.Include(c => c.Collection)
.OrderBy(c => c.ReadingOrder)
.Load(); // one new DB query for each series in loop
}
Option 3:
Projection?
Here and here is by the way something about the dangers of complex Include chains of multiple navigation properties. It can load huge amounts of duplicated data. Include ensures that you only have one DB roundtrip but possibly at the cost of much more transfered data. Explicite loading has multiple roundtrips but with possibly less data in total.
(I know, I gave you this Include...Select...Select...Select... chain, but how could I know that you would take me serious :). Well, depending on the size of your nested collections it can still be the best option.)
Off the top of my head, untested:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series
.Select(c => c.Comics
.Select(col => col.Collection)
.OrderBy(o => o.ReadingOrder)))
.SingleOrDefault();

Resources