How can I shorten this linq to sql query? - c#

I am am making a calendar and to make it easier on myself I break up appointments that span over multiple weeks.
For instance Jan 1st to Jan 31st spans like 6 weeks(my calendar is always 42 cells - 6 by 7). So I would basically have 6 rows stored in my database.
However somethings I do require to me to put all these rows back together into one row. For instance if I want to export my calendar in Ical format.
I have a field in my database called bindingClassName all these rows get the same unquie id to that group of tasks so I am able to get all the weeks easily.
// get all of the task rows by binding class name.
var found = plannerDb.Calendars.Where(u => u.UserId == userId && u.BindingClassName == bindingClassName)
.GroupBy(u => u.BindingClassName);
List<Calendar> allAppoingments = new List<Calendar>();
// go through each of the results and add it to a list of calendars
foreach (var group in found)
{
foreach (var row in group)
{
Calendar appointment = new Calendar();
appointment.AppointmentId = row.AppointmentId;
appointment.AllDay = row.AllDay;
appointment.BindingClassName = row.BindingClassName;
appointment.Description = row.Description;
appointment.EndDate = row.EndDate;
appointment.StartDate = row.StartDate;
appointment.Title = row.Title;
appointment.Where = row.Where;
appointment.UserId = row.UserId;
allAppoingments.Add(appointment);
}
}
// order
var test = allAppoingments.OrderBy(u => u.StartDate);
var firstAppointment = test.First();
var LastAppointment = test.Last();
Calendar newAppointment = new Calendar();
newAppointment.UserId = firstAppointment.UserId;
newAppointment.Description = firstAppointment.Description;
newAppointment.AllDay = firstAppointment.AllDay;
newAppointment.StartDate = firstAppointment.StartDate;
newAppointment.Title = firstAppointment.Title;
newAppointment.Where = firstAppointment.Where;
newAppointment.BindingClassName = firstAppointment.BindingClassName;
newAppointment.EndDate = LastAppointment.EndDate;
return newAppointment;
So basically that big blob finds all the appointments with the same binding name. Then I go through each one and make it into a Calendar object then finally once it is all made I get the first and last record to get the startDate and endDate.
So I am not good with linq but I am not sure if I can just add something after the groupBy to do what I want.
Edit.
I am trying group all my appointments together once I get all of them from the user.
So I have this so far
I tried something like this.
var allApointments = calendar.GetAllAppointments(userId);
var group = allApointments.GroupBy(u => u.BindingClassName).Select(u => new Calendar()).ToList
I was hoping that it would fill each group automatically but it does not. So I am not sure if don't need groupby again.
Edit # admin
Hi thanks for explaining sorting and grouping. How you explained it though it seems either one would work.
Like the code you have for getting the first and last date works great and does what I wanted it to.
I think grouping might have worked because in the end though I am looking just to have one row that has the startdate of the first record and the end date of the last record all the other information would be the same.
So I don't know if it would harder to write that instead or what but like I said your query does what I want.
However that query is used on a single basis. Like I use that query only when a user clicks to view that appointment on my calendar. By clicking on the appointment I get all the information about that appointment and thats where I need to look at if that task spans over multiple days and figure out when the appointment started and when it is going to end.
Now I need another query and I think it would be better if I could actually group them as how I understand it from your explanation it will make one row. the reason I think this is because I want to export all the records in the table from that user.
So if I order them into one continues block by binding name I still going to need some loops that goes through all the records and gets the first and start date. So if I could just group it in one go and the final result would be just one record for each group of binding names and it would have the first start date and the last end date from the first and last record would be better.

Why are you grouping the appointments if you aren't actually using the group? It looks like you're just using them individually. In any case, you're already filtering the rows on a single value for BindingClassName in the Where clause, so you would only end up with 1 (or 0) group(s) anyway.
You can rewrite that series of foreach loops into a Select and ToList() like this:
var allAppointments =
plannerDb.Calendars.Where(
row => row.UserId == userId &&
row.BindingClassName == bindingClassName).OrderBy(
row => row.StartDate).Select(
row => new Calendar()
{
AppointmentId = row.AppointmentId,
AllDay = row.AllDay,
BindingClassName = row.BindingClassName,
Description = row.Description,
EndDate = row.EndDate,
StartDate = row.StartDate,
Title = row.Title,
Where = row.Where,
UserId = row.UserId
}).ToList();
This will give you back the full list in the order you wanted. However, I'm curious why you're retrieving the whole list when it looks like you're only interested in the first and last appointment. You could instead do this:
var baseQuery =
plannerDb.Calendars.Where(
row => row.UserId == userId &&
row.BindingClassName == bindingClassName);
var first = baseQuery.OrderBy(row => row.StartDate).First();
var last = baseQuery.OrderByDescending(row => row.StartDate).Select(
row => row.EndDate).First();
return new Calendar()
{
AppointmentId = first.AppointmentId,
AllDay = first.AllDay,
BindingClassName = first.BindingClassName,
Description = first.Description,
EndDate = last,
StartDate = first.StartDate,
Title = first.Title,
Where = first.Where,
UserId = first.UserId
});
This should produce outputs that are the same as what you have now. I would question, however, if this is exactly what you want. Say you have two appointments:
Appointment 1 starts January 5 and ends on January 10
Appointment 2 starts January 6 and ends on January 7
Using this (and your) logic, you would get the end date as January 7, since Appointment 2 has the larger start date, but Appointment 1 actually ends later. I would recommend changing the second query to this:
var last = baseQuery.OrderByDescending(row => row.EndDate).Select(
row => row.EndDate).First();
This will give you the largest end date, which I think is what you're actually after.
EDIT
I think you're making the (very common) mistake of confusing grouping with sorting. When you say you want to "group the appointments by the binding name", it sounds like you want a full, complete list of appointments, and you want those appointments arranged in such a way as all appointments with a particular binding name form a contiguous block. If that's the case, you want to order the list by the binding name, not group them. Grouping takes the whole list and produces one row per grouping clause and allows you to perform aggregation functions on the remaining columns. For example, let's say I group the appointments on the binding name. This means that my result set will contain one row per binding name, and I can then do things like find the maximum start or end date or something like that; more formally, you can specify aggregation operations, which are operations that take a set of data (i.e. a list of start dates) and return a single piece of data (i.e. the maximum start date).
Unless I'm misunderstanding, it sounds like you still want to retrieve all of the individual assignments, you just want them arranged by binding name. If this is the case, just OrderBy(row => row.BindingName) and it will do the trick. In addition, you may want to avoid using the word "group", as people will think you mean the sort of grouping that I described above.

Just as a side point not concerning the linq, have you looked at AutoMapper? I am currently using this for populating data objects from linq and I've found it really useful for getting rid of the large sections of code where you just map to dtos. It wouldn't make the query parts of your code any shorter but would reduce:
return new Calendar()
{
AppointmentId = first.AppointmentId,
AllDay = first.AllDay,
BindingClassName = first.BindingClassName,
Description = first.Description,
EndDate = last,
StartDate = first.StartDate,
Title = first.Title,
Where = first.Where,
UserId = first.UserId
});
to:
return Mapper.Map(first,new Calendar{EndDate = last});

Related

ASP.NET Core Linq query for lists

I have this query that was recently changed to allow searches using lists. However, the logic doesn't seem correct. My initial search logic was as follows:
data = data.where(u=>u.location.contains(FilterInput.RepositoryName)).ToList();
This worked for individual inputs and the logic made sense. In the data result, check if location field contains the Input variable
However in order to handle inputs that are lists, I had to change it to the bottom code which is in this Input list, check if the it contains the location field.
The database outputs data as follows:
Output = {arhde, brhje, ckio}
That means my list input is a small section of what the database contains.
FilterInput.RepositoryName = {a,b,c}
data = (from item in dbContext.Documents
join id in initialData
on item.Id equals id.DocumentId
select new DocumentsListViewModel
{
Id = item.Id,
Name = item.Name,
ApplicationName = item.ApplicationName,
ApplicationSecretKey = item.ApplicationSecretKey,
Link = item.Link,
Location = item.Location,
FileType = item.FileType,
CreatedOn = item.CreatedOn
}).ToList();
if (FilterInput.RepositoryName.Count>0)
{
data = data.Where(u => FilterInput.RepositoryName.Contains(u.Location)).ToList();
}
I don't know if its possible to change this logic to use the first one but accomodate lists as well?

C# Remove duplicate times within range and mark them

So ive come across a slight issue with Lists in C#
I have a List that has some very similar dates. Now what i need to do is remove duplicates but where a duplicate is if its within one minute of another time, so not exactly the same.
As well as this, if there were duplicates of a specific time the object should have a flag saying so in the final list.
Now i can see how this could be done with lots of for loop, but i was hoping theres a nice way with LINQ to make this simple. So far my attempts havent been going very well though.
As an example a list of the following dates:
21/12/12 12:13:00
21/12/12 12:13:20
13/12/12 10:13:00
21/10/15 07:13:00
should turn into the list of DateDuplicate objects:
{date = 21/12/12 12:13:00, isDuplcicate=true}
{date = 13/12/12 10:13:00, isDuplcicate=false}
{date = 21/10/15 07:13:00, isDuplcicate=false}
public class DateDuplicate{
public DateTime date;
public bool isDuplicate = false;
}
List<DateDuplicate> RemoveAndMark(List<DateTime> dates){
///something nice here hopefully
}
thanks for any help!
Try this
List<DateDuplicate> RemoveAndMark(List<DateTime> dates)
{
var dateValues = dates.GroupBy(y => new { AddHours = y.Date.Date.AddHours(y.Hour), y.Minute}).Select(x =>
new DateDuplicate {isDuplicate = x.Count() > 1, date = x.Key.AddHours.AddMinutes(x.Key.Minute)}
);
return dateValues;
}

Comparing lists and values that exist in one and not the other

I am developing an MVC 5 Web Application - I have a screen where a user can tick checkboxes on a grid and this will save the data to the Database. What I need to implement now is the removal of the data if the user navigated back to the screen and unchecked one of the items and then continued.
So the code in my controller so far looks as below:
IEnumerable<string> splitSelectedCars = model.SelectedCars
.Split(',')
.Select(sValue => sValue.Trim());
if (cars.Count > 0)
{
IEnumerable<string> savedCarsInDb = cars.Select(c => c.Id).ToList();
//var merged = splitSelectedCars.Union(savedCarsInDb ,)
//puesdo code - for each value in merged call service layer to remove
}
I am not sure if using a union is the best approach here to find all the values that are in the splitSelected cars list from the model that are not in the savedCarsInDb list and if so what the IEqualityComparer should look like?
So an example list the first time would be 1,2,3,4 passed in to the model - split out and then saved to DB. If the User navigates back and deslects id 4 then splitSelected will have 1,2,3 and savedCarsInDb will still have 1,2,3,4 - so I need to find '4' and then call remove
LINQ can help you here, specificall the Except method:
var selected = model.SelectedCars.Split(',').Select(sValue => sValue.Trim());
var saved = cars.Select(c => c.Id).ToList();
var removed = saved.Except(selected);
Depending upon whether you wish casing to be sensitive or not you can pass in the appropriate string comparer:
var removed = saved.Except(selected, StringComparer.OrdinalIgnoreCase);

Error from use of C# Linq SQL CONCAT

I have the following three tables, and need to bring in information from two dissimilar tables.
Table baTable has fields OrderNumber and Position.
Table accessTable has fields OrderNumber and ProcessSequence (among others)
Table historyTable has fields OrderNumber and Time (among others).
.
var progress = from ba in baTable
from ac in accessTable
where ac.OrderNumber == ba.OrderNumber
select new {
Position = ba.Position.ToString(),
Time = "",
Seq = ac.ProcessSequence.ToString()
};
progress = progress.Concat(from ba in baTable
from hs in historyTable
where hs.OrderNumber == ba.OrderNumber
select new {
Position = ba.Position.ToString(),
Time = String.Format("{0:hh:mm:ss}", hs.Time),
Seq = ""
});
int searchRecs = progress.Count();
The query compiles successfully, but when the SQL executes during the call to Count(), I get an error
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
Clearly the two lists each have three items, one of which is a constant. Other help boards suggested that the Visual Studio 2010 C# compiler was optimizing out the constants, and I have experimented with alternatives to the constants.
The most surprising thing is that, if the Time= entry within the select new {...} is commented out in both of the sub-queries, no error occurs when the SQL executes.
I actually think the problem is that Sql won't recognize your String.Format(..) method.
Change your second query to:
progress = progress.Concat(from ba in baTable
from hs in historyTable
where hs.OrderNumber == ba.OrderNumber
select new {
Position = ba.Position.ToString(),
Time = hs.Time.ToString(),
Seq = ""
});
After that you could always loop trough the progress and format the Time to your needs.

Querying Distinct Events from Google Calendar

Right now I have a very simple query that pulls up entries that have a string and a specific date range.
EventQuery eQuery = new EventQuery(calendarInfo.Uri.ToString());
eQuery.Query = "Tennis";
eQuery.StartDate = startDate;
eQuery.EndDate = endDate;
EventFeed myResultsFeed = _service.Query(eQuery);
After querying, myResultsFeed will contain an atomEntryCollection. Each atomEntry has a Title. The way I have it set up, there could be multiple entries with the same title.
I would like my Query to be able to select UNIQUE titles. Is this possible?
Link to the API Docs
I hypothesized that I could use a WHERE object
Where x = new Where();
x.yadayada();
but it can't be passed to _service.Query()
I'm also exploring the .extraparameters object. is it possible to do something like this?
eQuery.ExtraParameters = "distinct";
Looking into the "Partial Response" feature..
http://code.google.com/apis/gdata/docs/2.0/reference.html#PartialResponse
it looks pretty promising..
I don't think what you're trying to do is possible using the Google Data API.
However, extenting upon #Fueled answer, you could do something like this if you need a collection of AtomEntry's.
// Custom comparer for the AtomEntry class
class AtomEntryComparer : IEqualityComparer<AtomEntry>
{
// EventEntry are equal if their titles are equal.
public bool Equals(AtomEntry x, AtomEntry y)
{
// adjust as needed
return x.Title.Text.Equals(y.Title.Text);
}
public int GetHashCode(AtomEntry entry)
{
// adjust as needed
return entry.Title.Text.GetHashCode();
}
}
EventFeed eventFeed = service.Query(query)
var entries = eventFeed.Entries.Distinct(new AtomEntryComparer());
It's probably not the solution you were looking for, but since you have in hand an AtomEntryCollection (which down the line implements IEnumerable<T>), you could use LINQ to retrieve the distinct titles, like so:
EventFeed feed = service.Query(query);
var uniqueEntries =
(from e in feed.Entries
select e.Title.Text).Distinct();
And then loop over them with a simple foreach:
foreach (var item in uniqueEntries)
{
Console.WriteLine(item);
}
But then you have only a collection of string representing the Event titles, and not a collection of AtomEntry. I guess you could link them together in a Dictionary.
Not optimal, but should work.

Categories

Resources