Mapping between models efficiently - c#

I'm a bit new to asp.net core. In this query, it keeps on requerying the db on every node to map from OrgStructures to ToOrgStructureModel is there a way we can make this more efficient:
This is the area where it keeps on requerying the db: .Select(org => org.ToOrgStructureModel(db.OrgStructures.Where(s => s.ParentNodeId == org.NodeId).Count() > 0))
Whole query:
public virtual IList<OrgStructureModel> GetAll()
{
using (var db = _context)
{
var result = db.OrgStructures
.Where(e => e.FiscalYear == 19)
.Select(org => org.ToOrgStructureModel(db.OrgStructures.Where(s => s.ParentNodeId == org.NodeId).Count() > 0))
.ToList();
_session.SetObjectAsJson("OrgStructure", result);
return result;
}
}
ToOrgStructureModel:
public static OrgStructureModel ToOrgStructureModel(this OrgStructure org, bool hasChildren)
{
return new OrgStructureModel
{
NodeId = org.NodeId,
ParentNodeId = org.ParentNodeId,
Name = org.Name,
DepartmentCode = org.DepartmentCode,
Acronym = org.Acronym,
LegacyId = org.LegacyId,
hasChildren = hasChildren
};
}
OrgStructureModel:
public class OrgStructureModel
{
[ScaffoldColumn(false)]
public int? NodeId { get; set; }
[Required]
public string Name { get; set; }
public string Acronym { get; set; }
public string DepartmentCode { get; set; }
public int? ParentNodeId { get; set; }
public int? LegacyId { get; set; }
public int FiscalYear { get; set; }
public int DepartmentId { get; set; }
[ScaffoldColumn(false)]
public bool hasChildren { get; set; }
public OrgStructure ToEntity()
{
return new OrgStructure
{
NodeId = NodeId,
Name = Name,
Acronym = Acronym,
ParentNodeId = ParentNodeId,
DepartmentCode = DepartmentCode,
LegacyId = LegacyId,
FiscalYear = FiscalYear,
DepartmentId = DepartmentId
};
}
}

Avoid using custom methods when using Linq-to-sql.
Here's a working alternative that doesn't use ToOrgStructureModel method:
var result = db.OrgStructures
.Where(e => e.FiscalYear == 19)
.Select(org => new OrgStructureModel
{
NodeId = org.NodeId,
ParentNodeId = org.ParentNodeId,
Name = org.Name,
DepartmentCode = org.DepartmentCode,
Acronym = org.Acronym,
LegacyId = org.LegacyId,
// Notice using "Any" method instead of comparing count with 0
hasChildren = db.OrgStructures.Any(s => s.ParentNodeId == org.NodeId),
})
.ToList();

You are creating a lot of queries, essentially for every record that it will pull out it will query one more time for each of them to check for hasChildren.
Include the link to the child in your main model (if it's a collection make it a collection),
public class OrgStructureModel
{
...
public int? ChildId {get;set;}
public OrgStructureModel Child {get;set;}
}
And then you can create a check in the query
var result = db.OrgStructures
.Where(e => e.FiscalYear == 19 && e.ChildId != null)
.Select(org => org.ToOrgStructureModel())
.ToList();
Also read this blog post on projection.

Related

C# web api data filtration

Good day,
I am doing Web api rest project and want to include product search for products by size and color, but I want to be able search for example:
1 One size
[httpGet][Route("oneSize/{sizeID}")]
2 Two Sizes
[httpGet][Route("TwoSizes/{sizeID1}/{sizeID2}")]
3 One size/ One color
[httpGet][Route("OneSizeOneColor/{sizeID1}/{ColorID}")]
4 Two sizes/ One color
[httpGet][Route("TwoSizeOneColor/{sizeID1}/{sizeID2}/{ColorID}")]
etc.
Do I need to create end point for every tipe of search or is there a smarter way of doing it?
You should use the query params. You can add them via FromQuery attribute:
[HttpGet]
public IActionResult SearchProducts([FromQuery] int[] sizeIds, [FromQuery] int[] colorIds) {
}
You can replace int with string if you have string Ids.
For example, if you want to make a request with sizes 1 and 2, and color 3 and 4, the request would look like this: https://localhost:5001/your-endpoint-name?sizeIds=1&sizeIds=2&colorIds=3&colorIds=4
So query is a list of key=value url parameters after the ? separated by & sign
EDIT
You can easily query the database with the sql IN operator.
In EF Core, it would look something like this:
IQuaryable<Product> query = dbContext.Products;
if (sizeIds.Length > 0) {
query= query.Where(p => sizeIds.Contains(p.SizeId));
}
if (colorIds.Length > 0) {
query= query.Where(p => colorIds.Contains(p.ColorId));
}
List<Product> result = await query.ToListAsync();
It would be translated to the following SQL:
SELECT * FROM Products
WHERE Products.SizeId IN (1, 2) AND Products.ColorId IN (3, 4);
The problem is that I have nested classess
public class ProductBase
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<ProductVariant> Variants { get; set; } = new List<ProductVariant>();
public int BaseImageId { get; set; } = 0;
public string BaseImagePath { get; set; } = string.Empty;
}
public class ProductVariant
{
public int Id { get; set; }
public int Quantity { get; set; }
public int ProductBaseId { get; set; }
public int ProductSizeId { get; set; }
public ProductSize ProductSize { get; set; }
public int ProductColorId { get; set; }
public ProductColor ProductColor { get; set; }
public IEnumerable<ImageVariant> imageVariants { get; set; } = new List<ImageVariant>();
}
public class ProductSize
{
public int Id { get; set; }
public string Size { get; set; }
}
public class ProductColor
{
public int Id { get; set; }
public string Color { get; set; }
}
I am trying something like this
public async Task<IQueryable<Models.ProductBase>> SearchProducts(int[] SizeIds, int[] ColorIds )
{
IQueryable<Models.ProductBase> query = _dataContext.ProductBases
.Include(pb => pb.Variants).ThenInclude(v => v.ProductSize)
.Include(pb => pb.Variants).ThenInclude(v => v.ProductColor)
.Include(pb => pb.Variants).ThenInclude(v => v.imageVariants);
if(SizeIds.Length > 0)
{
query = query.Where(pb => SizeIds.Contains(pb.Variants.Any(pb.Variants.doesnotWork));
}
if(ColorIds.Length > 0)
{
query = query.Where(pb => ColorIds.Contains(pb.Variants.Contains(pb.Variants.doesNotWork)));
}
List<ProductBase> result = await query.ToListAsync();
}
Fixed , this is working now
public async Task<IEnumerable<Models.ProductBase>> SearchProducts(int[] SizeIds, int[] ColorIds )
{
IQueryable<Models.ProductBase> query = _dataContext.ProductBases
.Include(pb => pb.Variants).ThenInclude(v => v.ProductSize)
.Include(pb => pb.Variants).ThenInclude(v => v.ProductColor)
.Include(pb => pb.Variants).ThenInclude(v => v.imageVariants);
if(SizeIds.Length > 0)
{
query = query.Where(pb => pb.Variants.ToList().Any(v => SizeIds.Contains(v.ProductSizeId)));
}
if(ColorIds.Length > 0)
{
query = query.Where(pb => pb.Variants.ToList().Any(v => ColorIds.Contains(v.ProductColorId)));
}
List<Models.ProductBase> result = await query.ToListAsync();
return result;
}

Filter data from 2 lists with diferent models C#

I have this models
public class RoutingAttributeModel
{
public int Bus_No { get; set; }
public int Attribute_No { get; set; }
public string Attribute_Name { get; set; }
public string Status { get; set; }
public string Notes { get; set; }
}
public class AgentRoutingAttributeModel
{
public int Agent_No { get; set; }
public int Bus_No { get; set; }
public int Attribute_No { get; set; }
public string Attribute_Name { get; set; }
public string Status { get; set; }
}
List<RoutingAttributeModel> lstComplete = new List<RoutingAttributeModel>();
List<AgentRoutingAttributeModel> lstAssigned = new List<AgentRoutingAttributeModel>();
Filled this with some data
Is it possible to filter with Linq? I want to save in a new list the diferent content between lstComplete and lstAssigned
I was trying to join both lists but got stuck there
var results1 = from cl in lstComplete
join al in lstAssigned
on cl.Attribute_No equals al.Attribute_No
select cl;
you can use linq
as my understanding, you try to find linked by attribute_No records and have a list of not matching properties?
lstComplete.Add(new RoutingAttributeModel(){
Attribute_Name = "aaa",
Attribute_No = 1,
Bus_No = 1,
Notes = "",
Status = "status"
});
lstAssigned.Add(new AgentRoutingAttributeModel()
{
Attribute_No = 1,
Agent_No = 10,
Bus_No = 1,
Attribute_Name = "bbb",
Status = "status2"
});
var lst = lstComplete
.Join(lstAssigned,
complete => complete.Attribute_No,
assigned => assigned.Attribute_No,
(complete, assigned) => new { lstComplete = complete, lstAssigned = assigned })
.Select(s => new { s.lstComplete, s.lstAssigned})
.Where(w=>
w.lstAssigned.Attribute_Name != w.lstComplete.Attribute_Name
|| w.lstAssigned.Bus_No != w.lstComplete.Bus_No
)
.ToList()
.Dump();
so result would be
You could try the following query
var filteredList = lstComplete
.Where(x => !lstAssigned.Any(y => y.Attribute_No == x.Attribute_No));

ViewModel with mixed fields and lists

I have the following ViewModel:
public class DayTaskListViewModel
{
public int Id { get; set; }
public string DateFormatted { get; set; }
public bool HasRegistrations { get; set; }
public bool HasStartedRegistrations { get; set; }
public string ItemName { get; set; }
public string WorkTypeName { get; set; }
public string Description { get; set; }
public string LocationName { get; set; }
public bool IsActive { get; set; }
public string UserName { get; set; }
public string StateStatus { get; set; }
public DateTime TodaysDate { get; set; }
public int WeekNumber { get; set; }
public int YearNumber { get; set; }
public string Msg { get; set; }
public string SignInUserName { get; set; }
public string UnitCode { get; set; }
public IEnumerable<Machinery> machineryList { get; set; }
public IEnumerable<Cleaning> cleaningList { get; set; }
}
In my controller I have this definition of the model to be sent to the view:
var model = (from a in _db.WorkTask.Where(y => y.TaskDate.Date == querytodaysDate.Date && y.IsActive == true && (y.IsPrivateUserName == null || y.IsPrivateUserName == currUserName))
join b in _db.WorkTaskLog.Where(x => (x.UserName == currUserName || x.UserName == null) && x.IsActive == true && x.StateStatusId < 4) on a.Id equals b.WorkTaskId into joinedT
from c in joinedT.DefaultIfEmpty()
select new DayTaskListViewModel
{
Id = a.Id,
DateFormatted = a.DateFormatted,
HasRegistrations = a.HasRegistrations,
HasStartedRegistrations = a.HasStartedRegistrations,
ItemName = a.ItemName,
WorkTypeName = a.WorkTypeName,
Description = a.Description,
IsActive = c.IsActive ? c.IsActive : false,
UserName = c.UserName ?? String.Empty,
StateStatus = c.StateStatus ?? "Klar",
WeekNumber = (int)currWeekNo,
YearNumber = (int)currYearNo,
Msg = "",
TodaysDate = (DateTime)todaysDate,
SignInUserName = currUserName,
LocationName = a.LocationName,
UnitCode = a.UnitCode,
//machineryList = _db.Machinery.ToList(),
//cleaningList = _db.Cleaning.ToList(),
}).ToList();
My problem is the following lines in the definition:
//machineryList = _db.Machinery.ToList(),
//cleaningList = _db.Cleaning.ToList(),
Everything works as expected, but as soon as I enable those 2 lines it breaks with null errors.
VS can compile, but it breaks runtime.
I think I see the problem, but I don't know how to solve it.
I want ALL fields in the ViewModel EXCEPT the 2 mentioned lines to be a list, and then the 2 lines to be separate lists independent of the majority.
I have tried all combinations of moving those lines around, but then VS complains.
An example from another controller is this:
DriveListViewModel model = new DriveListViewModel()
{
Drive = await _db.Drive
.Where(m => m.StatusId == 5 || m.StatusId == 1010 || m.StatusId == 1012)
.Where(m => m.LoadingComplete == null)
.Where(m => !m.UnitCode.Contains(excludeString))
.Include(s => s.DriveStatus)
.Include(d => d.Location)
.Include(f => f.Item)
.GroupBy(m => m.RegistrationNumber)
.Select(m => m.FirstOrDefault())
.OrderBy(m => m.DriverToLoad)
.ToListAsync(),
machineryList = await _db.Machinery.ToListAsync(),
cleaningList = await _db.Cleaning.ToListAsync(),
};
This works perfectly, but the former model definition as more complex, so basically, I need something similar to the latter example separating the 2 lists from the other properties in the ViewModel.
Maybe this is VERY simple - however I'm struggling with it...
Anyone see the solution to this?
I don't think that you model is a list with so many includes.
I guess you really need this ViewModel
public class ViewModel
{
public List<DayTaskListViewModel> DayTaskListViewModelList {get; set;}
public List<Machinery> MachineryList {get; set;}
public List<Cleaning> CleaningList {get; set;}
}
code
var dayTaskListViewModel= (from ....
.....
select new DayTaskListViewModel
{
Id = a.Id,
.......
}).ToList(); // or ToListAsync too?
var model = new ViewModel
{
DayTaskListViewModel = dayTaskListViewModel,
MachineryList = _db.Machinery.ToList(),
CleaningList = _db.Cleaning.ToList()
}
// or if you can or prefer , use async
MachineryList = await _db.Machinery.ToListAsync(),
CleaningList = await _db.Cleaning.ToListAsync(),
Here is a snip of the view:
Top:
#model List<Day.Models.ViewModels.DayTaskListViewModel>
#using Day.Extensions
Looping through all properties:
if (Model.Count > 0)
{
foreach (var item in Model)
{
<input name="description" class="form-input" id="description" type="text" value="#item.Description">
..Several input fields...
}
}
Works fine.
Then I need to include select fields using the machineryList from the ViewModel:
<select name="machinery" asp-items="#Model.machineryList.ToSelectListItem((int)defaultMachinery)"></select>

Linq group result to list of object

I'm trying group a collection of data by it's State but I'm stuck on the correct way to do this:
FileStateInfoDto
public class FileStateInfoDto : EntityDto<int>
{
public string StateName { get; set; }
public int StateNumber { get; set; }
public int FilesByStateCount { get; set; }
}
FileGroupDto
public class FileGroupDto : EntityDto<int>
{
public int CaseId { get; set; }
public string Name { get; set; }
public string ResourceKey { get; set; }
public bool IsFolder { get; set; }
public int SequenceNumber { get; set; }
public IList<FileStateInfoDto> FileStateInfo { get; set; }
public IList<FileGroupDto> FileGroups { get; set; }
public IList<FileInfoDto> Files { get; set; }
}
Here is the code I have:
return await Context.FileGroups
.Include(g => g.Case).Include(g => g.FileGroups).Include(g => g.Files)
.Where(g => g.Id == fileGroupId &&
g.CaseId == caseId &&
g.Case.CaseState != CaseState.Approved &&
g.Case.CaseState != CaseState.Submitted &&
(g.Case.CaseState != CaseState.Draft || g.Case.CreatorUserId == userId))
.OrderBy(g => g.SequenceNumber)
.Select(g => new FileGroup
{
Id = g.Id,
CaseId = g.CaseId,
Name = g.Name,
ResourceKey = g.ResourceKey,
IsFolder = g.IsFolder,
SequenceNumber = g.SequenceNumber,
FileGroups = g.FileGroups,
FileStateInfo = g.Files.GroupBy(f => f.State), <-- My problem
Files = g.Files.Where(f => f.IsActive && f.State != FileApprovalState.Approved).Select(
f => new File
{
Id = f.Id,
CreationTime = f.CreationTime,
CreatorUserId = f.CreatorUserId,
Title = f.Title,
FileName = f.FileName,
URL = f.URL,
Size = f.Size,
KeepOnPortal = f.KeepOnPortal,
CreatorUserName = Context.Users.FirstOrDefault(u => u.Id == (f.CreatorUserId ?? 0)).UserName,
CreatorUserRole = Context.CasePersons.Where(p => p.CaseId == caseId && p.UserId == f.CreatorUserId).Take(1).Select(p => p.CaseRoleType.Title).FirstOrDefault()
}
).ToList()
}).FirstOrDefaultAsync();
I'm trying to figure out how I should write this line FileStateInfo = g.Files.GroupBy(f => f.State) so it will give the expected result as below.
FileStateInfo = [{"StateName":"Approved","StateNumber":1, "FilesByStateCount":22},
{"StateName":"NotApproved","StateNumber":2, "FilesByStateCount":11}]
The State in g.Files.GroupBy(f => f.State) is an enum that contains Approved and NotApproved
StateName = Name of the State.
StateNumber = The Integer assinged.
FilesByStateCount = The files count by this state.
I hope it's possible because I've been trying to make this for a few days now.
I've tried things like this Post

Find Unique count on field using LINQ

I am trying to determine the Distinct count for a particular field in a collection of objects.
private static RemittanceCenterBatchSummaryListModel SummarizeFields(RemittanceCenterSummaryListModel remittanceCenterSummaryListModel)
{
var result = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecord.GroupBy(x => new{x.FileId, x.SourceFileName, x.BatchCode, x.BatchType})
.Select(x => new RemittanceCenterBatchSummarizedModel()
{
FileId = x.Key.FileId,
SourceFileName = x.Key.SourceFileName,
BatchCode = x.Key.BatchCode,
BatchType = x.Key.BatchType,
DetailRecordCountAdc = x.Count(y => y.BillingSystemCode == BillingSystemCode.Adc),
DetailRecordCountNotAdc = x.Count(y => y.BillingSystemCode == BillingSystemCode.Exd),
AmountAdc = x.Where(y => y.BillingSystemCode == BillingSystemCode.Adc).Sum(y => y.PaymentAmount),
AmountNotAdc = x.Where(y => y.BillingSystemCode == BillingSystemCode.Exd).Sum(y => y.PaymentAmount),
UniqueFileCount = x.Select(y => x.Key.FileId).Distinct().Count()
});
return CreateSummaryListModel(result);
}
Input entities:
public class RemittanceCenterSummaryListModel
{
public RemittanceCenterSummaryListModel()
{
this.RemittanceBatchSummaryRecord = new List<RemittanceBatchProcessingModel>();
}
public List<RemittanceBatchProcessingModel> RemittanceBatchSummaryRecord { get; private set; }
}
public class RemittanceCenterBatchSummarizedModel
{
public string FileId { get; set; }
public string SourceFileName { get; set; }
public string BatchCode { get; set; }
public string BatchType { get; set; }
public int DetailRecordCountAdc { get; set; }
public int DetailRecordCountNotAdc { get; set; }
public int DetailRecordCountTotal { get; set; }
public decimal AmountAdc { get; set; }
public decimal AmountNotAdc { get; set; }
public decimal AmountTotal { get; set; }
public BillingSystemCode BillingSystemCode { get; set; }
public int UniqueFileCount { get; set; }
}
private static RemittanceCenterBatchSummaryListModel CreateSummaryListModel(IEnumerable<RemittanceCenterBatchSummarizedModel> summaryModels)
{
var summaryModelList = new RemittanceCenterBatchSummaryListModel();
foreach (var summaryRec in summaryModels)
{
var summaryModel = new RemittanceCenterBatchSummarizedModel
{
FileId = summaryRec.FileId,
SourceFileName = summaryRec.SourceFileName,
BatchCode = summaryRec.BatchCode,
BatchType = summaryRec.BatchType,
DetailRecordCountAdc = summaryRec.DetailRecordCountAdc,
DetailRecordCountNotAdc = summaryRec.DetailRecordCountNotAdc,
AmountAdc = summaryRec.AmountAdc,
AmountNotAdc = summaryRec.AmountNotAdc,
UniqueFileCount = summaryRec.UniqueFileCount
};
summaryModelList.RemittanceBatchSummary.Add(summaryModel);
}
return summaryModelList;
}
Example input records:
Record1:
FileId: '123'
SourceFileName: 'test.file.txt'
BatchCode: 'aaa'
BatchType: 'scanned'
PaymentAmount: '50.00'
BillingSystemCode: 'Adc'
Record1:
FileId: '1234'
SourceFileName: 'test.file2.txt'
BatchCode: 'aab'
BatchType: 'scanned'
PaymentAmount: '52.00'
BillingSystemCode: 'Adc'
ActualOuput for UniqueFileCount Field:
UniqueFileCount = 1
ExpectedOutput results for UniqueFileCount Field:
UniqueFileCount = 2
What am I doing wrong?
It sounds like you want the distinct count of FileId for the entire collection and not just for each group, which will always be 1 since FileId is one of the fields you group on. If that is the case then you can just calculate that count first
int distinctFileIds = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecor‌​d
.Select(x => x.FileId)
.Distinct()
.Count();
Then use that in your Linq query
UniqueFileCount = distinctFileIds

Categories