i have a table in which i have record of users.table have field lastLoginMonth and LastLoginYear ...i want to fetch that user who have login time more than 5 month ..but here i found two case ..
1)current year and lastLoginYear same
2)current year and lastLoginYear different
to handle this i have to use different conditions but i don't know how to handle this in query.....
var year = db.UserManagers.ToList();
foreach (var y in year)
{
if (y.LastLoginYear == mydate.Year)
{
var modell = (from ummm in db.UserManagers
where ((mydate.Month - ummm.LastLoginMonth) >5
&& ummm.LoginWarning==false)
select ummm).ToList();
return View(modell);
}
var model = (from ummm in db.UserManagers
where (((12 - y.LastLoginMonth) + mydate.Month) >5
&& ummm.LoginWarning == false)
select ummm).ToList();
return View(model);
}
how i can organize this query in a simple way ...
Use ternary operator:
var modell = (from ummm in db.UserManagers
where (((y.LastLoginYear == mydate.Year)
? ((mydate.Month - ummm.LastLoginMonth) >5)
: ((12 - y.LastLoginMonth) + mydate.Month) >5)
&& ummm.LoginWarning==false)
select ummm).ToList();
Take a look at this example to understand what does this mean like:
var list = new List<string> { "1", "abc", "5"};
var sel = (from s in list where ((s.Length > 1) ? true : false) select s);
As you can see, we take each string s stored in the list and apply to it the next filter: If it’s Length more then 1, we take it (as it will be where true), otherwise, we don’t take it. Thus we will take only those strings that have Length more then 1.
Also pay attention that you make return inside the foreach loop. That means that the foreach will iterate only 1 time and then will exit on the return you wrote. So you might expect this code to make something different from what you have written.
First approach that came to my mind could be with simple inline if:
var year = db.UserManagers.ToList();
foreach (var y in year)
{
var model = (from ummm in db.UserManagers
where (((y.LastLoginYear == mydate.Year)?(mydate.Month - ummm.LastLoginMonth):((12 - y.LastLoginMonth) + mydate.Month)) >5 && ummm.LoginWarning==false)
select ummm).ToList();
return View(model);
}
}
I assume that you realize that a return inside a foreach loop would make it execute only once and then return the result?
Related
I am writing a small program that takes in a .csv file as input with about 45k rows. I am trying to compare the contents of this file with the contents of a table on a database (SQL Server through dynamics CRM using Xrm.Sdk if it makes a difference).
In my current program (which takes about 25 minutes to compare - the file and database are the exact same here both 45k rows with no differences), I have all existing records on the database in a DataCollection<Entity> which inherits Collection<T> and IEnumerable<T>
In my code below I am filtering using the Where method and then doing a logic based the count of matches. The Where seems to be the bottleneck here. Is there a more efficient approach than this? I am by no means a LINQ expert.
foreach (var record in inputDataLines)
{
var fields = record.Split(',');
var fund = fields[0];
var bps = Convert.ToDecimal(fields[1]);
var withdrawalPct = Convert.ToDecimal(fields[2]);
var percentile = Convert.ToInt32(fields[3]);
var age = Convert.ToInt32(fields[4]);
var bombOutTerm = Convert.ToDecimal(fields[5]);
var matchingRows = existingRecords.Entities.Where(r => r["field_1"].ToString() == fund
&& Convert.ToDecimal(r["field_2"]) == bps
&& Convert.ToDecimal(r["field_3"]) == withdrawalPct
&& Convert.ToDecimal(r["field_4"]) == percentile
&& Convert.ToDecimal(r["field_5"]) == age);
entitiesFound.AddRange(matchingRows);
if (matchingRows.Count() == 0)
{
rowsToAdd.Add(record);
}
else if (matchingRows.Count() == 1)
{
if (Convert.ToDecimal(matchingRows.First()["field_6"]) != bombOutTerm)
{
rowsToUpdate.Add(record);
entitiesToUpdate.Add(matchingRows.First());
}
}
else
{
entitiesToDelete.AddRange(matchingRows);
rowsToAdd.Add(record);
}
}
EDIT: I can confirm that all existingRecords are in memory before this code is executed. There is no IO or DB access in the above loop.
Himbrombeere is right, you should execute the query first and put the result into a collection before you use Any, Count, AddRange or whatever method will execute the query again. In your code it's possible that the query is executed 5 times in every loop iteration.
Watch out for the term deferred execution in the documentation. If a method is implemented in that way, then it means that this method can be used to construct a LINQ query(so you can chain it with other methods and at the end you have a query). But only methods that don't use deferred execution like Count, Any, ToList(or a plain foreach) will actually execute it. If you dont want that the whole query is executed everytime and you have to access this query multiple times , it's better to store the result in a collection(.f.e with ToList).
However, you could use a different approach which should be much more efficient, a Lookup<TKey, TValue> which is similar to a dictionary and can be used with an anonymous type as key:
var lookup = existingRecords.Entities.ToLookup(r => new
{
fund = r["field_1"].ToString(),
bps = Convert.ToDecimal(r["field_2"]),
withdrawalPct = Convert.ToDecimal(r["field_3"]),
percentile = Convert.ToDecimal(r["field_4"]),
age = Convert.ToDecimal(r["field_5"])
});
Now you can access this lookup in the loop very efficiently.
foreach (var record in inputDataLines)
{
var fields = record.Split(',');
var fund = fields[0];
var bps = Convert.ToDecimal(fields[1]);
var withdrawalPct = Convert.ToDecimal(fields[2]);
var percentile = Convert.ToInt32(fields[3]);
var age = Convert.ToInt32(fields[4]);
var bombOutTerm = Convert.ToDecimal(fields[5]);
var matchingRows = lookup[new {fund, bps, withdrawalPct, percentile, age}].ToList();
entitiesFound.AddRange(matchingRows);
if (matchingRows.Count() == 0)
{
rowsToAdd.Add(record);
}
else if (matchingRows.Count() == 1)
{
if (Convert.ToDecimal(matchingRows.First()["field_6"]) != bombOutTerm)
{
rowsToUpdate.Add(record);
entitiesToUpdate.Add(matchingRows.First());
}
}
else
{
entitiesToDelete.AddRange(matchingRows);
rowsToAdd.Add(record);
}
}
Note that this will work even if the key does not exist(an empty list is returned).
Add a ToList after your Convert.ToDecimal(r["field_5"]) == age);-line to force an immediate execution of the query.
var matchingRows = existingRecords.Entities.Where(r => r["field_1"].ToString() == fund
&& Convert.ToDecimal(r["field_2"]) == bps
&& Convert.ToDecimal(r["field_3"]) == withdrawalPct
&& Convert.ToDecimal(r["field_4"]) == percentile
&& Convert.ToDecimal(r["field_5"]) == age)
.ToList();
The Where doesn´t actually execute your query, it just prepares it. The actual execution happens later in a delayed way. In your case that happens when calling Count which itself will iterate the entire collection of items. But if the first condition fails, the second one is checked leading to a second iteration of the complete collection when calling Count. In this case you actually execute that query a thrird time when calling matchingRows.First().
When forcing an immediate execution you´re executing the query only once and thus iterating the entire collection only once also which will decrease your overall-time.
Another option, which is basically along the same lines as the other answers, is to prepare your data first, so that you're not repeatedly calling things like r["field_2"] (which are relatively slow to look up).
This is a (1) clean your data, (2) query/join your data, (3) process your data approach.
Do this:
(1)
var inputs =
inputDataLines
.Select(record =>
{
var fields = record.Split(',');
return new
{
fund = fields[0],
bps = Convert.ToDecimal(fields[1]),
withdrawalPct = Convert.ToDecimal(fields[2]),
percentile = Convert.ToInt32(fields[3]),
age = Convert.ToInt32(fields[4]),
bombOutTerm = Convert.ToDecimal(fields[5]),
record
};
})
.ToArray();
var entities =
existingRecords
.Entities
.Select(entity => new
{
fund = entity["field_1"].ToString(),
bps = Convert.ToDecimal(entity["field_2"]),
withdrawalPct = Convert.ToDecimal(entity["field_3"]),
percentile = Convert.ToInt32(entity["field_4"]),
age = Convert.ToInt32(entity["field_5"]),
bombOutTerm = Convert.ToDecimal(entity["field_6"]),
entity
})
.ToArray()
.GroupBy(x => new
{
x.fund,
x.bps,
x.withdrawalPct,
x.percentile,
x.age
}, x => new
{
x.bombOutTerm,
x.entity,
});
(2)
var query =
from i in inputs
join e in entities on new { i.fund, i.bps, i.withdrawalPct, i.percentile, i.age } equals e.Key
select new { input = i, matchingRows = e };
(3)
foreach (var x in query)
{
entitiesFound.AddRange(x.matchingRows.Select(y => y.entity));
if (x.matchingRows.Count() == 0)
{
rowsToAdd.Add(x.input.record);
}
else if (x.matchingRows.Count() == 1)
{
if (x.matchingRows.First().bombOutTerm != x.input.bombOutTerm)
{
rowsToUpdate.Add(x.input.record);
entitiesToUpdate.Add(x.matchingRows.First().entity);
}
}
else
{
entitiesToDelete.AddRange(x.matchingRows.Select(y => y.entity));
rowsToAdd.Add(x.input.record);
}
}
I would suspect that this will be the among the fastest approaches presented.
I make one database trip to get a list of entities.
I then would like to separate this list into 2 lists, one for the entities that have not expired (using a start and end) which i call TopListings and another which are regular listings, those that have expired or have start/end date as null (the ones that are not TopListings)
I am not entirely sure which filtering is fasted to separate into 2 lists, should I get the toplist first, then filter second list based on what is NOT in the top list for second?
var listings = ListingAdapter.GetMapListings(criteria);
var topListings = listings.Where(x => x.TopStartDate >= DateTime.Now && x.TopExpireDate >= DateTime.Now);
//I AM NOT SURE WHAT THIS LINE SHOULD BE
var regularListings = listings.Where(x => x.TopStartDate < DateTime.Now || x.TopExpireDate < DateTime.Now || x.TopStartDate == null || x.TopExpireDate == null );
Thank you
You might want to use a LookUp
like this:
var lookup = listings.ToLookup(x => x.TopStartDate >= DateTime.Now && x.TopExpireDate >= DateTime.Now);
var topListings = lookup[true];
var regularListings = lookup[false]; // I assume everything not a topListing is a regular listing.
If this isnt enough, you could create an enum
enum ListingType { Top, Regular, WhatEver };
...
var lookup = listings.ToLookUp(determineListingType); // pass a methoddelegate that determines the listingtype for an element.
...
var topListings = lookup[ListingType.Top];
var regularListings = lookup[ListingType.Regular];
var whateverListings = lookup[ListingType.WhatEver];
In this case, it would probably be easier to use a loop, instead of Linq operators:
var topListings = new List<Listing>();
var regularListings = new List<Listing>();
foreach (var x in listings)
{
if (x.TopStartDate >= DateTime.Now && x.TopExpireDate >= DateTime.Now)
topListings.Add(x);
else
regularListings.Add(x);
}
This is also more efficient, because the list is enumerated only once.
Take a look at the 'Except' operator to make things a little easier. You might have to add a .ToList() on topListings first though.
var regularListings = listings.Except(topListings);
http://blogs.msdn.com/b/charlie/archive/2008/07/12/the-linq-set-operators.aspx
Make use of regular foreach loop that's straight forward. You can iterate through listing with one go and add items to appropriate collections. If you are LINQ kind of guy, ForEach extension is what you are looking for:
var topListings = new List<Listing>();
var regularListings = new List<Listing>();
listing.ForEach(item=>{
if (x.TopStartDate < DateTime.Now
|| // I've inverted the condition, since it is faster-one or two conditions will be checked, instead of always two
x.TopExpireDate < DateTime.Now)
regularListings.Add(x);
else
topListings.Add(x);
});
I'm fairly new to programming but have been tasked with maintaining some applications that were created by a previous employee. I have a ?: statement that now needs to handle more than a true or false statement but I'm not sure how to go about doing it. The code in question is:
MailDomainContext mail = new MailDomainContext();
mail.Load(mail.GetMailsQuery("Workforce Attendence Issue",
loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().Username,
(loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().EmployeeShiftID >= 2 ? "supervisor1" : "supervisor2"),
loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().FirstName,
attendence.AttendenceDate.ToString("MM/dd/yyyy"),
attendence.TimeLost,
loadAbs.Entities.Where(abs => abs.AbsenceID == attendence.AbsenceID).First().AbsenceDescription,
(from inf in loadAtt.Entities
where inf.EmployeeID == _EmployeeID
where inf.AttendenceDate > DateTime.Now.AddDays(30 * -1)
where inf.Approved == false
select inf).Count() + 1,
attendence.UTOUsed
), null, null);
More specifically this line:
(loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().EmployeeShiftID >= 2 ? "supervisor1" : "supervisor2"),
I need to add 4 more supervisors to the list but haven't figured out a way to do it that doesn't make everything else unhappy. I apologize if this is too simple a question or if I left out some details you might need to know, as I said I'm pretty new to all of this.
This code is needlessly hard to maintain and also inefficient and not very defensive. The code is retrieving the employee three times.
loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().Username
The above line (and others) will throw an exception if the employee with _EmployeeID doesn't exist. Instead, you could use FirstOrDefault, or SingleOrDefault if you expect there to only ever be one employee with that ID (which should be the case as it looks like primary key for that entity). If loadEmp is actually an Entity Framework DbContext then you could also use Find.
You can do this query once and store the result in a local variable.
var employee = loadEmp.Entities.SingleOrDefault(emp => emp.EmployeeID == _EmployeeID);
if (employee == null)
{
// Handle employee not found
}
To then get the supervisor string based on the employee, you could create a method which takes the minimum amount of information needed to calculate the supervisor string, and pass this into the method to get your result.
GetSupervisorRole(employee.EmployeeShiftID);
...
private string GetSupervisorRole(int employeeShiftID)
{
// Logic here
}
One approach is to extract that code into a method and write that method any way you want.
Another approach is to use dictionary to map keys (if you have small number of them) to values.
var id =3;
var mapping = new Dictionary<int, string>() {
{ 1, "first" },
{ 2, "second" },
{ 3, "first" } //you can map 2 values (1,3) to the same "first" string
};
string value;
if (!mapping.TryGetValue(id, out value))
{
value = "unknown";
}
Create the following method:
string GetSupervisor(int employeeShiftId) {
if (employeeShiftId == 1) supervisor = "supervisor1";
else if (employeeShiftId == 2) supervisor = "supervisor2";
else if (employeeShiftId == 3) supervisor = "supervisor3";
else if (employeeShiftId == 4) supervisor = "supervisor4";
}
Then call it from your code and assign the result to a variable supervisor, which you can then use in mail.Load():
int employeeShiftId = loadEmp.Entities
.Where(emp => emp.EmployeeID == _EmployeeID).First()
.EmployeeShiftID;
string supervisor = GetSupervisor(employeeShiftId);
MailDomainContext mail = new MailDomainContext();
mail.Load(mail.GetMailsQuery("Workforce Attendence Issue",
loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().Username,
supervisor, // <-- Then use it here
...
);
I would replace this whole section
loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().Username,
(loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().EmployeeShiftID >= 2 ? "supervisor1" : "supervisor2"),
loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First().FirstName,
with
//Assign these variables ahead of time so others reading your code can
//figure out what's going on
var EmpID = loadEmp.Entities.Where(emp => emp.EmployeeID == _EmployeeID).First();
var UserName = EmpID.UserName;
var FirstName = EmpID.FirstName;
var Title = GetTitle(EmpID.EmployeeShiftID);
//Your original call
Mail.Load(mail.GetMailsQuery("Workforce Attendence Issue",
UserName,
Title,
FirstName,
//...Etc
);
//New method you will need to add, you could do this logic in line, but this will
//be less messy
private string GetTitle(int ShiftID)
{
switch (ShiftID)
{
case 1:
return "supervisor1";
break;
case 2:
return "supervisor2";
break;
//...etc
}
}
Not that this is a great idea, but you can combine inline if's as so:
static void Main(string[] args)
{
int EmpId = 2;
string supervisor = EmpId == 4 ? "Supervisor4" :
EmpId == 3 ? "Supervisor3" :
EmpId == 2 ? "supervisor2" :
"supervisor1" ;
Console.WriteLine(supervisor);
}
You can see another example of this in another stack question: Legible or not: C# multiple ternary operators + Throw if unmatched
I would probably instead go for the Method approach like KDiTraglia proposed where you just pass in the EmpId and get the supervisor name back, or alternatively the Dictionary lookup approach like Alexei Levenkov propsed though.
Can you help me with next: I'd like to add variable count of SqlMethods.Like() in one query?
For example: I've four words in list catCode, how can I create LINQ query with four SqlMethods.Like()?
using (var db = new MappingDataContext(_connection))
{
db.ObjectTrackingEnabled = false;
return (
from r in db.U_CDW_REPORTs
where (catCode.Length > 0 ?
SqlMethods.Like(r.CATEGORY, catCode) : r.CATEGORY != "*")
where r.SI_QTY > 0
orderby r.SI_QTY descending
select new Item
{
...
}).ToList();
}
What you need is dynamically OR-ing SqlMethod.Like operations. You need the PredicateBuilder.
Update: Here is an example of how to use the predicate builder.
string[] searchItems =
new string[] { "Pattern1", "Pattern2", "Pattern3" };
var likeExpression = PredicateBuilder.False<U_CDW_REPORT>();
foreach (string searchItem in searchItems)
{
var searchPattern = "%" + searchItem + "%";
likeExpression = likeExpression.Or(r =>
SqlMethods.Like(r.CATEGORY, searchPattern));
}
return (
from r in db.U_CDW_REPORTs.Where(likeExpression)
where r.SI_QTY > 0
orderby r.SI_QTY descending
select new Item { ... }).ToList();
You mean you have 4 disjoint LIKE clauses?
from entityRow in ctx.MyEntity
where (SqlMethods.Like(entityRow.Col, "%Pattern1%")
|| SqlMethods.Like(entityRow.Col, "%Patte%rn2%")
|| SqlMethods.Like(entityRow.Col, "Pattern3%")
|| SqlMethods.Like(entityRow.Col, "%Patte%rn4%"))
select entityRow;
UPDATE:
In that case, take a look at this: LINQ for LIKE queries of array elements
EDIT: made the code simpler. Also notice the updated explanation below.
You can build upon the query with multiple Where clauses in the following manner. Note that this approach ANDs the Where clauses, meaning all search terms will need to exist. In other words it's similar to ... where (condition1) && (condition2) && (conditionN).
string[] words = { "A", "B", "C" };
var query = dc.Products.AsQueryable(); // gives us an IQueryable<T> to build upon
foreach (var s in words)
{
query = query.Where(p => SqlMethods.Like(p.ProductName, "%" + s + "%"));
}
foreach (var item in query)
{
Console.WriteLine(item.ProductName);
}
The idea is to set the first part of the query up, then loop over the search terms and update the query. Of course you should update your pattern as needed; I used %word% for illustration purposes only.
I'm trying to select a subgroup of a list where items have contiguous dates, e.g.
ID StaffID Title ActivityDate
-- ------- ----------------- ------------
1 41 Meeting with John 03/06/2010
2 41 Meeting with John 08/06/2010
3 41 Meeting Continues 09/06/2010
4 41 Meeting Continues 10/06/2010
5 41 Meeting with Kay 14/06/2010
6 41 Meeting Continues 15/06/2010
I'm using a pivot point each time, so take the example pivot item as 3, I'd like to get the following resulting contiguous events around the pivot:
ID StaffID Title ActivityDate
-- ------- ----------------- ------------
2 41 Meeting with John 08/06/2010
3 41 Meeting Continues 09/06/2010
4 41 Meeting Continues 10/06/2010
My current implementation is a laborious "walk" into the past, then into the future, to build the list:
var activity = // item number 3: Meeting Continues (09/06/2010)
var orderedEvents = activities.OrderBy(a => a.ActivityDate).ToArray();
// Walk into the past until a gap is found
var preceedingEvents = orderedEvents.TakeWhile(a => a.ID != activity.ID);
DateTime dayBefore;
var previousEvent = activity;
while (previousEvent != null)
{
dayBefore = previousEvent.ActivityDate.AddDays(-1).Date;
previousEvent = preceedingEvents.TakeWhile(a => a.ID != previousEvent.ID).LastOrDefault();
if (previousEvent != null)
{
if (previousEvent.ActivityDate.Date == dayBefore)
relatedActivities.Insert(0, previousEvent);
else
previousEvent = null;
}
}
// Walk into the future until a gap is found
var followingEvents = orderedEvents.SkipWhile(a => a.ID != activity.ID);
DateTime dayAfter;
var nextEvent = activity;
while (nextEvent != null)
{
dayAfter = nextEvent.ActivityDate.AddDays(1).Date;
nextEvent = followingEvents.SkipWhile(a => a.ID != nextEvent.ID).Skip(1).FirstOrDefault();
if (nextEvent != null)
{
if (nextEvent.ActivityDate.Date == dayAfter)
relatedActivities.Add(nextEvent);
else
nextEvent = null;
}
}
The list relatedActivities should then contain the contiguous events, in order.
Is there a better way (maybe using LINQ) for this?
I had an idea of using .Aggregate() but couldn't think how to get the aggregate to break out when it finds a gap in the sequence.
Here's an implementation:
public static IEnumerable<IGrouping<int, T>> GroupByContiguous(
this IEnumerable<T> source,
Func<T, int> keySelector
)
{
int keyGroup = Int32.MinValue;
int currentGroupValue = Int32.MinValue;
return source
.Select(t => new {obj = t, key = keySelector(t))
.OrderBy(x => x.key)
.GroupBy(x => {
if (currentGroupValue + 1 < x.key)
{
keyGroup = x.key;
}
currentGroupValue = x.key;
return keyGroup;
}, x => x.obj);
}
You can either convert the dates to ints by means of subtraction, or imagine a DateTime version (easily).
In this case I think that a standard foreach loop is probably more readable than a LINQ query:
var relatedActivities = new List<TActivity>();
bool found = false;
foreach (var item in activities.OrderBy(a => a.ActivityDate))
{
int count = relatedActivities.Count;
if ((count > 0) && (relatedActivities[count - 1].ActivityDate.Date.AddDays(1) != item.ActivityDate.Date))
{
if (found)
break;
relatedActivities.Clear();
}
relatedActivities.Add(item);
if (item.ID == activity.ID)
found = true;
}
if (!found)
relatedActivities.Clear();
For what it's worth, here's a roughly equivalent -- and far less readable -- LINQ query:
var relatedActivities = activities
.OrderBy(x => x.ActivityDate)
.Aggregate
(
new { List = new List<TActivity>(), Found = false, ShortCircuit = false },
(a, x) =>
{
if (a.ShortCircuit)
return a;
int count = a.List.Count;
if ((count > 0) && (a.List[count - 1].ActivityDate.Date.AddDays(1) != x.ActivityDate.Date))
{
if (a.Found)
return new { a.List, a.Found, ShortCircuit = true };
a.List.Clear();
}
a.List.Add(x);
return new { a.List, Found = a.Found || (x.ID == activity.ID), a.ShortCircuit };
},
a => a.Found ? a.List : new List<TActivity>()
);
Somehow, I don't think LINQ was truly meant to be used for bidirectional-one-dimensional-depth-first-searches, but I constructed a working LINQ using Aggregate. For this example I'm going to use a List instead of an array. Also, I'm going to use Activity to refer to whatever class you are storing the data in. Replace it with whatever is appropriate for your code.
Before we even start, we need a small function to handle something. List.Add(T) returns null, but we want to be able to accumulate in a list and return the new list for this aggregate function. So all you need is a simple function like the following.
private List<T> ListWithAdd<T>(List<T> src, T obj)
{
src.Add(obj);
return src;
}
First, we get the sorted list of all activities, and then initialize the list of related activities. This initial list will contain the target activity only, to start.
List<Activity> orderedEvents = activities.OrderBy(a => a.ActivityDate).ToList();
List<Activity> relatedActivities = new List<Activity>();
relatedActivities.Add(activity);
We have to break this into two lists, the past and the future just like you currently do it.
We'll start with the past, the construction should look mostly familiar. Then we'll aggregate all of it into relatedActivities. This uses the ListWithAdd function we wrote earlier. You could condense it into one line and skip declaring previousEvents as its own variable, but I kept it separate for this example.
var previousEvents = orderedEvents.TakeWhile(a => a.ID != activity.ID).Reverse();
relatedActivities = previousEvents.Aggregate<Activity, List<Activity>>(relatedActivities, (items, prevItem) => items.OrderBy(a => a.ActivityDate).First().ActivityDate.Subtract(prevItem.ActivityDate).Days.Equals(1) ? ListWithAdd(items, prevItem) : items).ToList();
Next, we'll build the following events in a similar fashion, and likewise aggregate it.
var nextEvents = orderedEvents.SkipWhile(a => a.ID != activity.ID);
relatedActivities = nextEvents.Aggregate<Activity, List<Activity>>(relatedActivities, (items, nextItem) => nextItem.ActivityDate.Subtract(items.OrderBy(a => a.ActivityDate).Last().ActivityDate).Days.Equals(1) ? ListWithAdd(items, nextItem) : items).ToList();
You can properly sort the result afterwards, as now relatedActivities should contain all activities with no gaps. It won't immediately break when it hits the first gap, no, but I don't think you can literally break out of a LINQ. So it instead just ignores anything which it finds past a gap.
Note that this example code only operates on the actual difference in time. Your example output seems to imply that you need some other comparison factors, but this should be enough to get you started. Just add the necessary logic to the date subtraction comparison in both entries.