Entity Framework: Load data from different tables in one query - c#

In my C# project with EF Core 5.0, I have several independents tables: Clothes, Hairs, Makeup. Some of their columns are similar, but some are not..`
I need to write a method that will load the rows from these tables. What I have now is:
public async Task<(ClothesDbModel[] clothes, MakeupDbModel[] makeups, HairDbModel[] hairs)> GetDressup(int[] clothesIds, int[] makeupIds, int[] hairIds)
{
ClothesDbModel[] clothes = new ClothesDbModel[0];
if (clothesIds.Length > 0)
{
clothes = await _dbContext.Clothes.Where(c => clothesIds.Contains(c.Id)).ToArrayAsync();
}
MakeupDbModel[] makeups = new MakeupDbModel[0];
if (makeupIds.Length > 0)
{
makeups = await _dbContext.Makeups.Where(c => makeupIds.Contains(c.Id)).ToArrayAsync();
}
HairDbModel[] hairs = new HairDbModel[0];
if (hairIds.Length > 0)
{
hairs = await _dbContext.Hairs.Where(c => hairIds.Contains(c.Id)).ToArrayAsync();
}
return (clothes, makeups, hairs);
}
However, in this case, I have 3 separate queries to the database (3 awaits). I believe that is not the best way to load the data from a performance point of view. Maybe I can load the same data using DbContext only once&

I hope it will work with EF. But I know there are problems with Concat, so it may fail.
class CombinedResult
{
public ClothesDbModel Clothes;
public MakeupDbModel Makeup;
public HairDbModel Hair;
}
...
public async Task<(ClothesDbModel[] clothes, MakeupDbModel[] makeups, HairDbModel[] hairs)> GetDressup(int[] clothesIds, int[] makeupIds, int[] hairIds)
{
var queries = new List<IQueryable<CombinedResult>>();
if (clothesIds.Length > 0)
{
queries.Add(_dbContext.Clothes.Where(c => clothesIds.Contains(c.Id)).Select(c => new CombinedResult { Clothes = c }));
}
if (makeupIds.Length > 0)
{
queries.Add(_dbContext.Makeups.Where(c => makeupIds.Contains(c.Id)).Select(c => new CombinedResult { Makeup = c }));
}
if (hairIds.Length > 0)
{
queries.Add(_dbContext.Hairs.Where(c => hairIds.Contains(c.Id)).Select(c => new CombinedResult { Hair = c }));
}
var clothes = new List<ClothesDbModel>();
var makeups = new List<MakeupDbModel>();
var hairs = new List<HairDbModel>();
if (queries.Count > 0)
{
var query = queries[0];
for (var i = 1; i < queries.Count; i++)
{
query = query.Concat(queries[i]);
}
var items = await query.ToListAsync();
foreach (var item in items)
{
if (item.Clothes != null)
clothes.Add(item.Clothes);
else if (item.Makeup != null)
makeups.Add(item.Makeup);
else if (item.Hair != null)
hairs.Add(item.Hair);
}
}
return (clothes.ToArray(), makeups.ToArray(), hairs.ToArray());
}

Related

How to convert nested for loop with if condition to .Net Linq?

I am working on a function which will modify the input payload properties. The payload contains nodes and each node contains list of features. I need to remove some specific features match condition and also modify each node window property start and end time. I have written the function using traditional nested for loop, but struggling to convert it to Linq function. Anyone has idea how to convert this nested for loop function to a Linq function?
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
var nodes = payload.Nodes;
for (var i = 0; i < nodes.Count(); ++i)
{
var node = nodes[i];
var features = node.Features;
for (var j = 0; j < features.Count(); ++j)
{
var feature = features[j];
if (feature.NodeFeatureTypeID
== FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
{
features.RemoveAt(j);
}
}
var windows = node.Windows;
for (var k = 0; k < windows.Count(); ++k)
{
var window = windows[k];
if (window.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS) continue;
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
}
Let's do it in parts. This first code of yours removes features that are in a list,
for (var j = 0; j < features.Count(); ++j)
{
var feature = features[j];
if (feature.NodeFeatureTypeID
== FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
{
features.RemoveAt(j);
}
}
We need to convert that to "keep features not in a list"
var discard = new [] {FeatureTypeEnum.FEATURE_A, FeatureTypeEnum.FEATURE_B, FeatureTypeEnum.FEATURE_C, FeatureTypeEnum.FEATURE_D };
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
This part of your code skips if function type is a certain kind, and zeroes timespans if the function type is not a certain kind:
if (window.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS) continue;
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
Which might be better as a loop that operates on just those things we are interested in (we want to modify only those Windows where the nodefuctionTypeId is not Hours):
foreach(var window in node.Windows.Where(w => w.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS){
window.StartHour = new TimeSpan.FromHous(startTime);
window.EndHour = new TimeSpan.FromHours(endTime);
}
Meaning the whole is:
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
var discard = new [] {FeatureTypeEnum.FEATURE_A, FeatureTypeEnum.FEATURE_B, FeatureTypeEnum.FEATURE_C, FeatureTypeEnum.FEATURE_D };
foreach (var node in payload.Nodes)
{
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
foreach(var window in node.Windows.Where(w => w.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS){
window.StartHour = TimeSpan.FromHous(startTime);
window.EndHour = TimeSpan.FromHours(endTime);
}
}
}
I don't think I'd convert it all to a Linq form, as it would make a mess; linq queries should not have side effects (modify the objects the query iterates over) so particularly the second part where the time spans are being zeroed would have to become an operation where everything about the window was being copied over to a new Window if you wanted to LINQ it.
If a window is literally just a time span pair then it's not so bad, or if you want to provide a constructor that takes an existing Window and a startTime and endTime:
public Window(Window copyFrom, int startTime, int endTime){
this.X = copyFrom.X;
this.Y = copyFrom.Y;
...
this.StartHour = TimeSpan.FromHours(strtTime);
this.EndHour = TimeSpan.FromHours(endTime);
}
Then maybe your method could become some linq:
foreach (var node in payload.Nodes)
{
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
node.Windows = node.Windows.Select(w => w.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS ? w : new Window(w, startTime, endTime).ToArray();
}
..but I don't know if I would try for a wholesale replacement of the entire nodes list, for reasons even based on the name of the method: ApplyTransformations sounds like it means "take this list of nodes and change bits of them" not "take this list of nodes and give me a new list with some or all of the nodes replaced or modified" - that sort of behavior in code could wreck something else, if the calling code is expecting a tweak and the object it sends (or objects within it) are swapped out for new ones
Using a linq query for the second part would make things messier, better to just have a for loop
Something like this should work:
var nodes = payload.Nodes;
nodes.Features = nodes.Features.Where(f => !(
f.NodeFeatureTypeID == FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
);
foreach(var window in nodes.Windows)
{
if (window.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS)
{
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
Pls have look at below code snippet to avoid nested loop using linq.
public class Employee
{
public string Name { get; set; }
public List<EmployeeProject> EmployeeProject { get; set; }
}
public class EmployeeProject
{
public string ProjectName { get; set; }
public string ClientName { get; set; }
}
/// <summary>
/// Object mapping
/// </summary>
/// <returns></returns>
public static List<Employee> CreateObject()
{
var employeeList = new List<Employee>();
var employee = new Employee();
var employeeProjectList = new List<EmployeeProject>();
employee.Name = "John";
var employeeProject = new EmployeeProject();
employeeProject.ProjectName = "Chrome";
employeeProject.ClientName = "Google";
employeeProjectList.Add(employeeProject);
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "WhatsApp";
employeeProject.ClientName = "Meta";
employeeProjectList.Add(employeeProject);
employee.EmployeeProject = employeeProjectList;
employeeList.Add(employee);
employee.Name = "Alex";
employeeProjectList = new List<EmployeeProject>();
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "Chrome2";
employeeProject.ClientName = "Google2";
employeeProjectList.Add(employeeProject);
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "WhatsApp2";
employeeProject.ClientName = "Meta2";
employeeProjectList.Add(employeeProject);
employee.EmployeeProject = employeeProjectList;
employeeList.Add(employee);
return employeeList;
}
/// <summary>
/// Linq function
/// </summary>
public static void LinqFunctionForNestedQuery()
{
var employeeObject = CreateObject();
var result1 = employeeObject.Select(x =>
{
x.EmployeeProject = x.EmployeeProject.Select(y =>
{
y.ProjectName.Contains("Chrome");
return y;
}).ToList();
return x;
});
}
To maintain readability of the code and to make it simpler , I have modified the code.
Step1 : Replace for with foreach if you can
Step2 : Replace foreach with linq
Important tip. Resharper helps you with code suggestions.
Solution
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
foreach (var node in payload.Nodes)
{
var features = node.Features;
foreach (var feature in features.Where(feature => feature.NodeFeatureTypeID == FeatureTypeEnum.FEATURE_A ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D))
{
features.Remove(feature);
}
var windows = node.Windows;
foreach (var window in windows
.Where(window => window.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS))
{
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
}

loop through sql results and delete the single rows

Im trying to delete persons from a list through a for loop. My problem is now that im trying to find each id to delete the specific row of my database but i dont know how i can do it with a for loop.
My Code right now:
[HttpPost("delUebertrag/")]
public async Task<ActionResult<ProzessPersonenzuordnungen>> delRecUebertrag([FromBody] recUebertragModel user)
{
ProzessPersonenzuordnungen ppz = new();
for (var i = 0; i <= user.personList.Length; i++)
{
Guid personId = new Guid(user.personList[i]);
ppz.ProzessId = new Guid(user.prozessId);
var prozessPersonenzuordnungen = _context.ProzessPersonenzuordnungens.Where(p => p.ProzessId == ppz.ProzessId && p.PersonId == personId);
if (prozessPersonenzuordnungen == null)
{
return NotFound();
}
//Everythings works fine above, prozessPersonenzuordnungen haves for Example 2 results
for(var j = 0; j < prozessPersonenzuordnungen.Count(); j++) // i dont know if Counts is fine, looking for something like length of the results
{
var toDeletingRow = await _context.ProzessPersonenzuordnungens.FindAsync(prozessPersonenzuordnungen.Select(p => p.ProzessPersonenzuordnungId)); // Here i need to go through every singleId of my results, something like p.ProzessPersonenzuordnungId[j](does not work)
_context.ProzessPersonenzuordnungens.Remove(toDeletingRow);
await _context.SaveChangesAsync();
}
}
return Ok();
}
The FindAsync can be only used for single item. Instead do
var rowsToDelete = _context.ProzessPersonenzuordnungens.Where(x=> prozessPersonenzuordnungen.Select(p => p.ProzessPersonenzuordnungId).Contains(x.Id));
And instead of Remove, use
_context.ProzessPersonenzuordnungens.RemoveRange(rowsToDelete);
And one important thing, accessing database in a loop is a very bad practice.
Try to do it without the loop.
Edit:
Try this
ProzessPersonenzuordnungen ppz = new ProzessPersonenzuordnungen();
for (var i = 0; i <= user.personList.Length; i++)
{
Guid personId = new Guid(user.personList[i]);
ppz.ProzessId = new Guid(user.prozessId);
var prozessPersonenzuordnungen = _context.ProzessPersonenzuordnungens.Where(p => p.ProzessId == ppz.ProzessId && p.PersonId == personId)
.Select((x)=> x.ProzessPersonenzuordnungId);
if (prozessPersonenzuordnungen == null)
{
return NotFound();
}
var rowsToDelete = _context.ProzessPersonenzuordnungens.Where(x => prozessPersonenzuordnungen.Contains(x.Id));
_context.ProzessPersonenzuordnungens.RemoveRange(rowsToDelete);
await _context.SaveChangesAsync();
return Ok();
}

using group by for getting some statistic data from one table with bulk records and puting the results into the second table

As I explained in title I have 2 tables :
1 - "Leaves" with 21000 rec of leaves for about 50 peoples during 20 years
2- "StatisticLeave" which is empty
now i want to use group by for getting some statistic data from table 1 and after doing some calculation (like sum & ...) putting the results into the second table.
I wrote below code :
public ActionResult UserStatistic()
{
var ST = new List<StatisticLeave>();
var resuls = db.Leaves.GroupBy(p => p.Pcode);
foreach (var Pcode in resuls)
{
var statistic = new StatisticLeave();
foreach (var item in Pcode)
{
var used = UsedLeaves(item.Pcode);
var estelaji = db.Leaves.Where(p => p.DLT == Leave.DLType.Estelaji).Sum(p => p.LeaveDays);
var bh = db.Leaves.Where(p => p.DLT == Leave.DLType.Bihoghoogh).Sum(p => p.LeaveDays);
statistic.Yearlyhours = ViewBag.mins/60;
statistic.YearlyDays = ViewBag.days;
statistic.YearEstelaji = estelaji;
statistic.YearBihoghoogh = bh;
statistic.Pcode = item.Pcode;
statistic.Year = item.HijriYear;
statistic.UsedYearLaeve = (used / 60) / 8;
ST.Add(statistic);
}
db.StatisticLeave.AddRange(ST);
}
db.SaveChanges();
return View();
}
when I trace the code i get the following caution:
"System.InvalidOperationException
HResult=0x80131509
Message=Unable to translate the given 'GroupBy' pattern. Call 'AsEnumerable' before 'GroupBy' to evaluate it client-side."
would you please tell me where is the problem or how can i fix it.
I changed my code as below and the problem Solved
public ActionResult UserStatistic(Int32? PCode)
{
var ST = new List<StatisticLeave>();
var results = db.Leaves.Where(p => p.Pcode == PCode).ToLookup(p => p.HijriYear);
foreach (var HijriYear in results)
{
var statistic = new StatisticLeave();
foreach (var item in HijriYear)
{
var used = UsedLeaves(item.Pcode, item.HijriYear);
var estelaji = db.Leaves.Where(x => x.Pcode == item.Pcode).Where(p => p.DLT == Leave.DLType.Estelaji).Where(p => p.HijriYear == item.HijriYear).Sum(p => p.LeaveDays);
var bh = db.Leaves.Where(x => x.Pcode == item.Pcode).Where(p => p.DLT == Leave.DLType.Bihoghoogh).Where(p => p.HijriYear == item.HijriYear).Sum(p => p.LeaveDays);
statistic.Yearlyhours = ViewBag.mins / 60;
statistic.YearlyDays = ViewBag.days;
statistic.YearEstelaji = estelaji;
statistic.YearBihoghoogh = bh;
statistic.Pcode = item.Pcode;
statistic.Year = item.HijriYear;
statistic.UsedYearLaeve = (used / 60) / 8;
ST.Add(statistic);
}
db.StatisticLeave.AddRange(ST);
}
db.SaveChanges();
return View();
}

How to fix : Cannot convert lambda expression to type int because is not delegate?

i am trying to get user decision from the list but i get that Cannot convert lambda expression to type int because is not delegate. How can i solve this? I have had a look on the internet but since i am quite new i am not sure what is the right solution
public static List<int> UserDecisionResult { get; set; }
public void GetUserDecision()
{
List<int> userDecision = new List<int>();
if (FilterAllItems) userDecision.Add(_parentCategoryId = -1);
if (FilterBeginnerItems) userDecision.Add(_parentCategoryId = 1);
if (FilterIntermediateItems) userDecision.Add(_parentCategoryId = 2);
if (FilterUpperIntermediateItems) userDecision.Add(_parentCategoryId = 3);
if (FilterAdvancedItems) userDecision.Add(_parentCategoryId = 4);
UserDecisionResult = userDecision;
}
private static List<Article> FindAllArticlesForPurchase(List<Article> allArticles)
{
var result = UserDecisionResult;
if (_parentCategoryId != -1)
{
foreach (var categoryGroup in _allUserCategoryGroups)
{
var allGroupCategories = _allCategories.Where(m => m.CategoryGroupId == categoryGroup.Id).ToList();
if (_parentCategoryId != -1)
{
foreach (var category in allGroupCategories)
{
if (category.ParentId == _parentCategoryId && _parentCategoryId != -1)
{
var categoryArticles = _allArticlesForPurchase.Where(result.Contains(m => m.CategoryId == category.Id).ToList();
//var categoryArticles = _allArticlesForPurchase.Where(result.Contains(m => m.CategoryId == category.Id).ToList());
allArticles.AddRange(categoryArticles);
}
}
}
}
}
else
{
allArticles = _allArticlesForPurchase;
}
return allArticles;
}
Your lambda expression is wrong here due to syntax. You can try:
var categoryArticles = _allArticlesForPurchase
.Where(m => m.CategoryId == category.Id
&& result.Contains(m.CategoryId))
.ToList();
or if Contains here is some custom implementation then try :
var categoryArticles = _allArticlesForPurchase
.Where(_ => result.Contains(m => _.CategoryId == category.Id))
.ToList();

Merging lists with a for loop

I'm working on an algorithm which can generate 2 types of recommendations, restaurants and dishes. All of this works fine, but I wanted to merge these 2 types of recommendations in a single list, which is where I encountered some issues. From my previous question I concluded that I needed a wrapper class, which I have set up like this:
public class RecommenderItem
{
public Guid Id { get; set; }
public object Entity { get; set; }
}
Now I want to alternate the 2 types of recommendations so the list would look like this:
[Restaurant][Dish][Restaurant][Dish][Restaurant][Dish] //Etc...
Note that these recommendations are completely separate. They are generated purely based on the user's preference, and they have no correlation in between them. My product owner wants to show these recommendations on the home page of our app like this.
These lists are different in length, so if I have added all items from a list, I wanted to just add the remaining objects from the other list. A possible scenario of this could look like this:
/*Other objects before this...*/[Dish][Restaurant][Dish][Dish][Dish] //Etc...
Here did the list of restaurant objects run out and I just wanted to add the remaining dish recommendations at the end of the list.
I have gotten this far, but I'm unsure how I would catch an IndexOutOfBounds exception and add the rest of the remaining objects at the end.
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations,
List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = 0;
//Check which list is longer and use that count
if (restaurantRecommendations.Count > dishRecommendations.Count)
count = dishRecommendations.Count;
else
count = restaurantRecommendations.Count;
for (int i = 0; i < count; i++)
{
//I'm fully aware this isn't the most optimal way of doing this,
//but I'm only looking at functionality here, optimizing performance comes later.
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
return output;
}
Does anyone have an idea how I could do this? Could I just catch an IndexOutOfBounds exception and use .AddRange() for the remaining objects? I'm not sure how I could check which list was out of bounds.
Let me know if I should elaborate more and thanks in advance!
Edit: -removed because it wasn't fair.-
This is a fairly succinct way of doing this.
While not Linq, it works in the spirit of the way Linq works by deferring doing any work until the resulting sequence is enumerated:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
using (var r = restaurants.GetEnumerator())
using (var d = dishes.GetEnumerator())
{
while (true)
{
bool rAvailable = r.MoveNext();
bool dAvailable = d.MoveNext();
if (rAvailable)
yield return new RecommenderItem { Id = r.Current.Id, Entity = r.Current };
if (dAvailable)
yield return new RecommenderItem { Id = d.Current.Id, Entity = d.Current };
if (!rAvailable && !dAvailable)
break;
}
}
}
If you happen to be using the MoreLinq NuGet package that includes the ZipLongest extension method, you can use the following simplified implementation instead:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
foreach (var item in restaurants.ZipLongest(dishes, (r, d) => new { r, d }))
{
if (item.r != null)
yield return new RecommenderItem { Id = item.r.Id, Entity = item.r };
if (item.d != null)
yield return new RecommenderItem { Id = item.d.Id, Entity = item.d };
}
}
Addendum
As #InBetween posted in his answer, you can put the interleaving logic into an extension method. Here's my version; it's substantially the same, except I've added a small optimisation to avoid calling .MoveNext() when its not necessary:
public static class EnumerableExt
{
public static IEnumerable<T> Interleave<T>(this IEnumerable<T> a, IEnumerable<T> b)
{
using (var ae = a.GetEnumerator())
using (var be = b.GetEnumerator())
{
bool aAvailable = true;
bool bAvailable = true;
while (aAvailable || bAvailable)
{
aAvailable = aAvailable && ae.MoveNext();
bAvailable = bAvailable && be.MoveNext();
if (aAvailable)
yield return ae.Current;
if (bAvailable)
yield return be.Current;
}
}
}
}
Once you have that, I realised that you don't need to write an implict operator. Instead, you can just convert the two sequences to the resultant type before calling Interleave() like so:
var restaurantsAsRecommenderItems =
restaurantRecommendations
.Select(r => new RecommenderItem {Id = r.Id, Entity = r});
var dishesAsRecommenderItems =
dishRecommendations
.Select(d => new RecommenderItem {Id = d.Id, Entity = d});
var result =
restaurantsAsRecommenderItems
.Interleave(dishesAsRecommenderItems)
.ToList();
My recommendation would be to just make simple implicit operator :
public static implicit operator RecommenderItem(Restaurant restaurant) {
return new RecommenderItem { Id = restaurant.Id, Entity = restaurant };
}
Then you have possibility to convert these types easily like :
Restaurant rest = //...
RecommenderItem rItem = rest; // here the implicit operator is called
After doing this you can just use one for loop :
int count = Math.Max(restaurantRecommendations.Count, dishRecommendations.Count);
for ( int i = 0; i < count; i++ ) {
if ( i < restRecommendations.Count )
output.Add(restRecommendations[i]);
if ( i < dishRecommendations.Count )
output.Add(dishRecommendations[i]);
}
This will make your work much more easier.
Well, there are probably more elegant LINQ solutions but you have already most, it's also a very efficient approach:
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations, List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = Math.Min(restaurantRecommendations.Count, dishRecommendations.Count);
for (int i = 0; i < count; i++)
{
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
int remainingRestaurant = restaurantRecommendations.Count - count;
int remainingDishes = dishRecommendations.Count - count;
if (remainingRestaurant > 0)
{
for (int i = count; i < restaurantRecommendations.Count; i++)
{
var restRecommendation = restaurantRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
}
}
else if (remainingDishes > 0)
{
for (int i = count; i < dishRecommendations.Count; i++)
{
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
}
return output;
}
A simple way of doing it would be:
public static IEnumerable<T> Merge<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
using (var firstEnumerator = first.GetEnumerator())
using (var secondEnumerator = second.GetEnumerator())
{
while (firstEnumerator.MoveNext())
{
yield return firstEnumerator.Current;
if (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
while (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
}
After having created two arrays of restaurants and dishes of the same type RecommenderItem, you can use the Zip method like :
var restaurants = restaurantRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var dishes = dishRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var output = restaurants.Zip(dishes, (r, d) => new[] { r, d })
.SelectMany(r => r).Concat(dishes.Skip(restaurants.Length))
.Concat(restaurants.Skip(dishes.Length));
Restaraunt and Dish would have to share a base type:
restaurantRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
dishRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
Once that's the case you could use something like this slightly modified version of Zip (from System.Linq):
private static IEnumerable<T> ZipThrough<T>(IEnumerable<T> first, IEnumerable<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
using (var e1 = first.GetEnumerator())
{
using (var e2 = second.GetEnumerator())
{
while (true)
if (e1.MoveNext())
{
yield return e1.Current;
if (e2.MoveNext()) yield return e2.Current;
}
else if (e2.MoveNext())
{
yield return e2.Current;
}
else
{
break;
}
}
}
}

Categories