Select a restricted subset of items based on child property - c#

I'm just wondering if there's a better way to write this code, basically the source object contains a mix of items with a boolean property however the destination object has two lists which should contain the true/false items independently.
I've written it in Linq and it works just fine but it feels as though there's a better way. Any suggestions?
void Main()
{
var s = new ResponseObject()
{
Results = new List<GroupedObject>()
{
new GroupedObject()
{
Name = "List A",
List=new List<DetailObject>()
{
new DetailObject{ Name = "Allowed", AllowedAccess = true},
new DetailObject{ Name = "Restricted", AllowedAccess = false}
}
},
new GroupedObject()
{
Name = "List B",
List=new List<DetailObject>()
{
new DetailObject{ Name = "Allowed", AllowedAccess = true},
new DetailObject{ Name = "Restricted", AllowedAccess = false}
}
}
}
};
var d = new ResponseViewModel();
d.AllowedResults = FilterObjectsByAccess(s.Results, true);
d.RestrictedResults = FilterObjectsByAccess(s.Results, false);
// Other stuff
}
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source.Where(i => i.List.Any(c => c.AllowedAccess == allowAccess))
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess)
});
}
public class ResponseObject
{
public IEnumerable<GroupedObject> Results { get; set; }
}
public class ResponseViewModel
{
public IEnumerable<GroupedObject> AllowedResults { get; set; }
public IEnumerable<GroupedObject> RestrictedResults { get; set; }
}
public class GroupedObject
{
public string Name { get; set; }
public IEnumerable<DetailObject> List { get; set; }
}
public class DetailObject
{
public string Name { get; set; }
public bool AllowedAccess { get; set; }
}

One change that may be worth benchmarking would be changing:
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source.Where(i => i.List.Any(c => c.AllowedAccess == allowAccess))
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess)
});
}
to:
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess).ToList() // `ToList` here is optional - it is a trade-off between RAM and CPU
})
.Where(z => z.List.Any());
}
Your original code, with the use of Any then Where would enumerate i.List twice. The above change would likely improve that.
Another approach, which would likely involve even higher memory consumption could be to switch to using ToLookup:
var d = new ResponseViewModel
{
AllowedResults =
FilterObjectsByAccess(s.Results)
.Select(z => new GroupedObject() { Name = z.Name, List = z.GroupedList[false] })
.Where(z => z.List.Any()),
RestrictedResults =
FilterObjectsByAccess(s.Results)
.Select(z => new GroupedObject() { Name = z.Name, List = z.GroupedList[true] })
.Where(z => z.List.Any())
};
// Other stuff
}
public List<SpecialGroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source)
{
return source.Select(i => new SpecialGroupedObject()
{
Name = i.Name,
GroupedList = i.List.ToLookup(c => c.AllowedAccess)
}).ToList();
}

I can suggest you to use ToDictionary() like this:
var result = new[] {true, false}.ToDictionary(k => k,
v =>
s.Results.Where(w => w.List.Any(x => x.AllowedAccess == v))
.Select(c => new GroupedObject {Name = c.Name, List = c.List.Where(l => l.AllowedAccess == v)}));
var allowedResults = result[true];
var restrictedResults = result[false];
Or this:
var result = s.Results
.SelectMany(c => c.List, (b, c) => new {b.Name, DObj = c})
.GroupBy(g => g.DObj.AllowedAccess)
.ToDictionary(k=> k.Key,
c =>
new {
c.Key,
List =
c.GroupBy(cg => cg.Name)
.Select(
x => new GroupedObject {Name = x.Key, List = x.Select(l => l.DObj).ToList()})
.ToList()
});

Related

LINQ Lambda to form a Nested Object/Collection from a Flat List, it's not hierarchical but to group similar data

Note: I don't have a tree with parent & child relation instead trying to group similar code & store together and form nested objects/collection
I have a scenario where I have data flattened & wanted to form nested collection based on code & store.
public class Item
{
public int Code { get; set; }
public string Price { get; set; }
public int Store { get; set; }
public string PriceType { get; set; }
}
public class PriceDto
{
public string PriceType { get; set; }
public string Price { get; set; }
}
public class ItemVm
{
public int Code { get; set; }
public int Store { get; set; }
public List<PriceDto> SalePrice { get; set; }
public List<PriceDto> PermPrice { get; set; }
}
public static class IEnumerableExtension
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
}
I have below code to form nested collection from flattened list.
IList<Item> items = new List<Item>() {
new Item() { Code = 1, Price = "3.83", Store = 1202, PriceType = "Sale"} ,
new Item() { Code = 1, Price = "4.10", Store = 1202, PriceType = "Perm"} ,
new Item() { Code = 2, Price = "3.00", Store = 1315, PriceType = "Perm"} ,
new Item() { Code = 3, Price = "1.99" , Store = 1420, PriceType = "Sale"} ,
new Item() { Code = 3, Price = "2.25" , Store = 1420, PriceType = "Perm" }
};
//Distinct Code, Store & then form nested collection by loop
var itemsList = items.Select(
i => new ItemVm { Code = i.Code, Store = i.Store, }
).DistinctBy(d => new { d.Code,d.Store }).ToList();
foreach( var i in itemsList)
{
i.PermPrice = items.Where(x=> x.Code == i.Code && x.Store == i.Store && x.PriceType == "Perm").Select(d => new PriceDto
{ Price = d.Price, PriceType = d.PriceType }).ToList();
i.SalePrice = items.Where(x=> x.Code == i.Code && x.Store == i.Store && x.PriceType == "Sale").Select(d => new PriceDto
{ Price = d.Price, PriceType = d.PriceType }).ToList();
}
Is there a better way than this to form the nested collection in single LINQ query ?
Any suggestions.
You have to use GroupBy
var itemsList = items
.GroupBy(d => new { d.Code, d.Store })
.Select(g => new ItemVm
{
Code = g.Key.Code,
Store = g.Key.Store,
PermPrice = g.Where(x => x.PriceType == "Perm")
.Select(d => new PriceDto { Price = d.Price, PriceType = d.PriceType })
.ToList(),
SalePrice = g.Where(x => x.PriceType == "Sale")
.Select(d => new PriceDto { Price = d.Price, PriceType = d.PriceType })
.ToList()
})
.ToList();

Linq with expect: Only primitive types or enumeration types are supported in this context

I have this exeption: Unable to create a constant value of type ....ViewModels.Yarn.FilterNameDto'. Only primitive types or enumeration types are supported in this context.
ViewModels:
public class YarnListViewModel
{
public YarnListFilter YarnListFilter { get; set; }
public IEnumerable<FilterNameDto> FilterNames { get; set; }
}
public class YarnListFilter
{
public int? BrandId { get; set; }
public string ProductName { get; set; }
}
public class FilterNameDto
{
public string ProductName { get; set; }
}
In Contoller:
List<FilterNameDto> nlnames = new List<FilterNameDto>
{
new FilterNameDto { ProductName = "Farouche" },
new FilterNameDto { ProductName = "La Parisienne" },
...
};
var filternamesdb = _context.YarnFullAdmins
.Where(n => n.ProductName != null)
.GroupBy(n => n.ProductName)
.Select(n => n.FirstOrDefault());
if (yarnListFilter.BrandId > 0)
filternamesdb = filternamesdb.Where(b => b.BrandId == yarnListFilter.BrandId);
// Until here everything works fine
var filternamesdblist = filternamesdb.Select(n => new FilterNameDto
{
ProductName = n.ProductName,
}).Except(nlnames).ToList(); // I remove the names who are in the nlnames list
nlnames.AddRange(filternamesdblist); // And I add them so they come out first
var filternames = filternamesdblist;
if (yarnListFilter.BrandId == null || yarnListFilter.BrandId == 1)
filternames = nlnames;
var viewModel = new YarnListViewModel
{
FilterNames = filternames
};
return View(viewModel);
.Exept is my problem!
View:
#Html.DropDownListFor(f => f.YarnListFilter.ProductName
, new SelectList(Model.FilterNames, "ProductName", "ProductName")
,"Filter by Name"
, new { #class = "form-control", #onchange = "this.form.submit();" })
My goal is to have some of the items (referenced in nlnames List) who are actually in the query result (everywhere in this list) to come out first. So, I thought I remove them from the list and then add them so they are first listed.
Or is there (and I'm sure there is) a much better way to achieve this?!?!?
To resume it clear and short:
The database returns Aaa, Bbb, Ccc, Ddd, Eee
And I want Bbb, Ddd to be first!
Thanks in advance for your help.
The problem is your object nlnames can't be translated into something which SQL Server understands. In order to get around this you could call .ToList() before the .Except()
var filternamesdblist = filternamesdb
.Select(n => new FilterNameDto
{
ProductName = n.ProductName,
})
.ToList()
.Where(n => !nlnames.Any(nl => nl.ProductName == n.ProductName))
.ToList();
Alternatively, you could change the type of nlnames to List<string> which can be understood by SQL Server:
var nlnames = new List<string> { "Farouche", "La Parisienne" };
var filternamesdblist = filternamesdb
.Where(n => !nlnames.Contains(n.ProductName))
.Select(n => new FilterNameDto
{
ProductName = n.ProductName,
})
.ToList();

C# Nest, Elastic Search: update and add to a field that is a list

This may be a bit of a dumb question but I'm new to elastic search and nest.
I have a class
Person
{
public string Id {get; set;}
public string Name {get; set;}
public IList<string> PhoneNumbers {get; set;}
}
And what I want to do is update the Phone number by adding to it.
Right now I'm doing it with 2 queries but I'm wondering if there is a way I could manage it with 1.
// See if the Person already exists
var result = NestClient.Search<Person>(s => s
.Index(_indexName)
.Take(1)
.Query(q => q
.Term(p => p.Name, person.Name)
&& q.Term(p => p.Id, person.Id)));
if (result.ServerError != null)
{
throw result.OriginalException;
}
if (result.Documents.FirstOrDefault() == null)
{
var response = NestClient.Index<Person>(person);
if (response.ServerError != null)
{
throw response.OriginalException;
}
}
else
{
// If it does exist update and overwrite
var savedPerson = result.Documents.First();
IList<string> oldNums = SavedPerson.PhoneNumbers;
IList<string> newNums = newPerson.PhoneNumbers;
var combinedNums = oldNums.Concat(newNums);
newPerson.PhoneNumbers = combinedNums.ToList<string>();
var response = NestClient.Update(DocumentPath<Person>
.Id(newPerson.Id),
u => u.Doc(newPerson).DocAsUpsert(true));
if (response.ServerError != null)
{
throw response.OriginalException;
}
}
Basically I want my upsert to add to the existing list of phone numbers if it exists.
If script is an option you can do this with scripted update.
Option with duplicated items after update in array
var updateResponse = client.Update<Document, DocumentPartial>(DocumentPath<Document>.Id(1), descriptor => descriptor
.Script(#"ctx._source.array += tab;")
.Params(p => p.Add("tab", new[] {4, 5, 3})));
and without
var updateResponse = client.Update<Document, DocumentPartial>(DocumentPath<Document>.Id(1), descriptor => descriptor
.Script(#"ctx._source.array += tab; ctx._source.array.unique();")
.Params(p => p.Add("tab", new[] {4, 5, 3})));
Full example:
public class DocumentPartial
{
public int[] Array { get; set; }
}
public class Document
{
public int Id { get; set; }
public int[] Array { get; set; }
}
var client = new ElasticClient(settings);
client.CreateIndex(indexName, descriptor => descriptor
.Mappings(map => map
.Map<Document>(m => m.AutoMap())));
var items = new List<Document>
{
new Document
{
Id = 1,
Array = new[] {1,2,3}
}
};
var bulkResponse = client.IndexMany(items);
client.Refresh(indexName);
var updateResponse = client.Update<Document, DocumentPartial>(DocumentPath<Document>.Id(1), descriptor => descriptor
.Script(#"ctx._source.array += tab; ctx._source.array.unique();")
.Params(p => p.Add("tab", new[] {4, 5, 3})));
Hope it helps.

String Join Using a Lambda Expression

If I have a list of some class like this:
class Info {
public string Name { get; set; }
public int Count { get; set; }
}
List<Info> newInfo = new List<Info>()
{
{new Info { Name = "ONE", Count = 1 }},
{new Info { Name = "TWO", Count = 2 }},
{new Info { Name = "SIX", Count = 6 }}
};
Can a Lambda expression be used to string join the attributes in the list of classes like this:
"ONE(1), TWO(2), SIX(6)"
string.Join(", ", newInfo.Select(i => string.Format("{0}({1})", i.Name, i.Count)))
You could also override ToString.
class Info
{
....
public override ToString()
{
return string.Format("{0}({1})", Name, Count);
}
}
... and then the call is dead simple (.Net 4.0):
string.Join(", ", newInfo);
String.Join(", ", newInfo.Select(i=>i.Name+"("+i.Count+")") );
You Can use as like following
You can Return a specific type like this
Patient pt = dc.Patients.Join(dc.PatientDetails, pm => pm.PatientId, pd => pd.PatientId,
(pm, pd) => new
{
pmm = pm,
pdd = pd
})
.Where(i => i.pmm.PatientCode == patientCode && i.pmm.IsActive || i.pdd.Mobile.Contains(patientCode))
.Select(s => new Patient
{
PatientId = s.pmm.PatientId,
PatientCode = s.pmm.PatientCode,
DateOfBirth = s.pmm.DateOfBirth,
IsActive = s.pmm.IsActive,
UpdatedOn = s.pmm.UpdatedOn,
UpdatedBy = s.pmm.UpdatedBy,
CreatedOn = s.pmm.CreatedOn,
CreatedBy = s.pmm.CreatedBy
})
Or You can retrieve anonymous type like this
var patientDetails = dc.Patients.Join(dc.PatientDetails, pm => pm.PatientId, pd => pd.PatientId,
(pm, pd) => new
{
pmm = pm,
pdd = pd
})
.Where(i => i.pmm.PatientCode == patientCode && i.pmm.IsActive || i.pdd.Mobile.Contains(patientCode))
.Select(s => new
{
PatientId = s.pmm.PatientId,
PatientCode = s.pmm.PatientCode,
DateOfBirth = s.pmm.DateOfBirth,
IsActive = s.pmm.IsActive,
PatientMobile = s.pdd.Mobile,
s.pdd.Email,
s.pdd.District,
s.pdd.Age,
s.pdd.SittingId
})

LINQ Lambda Group By with Sum

Hi I can do this in method syntax but I'm trying to improve my lambda skills how can I do:
SELECT SUM([job_group_quota]) as 'SUM'
FROM [dbo].[tbl_job_session]
WHERE [job_group_job_number] = #jobnum
and [job_group_ID] like #sess
GROUP BY [job_group_job_number]
I've been messing around with it but can't get it right.
lnq.tbl_job_sessions.GroupBy(a => a.job_group_job_number == jnum)
.Select(b => new { b.job_group_quota}).Sum();
A general example:
query
.GroupBy(item => item.GroupKey)
.Select(group => group.Sum(item => item.Aggregate));
Few Group by Examples
public void GroupBy1()
{
var personList = dbEntities.People.GroupBy(m => m.PersonType).Select(m => new { PersonType = m.Key, Count = m.Count() });
}
public void GroupBy2()
{
var personList = dbEntities.People.GroupBy(m => new { m.PersonType, m.FirstName }).Select(m => new { PersonType = m.Key, Count = m.Count() });
}
public void GroupBy3()
{
var personList = dbEntities.People.Where(m => m.EmailPromotion != 0).GroupBy(m => new { m.PersonType, m.FirstName }).Select(m => new { PersonType = m.Key, Count = m.Count() });
}
public void GroupBy4()
{
var personList = dbEntities.People.GroupBy(m => new { m.PersonType, m.FirstName }).Where(m => m.Count() > 70).Select(m => new { PersonType = m.Key, Count = m.Count() });
}
public void GroupBy5()
{
var personList = dbEntities.People
.GroupBy(m =>
new
{
m.PersonType
}).Where(m => m.Count() > 70)
.Select(m =>
new
{
PersonType = m.Key,
Count = m.Count()
});
var list1 = dbEntities.People.
GroupBy(m => new { m.PersonType }).
Select(m =>
new
{
Type = m.Key,
Count = m.Count()
})
.Where(
m => m.Count > 70
&& m.Type.PersonType.Equals("EM")
|| m.Type.PersonType.Equals("GC"));
}
public void GroupBy6()
{
var list1 = dbEntities.People.
GroupBy(m => new { m.PersonType, m.EmailPromotion }).Select(m =>
new
{
Type = m.Key,
Count = m.Count()
})
.Where
(
m => m.Count > 70 && m.Type.EmailPromotion.Equals(0) &&
(
m.Type.PersonType.Equals("EM") ||
m.Type.PersonType.Equals("GC")
));
}
public void GroupBy7()
{
var list1 = dbEntities.People.
GroupBy(m => m.PersonType).
Select(c =>
new
{
Type = c.Key,
Total = c.Sum(p => p.BusinessEntityID)
});
}
public void GroupBy8()
{
var list1 = dbEntities.People.
GroupBy(m => m.PersonType).
Select(c =>
new
{
Type = c.Key,
Count = c.Count(),
Total = c.Sum(p => p.BusinessEntityID)
});
}
public void GroupBy9()
{
var list1 = dbEntities.People.
GroupBy(m => m.PersonType).
Select(c =>
new
{
Type = c.Key,
Max = c.Max(),
});
}
If you want to get a key-to-sum Dictionary result.
var allJobQuota = jobSessions.GroupBy(s => s.jobNumber)
.ToDictionary(g => g.Key, g => g.Sum(s => s.quota));
This example shows how to iterate the grouped values getting the key and totals, and how to get totals directly (like previous). Both using only lambda operator.
using System;
using System.Collections.Generic;
using System.Linq;
public class Person
{
public string Name { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
public int SomeValue { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Person> data = GetPopulatedData();
var totals = data.GroupBy(x =>
new { x.Name, x.City, x.ZipCode }).Select(y =>
y.Sum(i => i.SomeValue));
var groupsForIterate = data.GroupBy(x =>
new { x.Name, x.City, x.ZipCode });
Console.WriteLine("Totals: ");
foreach (var total in totals)
{
Console.WriteLine(total);
}
Console.WriteLine("Categories: ");
foreach (var categ in groupsForIterate)
{
// You can refer to one field like this: categ.Key.Ciduad
Console.WriteLine("Group" + categ.Key);
Console.WriteLine(categ.Sum(x => x.SomeValue));
}
//Output:
//Totals:
//1
//2
//1
//Categories:
//Group{ Name = Mark, City = BCN, ZipCode = 00000 }
//1
//Group{ Name = Mark, City = BCN, ZipCode = 000000 }
//2
//Group{ Name = John, City = NYC, ZipCode = 000000 }
//1
}
private static List<Person> GetPopulatedData()
{
List<Person> datos = new List<Person>()
{
new Person(){Name="Mark", City = "BCN",
ZipCode = "00000", SomeValue = 1}, // group A
new Person(){Name="Mark", City = "BCN",
ZipCode = "000000", SomeValue = 1}, // group B
new Person(){Name="Mark", City = "BCN",
ZipCode = "000000", SomeValue = 1}, // group B
new Person(){Name="John", City = "NYC",
ZipCode = "000000", SomeValue = 1}, // group C
};
return datos;
}
}
Sum Ficha_Venda and Entrada from Movimento:
var query = from bd in db.Movimento
where (movimento.Data != null ? bd.Data == movimento.Data : bd.Data == movimento.Data)
&& (bd.Loja == Loja)
group bd by bd.Data into t
select new {entrada = t.Sum(bd=> bd.Entrada), ficha = t.Sum(bd=> bd.Ficha_Venda)};

Categories

Resources