I have method that gets data from Db with nested collection
Here is model
public class FilterOptionDto
{
public Guid Id { get; set; }
public string FilterName { get; set; }
public ICollection<OptionDto> Options { get; set; }
}
Here is method that get's data
public async Task<List<FilterOptionDto>?> SetFilterOptions(SetFilterOptionsInputDto input)
{
var useCase = await _dbContext.UseCases.FirstOrDefaultAsync(x => x.Id == input.UseCaseId);
var dimensionsConfiguration =
_analysisDimensionsProvider.AnalysisDimensionsSetting.FirstOrDefault(x => x.FieldNameDomain == input.Name);
if (dimensionsConfiguration != null)
{
var filters = _mapper.Map<List<FilterOptionDto>>(await _dbContext.VwAnalysisUseCaseFilters.Where(x =>
x.JobId == useCase!.JobId && x.IsUseCaseFilter == true && x.FilterType == "multiselect" &&
x.IsAvailable && x.FieldNameDomain == input.Name)
.ToListAsync());
foreach (FilterOptionDto item in filters)
{
item.Options = await GetOptions(useCase?.JobId, dimensionsConfiguration.Source,
dimensionsConfiguration.FieldNameDomain);
}
if (!string.IsNullOrEmpty(input.SearchInput))
{
filters = filters.Where(x => x.Options.Any(x => x.Value.Contains(input.SearchInput))).ToList();
}
// filters = filters.Where(x => x.FilterName.Contains(input.Name)).ToList()
// .GetRange(input.Offset, input.Offset + 3);
return filters;
}
return null;
}
on this line I need to get range on nested collection Options from model, how I can do this?
// filters = filters.********
// .GetRange(input.Offset, input.Offset + 3);
You can chain Select, Skip, and Take.
filters.Select(x => x.Options.Skip(input.Offset).Take(3));
Related
I have a below method where I am loop through the list of id's and getting the data from db based on id and then creating the material and then adding to material list
public Construction AddToOsm(Model model, APIDbContext dbContext)
{
var construction = new Construction(model);
var surfaceType = dbContext.IntendedSurfaceTypes.SingleOrDefault(s => s.Id == this.SurfaceTypeId);
construction.setName(surfaceType?.Name);
using var materials = new MaterialVector();
var fenestrationMaterialById = new Dictionary<Guid, FenestrationMaterial>();
var opaqueMaterialById = new Dictionary<Guid, StandardOpaqueMaterial>();
foreach (var materialId in this.LayerIds.Where(i => i != default))
{
var opaqueMaterial = dbContext.OpaqueMaterials.SingleOrDefault(o => o.Id == materialId);
if (opaqueMaterial != default)
{
materials.Add(opaqueMaterialById.GetOrCreate(opaqueMaterial.Id, () => opaqueMaterial.AddToOsm(model)));
}
else
{
var glazingMaterial = dbContext.GlazingMaterials.SingleOrDefault(o => o.Id == materialId);
if (glazingMaterial != default)
{
materials.Add(fenestrationMaterialById.GetOrCreate(glazingMaterial.Id, () => glazingMaterial.AddToOsm(model)));
}
else
{
var glazingSimpleMaterial = dbContext.SimpleGlazingMaterials.SingleOrDefault(s => s.Id == materialId);
if(glazingSimpleMaterial != default)
{
materials.Add(fenestrationMaterialById.GetOrCreate(glazingSimpleMaterial.Id, () => glazingSimpleMaterial.AddToOsm(model)));
}
else
{
var gasGlazingMaterials = dbContext.GasGlazingMaterials.SingleOrDefault(a => a.Id == materialId);
if(gasGlazingMaterials != default)
{
materials.Add(fenestrationMaterialById.GetOrCreate(gasGlazingMaterials.Id, () => gasGlazingMaterials.AddToOsm(model)));
}
}
}
}
}
construction.setLayers(materials);
return construction;
}
I am looking a way to avoid this much of if-else statements mainly refactoring this but could not find a way to do. Could any one please suggest any idea on how to achieve the same.
Thanks in advance.
update: sample entity structure
public class GasGlazingMaterial : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("SourceOfData")]
public Guid? SourceOfDataId { get; set; }
public virtual CodeStandardGuideline SourceOfData { get; set; }
......
.....
}
A simple fix would be to "continue" after each materials.add. This would mean you dont need to embed the rest in an else
How can I convert expression tree to Dictionary?
For example:
class Dummy
{
public int Id { get; set; }
public string Name { get; set; }
}
Example 1:
MyStaticClass.ParseExpression<Dummy>(t => t.Id == 2)
//Result is dictionary with item:
<key, value>Id,2
Example 2:
var s = "Foo";
MyStaticClass.ParseExpression<Dummy>(t => t.Id == 2 && t.Name == s)
//Result is dictionary with items:
<key, value>Id,2
<key, value>Name,"Foo"
I know EF Core does this, but don't know how, and source code is to complicated for me to parse it.
I should say expression doesn't contain || and ().
For example:
MyStaticClass.ParseExpression<Dummy>(t => t.Id == 2 || t.Id == 3)
or
MyStaticClass.ParseExpression<Dummy>(t => t.Id == 2 && (Name == "Foo" || Id Name == "Test")
If you are sure that expressions will be in provided format only - you can do something like this:
public class Dummy
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class ExpressionConverter
{
public static Dictionary<string, string> Convert<T>(Expression<Func<T,bool>> expression)
{
var result = new Dictionary<string,string>();
var current = (BinaryExpression)expression.Body;
while (current.NodeType != ExpressionType.Equal)
{
ParseEquals((BinaryExpression)current.Right);
current = (BinaryExpression)current.Left;
}
ParseEquals(current);
void ParseEquals(BinaryExpression e)
{
var key = (MemberExpression) e.Left;
var value = (ConstantExpression) e.Right;
result.Add(key.Member.Name, value.Value.ToString());
}
return result;
}
}
Usage:
var test = ExpressionConverter.Convert<Dummy>(x => x.Id == 5 && x.Name == "dummy" && x.Age == 11);
Or replace ParseEquals:
void ParseEquals(BinaryExpression e)
{
var key = (MemberExpression) e.Left;
object value;
switch (e.Right)
{
case ConstantExpression constantExpression:
value = constantExpression.Value;
break;
case MemberExpression memberExpression:
var obj = ((ConstantExpression)memberExpression.Expression).Value;
value = obj.GetType().GetField(memberExpression.Member.Name).GetValue(obj);
break;
default:
throw new UnknownSwitchValueException(e.Right.Type);
}
result.Add(key.Member.Name, value);
}
To support:
var myVar = "dummy";
var test = ExpressionConverter.Convert<Dummy>(x => x.Id == 5 && x.Name == myVar && x.Age == 11);
I have a view model which contains a List<>. This list is a collection of another model and I'm trying to fill this list while filling an IEnumerable of my view model. While doing this I get the error “Only parameterless constructors and initializers are supported in LINQ to Entities”. The error came to be because of the Locations = new List<> part in which I try to fill the list. What I would like to know is how to fill this list the correct way.
Code:
IEnumerable<PickListLineViewModel> lineList = dbEntity.PickListLine
.Where(i => i.PickID == id && i.Status != "C")
.Select(listline => new PickListLineViewModel
{
ArticleName = dbEntity.Item
.Where(i => i.ItemId == dbEntity.SalesOrderLine
.Where(idb => idb.DocId == listline.BaseDocID &&
idb.DocType.Equals(listline.BaseDocType) &&
idb.LineNum == listline.BaseLineNum)
.Select(iid => iid.ItemId)
.FirstOrDefault())
.Select(p => p.Description)
.FirstOrDefault(),
PickID = listline.PickID,
BaseDocID = listline.BaseDocID,
BaseDocType = listline.BaseDocType,
BaseLineNum = listline.BaseLineNum,
LineNum = listline.LineNum,
Quantity = listline.Quantity,
ReleasedByQty = listline.ReleasedByQty,
Status = listline.Status,
PickedQuantity = listline.PickedQuantity,
Locations = new List<BinLocationItemModel>(dbEntity.BinLocation_Item
.Where(t => t.ItemId == dbEntity.SalesOrderLine
.Where(idb => idb.DocId == listline.BaseDocID &&
idb.DocType.Equals(listline.BaseDocType) &&
idb.LineNum == listline.BaseLineNum)
.Select(iid => iid.ItemId)
.FirstOrDefault())
.Select(locitem => new BinLocationItemModel
{
ItemId = locitem.ItemId,
Barcode = locitem.BinLocation.Barcode,
BinLocationCode = locitem.BinLocation.BinLocationCode,
BinLocationId = locitem.BinLocationId,
BinLocationItemId = locitem.ItemId,
StockAvailable = locitem.StockAvailable
}))
.ToList(),
ArticleID = dbEntity.Item
.Where(i => i.ItemId == dbEntity.SalesOrderLine
.Where(idb => idb.DocId == listline.BaseDocID &&
idb.DocType.Equals(listline.BaseDocType) &&
idb.LineNum == listline.BaseLineNum)
.Select(iid => iid.ItemId)
.FirstOrDefault())
.Select(p => p.ItemCode)
.FirstOrDefault()
})
.AsEnumerable();
BinLocationItemModel:
public class BinLocationItemModel
{
[Required]
public int BinLocationItemId { get; set; }
public string Barcode { get; set; }
public string BinLocationCode { get; set; }
[Required]
public int BinLocationId { get; set; }
[Required]
public int ItemId { get; set; }
public decimal? StockAvailable { get; set; }
}
In your PickListLineViewModel constructor you should initialise your Locations list like below
public class PickListLineViewModel
{
public PickListLineViewModel()
{
Locations = new List<BinLocationItemModel>();
}
public List <BinLocationItemModel> Locations { get; set; }
}
public class BinLocationItemModel
{
....
}
Then for your linq query you should be able to do the following, you wont need your new list inside the linq:
Locations = dbEntity.BinLocation_Item
.Where(t => t.ItemId == dbEntity.SalesOrderLine
.Where(idb => idb.DocId == listline.BaseDocID &&
idb.DocType.Equals(listline.BaseDocType) &&
idb.LineNum == listline.BaseLineNum)
.Select(iid => iid.ItemId)
.FirstOrDefault())
.Select(locitem => new BinLocationItemModel
{
ItemId = locitem.ItemId,
Barcode = locitem.BinLocation.Barcode,
BinLocationCode = locitem.BinLocation.BinLocationCode,
BinLocationId = locitem.BinLocationId,
BinLocationItemId = locitem.ItemId,
StockAvailable = locitem.StockAvailable
})
.ToList(),
I have a very large data set. In order to optimize query performance I'm making queries based on which filters a user selected.
using (_db)
{
if (string.IsNullOrEmpty(CompanyID))
{
if (string.IsNullOrEmpty(HealthplanCode))
{
foreach (string x in _db.BM_OPT_MASTER.Select(y => y.OPT).Distinct())
{
currentComboBox.Items.Add(x);
}
}
else
{
foreach (string x in _db.BM_OPT_MASTER.Where(y => y.HPCODE == HealthplanCode).Select(y => y.OPT).Distinct())
{
currentComboBox.Items.Add(x);
}
}
}
else
{
if (string.IsNullOrEmpty(HealthplanCode))
{
foreach (string x in _db.BM_OPT_MASTER.Where(y => y.COMPANY_ID == CompanyID).Select(y => y.OPT).Distinct())
{
currentComboBox.Items.Add(x);
}
}
else
{
foreach (string x in _db.BM_OPT_MASTER.Where(y => y.COMPANY_ID == CompanyID && y.HPCODE == HealthplanCode).Select(y => y.OPT).Distinct())
{
currentComboBox.Items.Add(x);
}
}
}
}
As you can see this can get pretty annoying as more and more filter options are added. Is there a way to refactor this code in such a manner that the query is still optimized without relying on nested if else statements?
You could define a custom class with "query options" that you could fill out with whatever parameters you want. It would look something like this:
public class QueryFilterOptions
{
public string CompanyID { get; set; }
public string HealthplanCode { get; set; }
public string SomeOtherQueryOption { get; set; }
}
This can then be used as such (I was unsure what type your items in BM_OPT_MASTER are so I just took that):
public void AddItems(QueryFilterOptions options = null) {
using (_db)
{
if (options == null) {
options = new QueryFilterOptions();
}
var items = _db.BM_OPT_MASTER;
items = FilterOnCompanyID(items, options.CompanyID);
items = FilterOnHealthPlanCode(items, options.HealthplanCode);
items = FilterOnSomeOtherQueryOption(items, options.SomeOtherQueryOption);
//...other filters
items = items.Select(y => y.OPT).Distinct();
foreach (var item in items)
{
currentComboBox.Items.Add(item);
}
}
}
private IQueryable<BM_OPT_MASTER> FilterOnCompanyID(IQueryable<BM_OPT_MASTER> items, string companyID)
{
if (!(string.IsNullOrEmpty(companyID)))
{
items = items.Where(y => y.COMPANY_ID == companyID);
}
return items;
}
private IQueryable<BM_OPT_MASTER> FilterOnHealthPlanCode(IQueryable<BM_OPT_MASTER> items, string healthplanCode)
{
if (!(string.IsNullOrEmpty(healthplanCode)))
{
items = items.Where(y => y.HPCODE == healthplanCode);
}
return items;
}
private IQueryable<BM_OPT_MASTER> FilterOnSomeOtherQueryOption(IQueryable<BM_OPT_MASTER> items, string someOtherQueryOption)
{
if (!(string.IsNullOrEmpty(someOtherQueryOption)))
{
items = items.Where(y => y.SOME_OTHER_QUERY_OPTION == someOtherQueryOption);
}
return items;
}
You would just call the AddItems functions with whatever filter values you have, for example:
AddItems(new QueryFilterOptions
{
CompanyID = "SOME-COMPANY-ID"
});
or
AddItems(new QueryFilterOptions
{
HealthplanCode = "SOME-HEALTHPLAN-CODE",
SomeOtherQueryOption = "SOME-OTHER-QUERY-OPTION"
});
To add new query filter options, you just add a new field to the QueryFilterOptions class and add a FilterOn... method.
I have this list that I am checking and then creating another list, where the definition is not equal to null.
var new = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
public class WebWordForm
{
public string definition { get; set; }
public string partOfSpeech { get; set; }
public int sourceId { get; set; }
public List<string> synonyms { get; set; }
public List<string> typeOf { get; set; }
public List<string> hasTypes { get; set; }
public List<string> derivation { get; set; }
public List<string> examples { get; set; }
}
Is there a simple way that I could also set the sourceId in my rootObject.webWordForms list to the value of 2?
var new = rootObject.webWordForms
.Where(w => w.definition != null)
.Select(w => new WebWordForm{
definition = w.definition,
partOfSpeech = w.partOfSpeech,
sourceId = 2,
synonyms= w.synonyms,
typeOf = w.typeOf,
hasTypes = w.hasTypes,
derivation = w.derivation,
examples = w.examples
}).ToList();
You could use List ForEach method to do this, but please mind this is nothing different than looping.
var list = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
list.ForEach(x=> x.sourceId =2);
Use .ForEach() on new, this would iterate on list new and update the param.
var finalList = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
finalList.ForEach(n=>n.sourceId = 2);
Note - If the queried final list is your return you need to do above operations before returning anything.
While I am suggesting ForEach(), many articles focus on avoiding it.
An alternative,
var finalList = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
finalList.All(n=>{
n.sourceId = 2;
return true;
});