C# Pulling the Count of tables Associated with ID - c#

Hello I have two tables that look like the following:
PeriodValue
Id (PK)
Name
Description
StartDate
EndDate
ActiveFlg
AcademicTerm
Id (PK)
Year Id (FK)
Name
Start Date
End Date
The objective is to pull the count of terms associated with every period value. This is my code to do so.
public async Task<PeriodValueDTO> GetSchoolYearPeriodValueDTOById (int periodValueId)
{
var value = await Db.PeriodValues.FindAsync(periodValueId);
return new PeriodValueDTO()
{
id = periodValueId,
Name = value.Name,
StartDate = value.Date.ToShortDateString(),
EndDate = value.EndDate.ToShortDateString(),
Description = value.Description
};
}
This method calls the one above
public async Task<List<PeriodValueDTO>> GettAllPeriodValueDTOsByType(int periodTypeId)
{
var toReturn = new List<PeriodValueDTO>();
var pvs = await Db.PeriodValues.Where(x => x.PeriodTypeId == periodTypeId).ToListAsync();
var pvIds = pvs.Select(x => x.Id).ToList();
var periodPeriodVal = await Db.Period_PeriodValue.Where(x => pvIds.Contains(x.PeriodValueId)).ToListAsync();
foreach (var ppv in periodPeriodVal)
{
var periodValue = pvs.FirstOrDefault(x => x.Id == ppv.PeriodValueId);
var value = await GetSchoolYearPeriodValueDTOById(periodTypeId);
var rightId = value.id; //Added this
var terms = Db.AcademicTerms.Where(x => x.YearId == rightId).ToArray(); //Changed This
var dto = new PeriodValueDTO()
{
id = periodValue.Id,
Name = periodValue.Name,
StartDate = periodValue.Date.ToShortDateString(),
EndDate = periodValue.EndDate.ToShortDateString(),
Description = periodValue.Description,
Count = terms.Length //Changed this
};
toReturn.Add(dto);
};
return toReturn;
}
However I am getting this error:
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
Error: Resolving failed with a reason [object Object], but no resolveFailed provided for segment SchoolYear
If I comment out the lines that include var terms, var value, and Count it runs. When they are included I get the error above.
Still a novice to this. Any help would be great.

It's likely because of this line:
var terms = Db.AcademicTerms.Where(x => x.YearId == value.id);
Here, you are trying to generate an expression to be translated into SQL and executed on the database (because Db.AcademicTerms is IQueryable). You are not executing this query in memory. The query parser tries to convert value to a SQL parameter and fails because it is not a primitive object. So you have two choices:
Save value.id into a separate variable and then use that in your query, or better:
Save all AcademicTerms into memory before your loop (I assume there are not hundreds of them) by calling .ToArray() and then query against this. This also resolves the additional N+1 Selects antipattern you have, while allowing greater flexibility.

var value = await GetSchoolYearPeriodValueDTOById(periodTypeId);
should be :
var value = await GetSchoolYearPeriodValueDTOById(periodValue.Id);
no ?

Related

Selecting into class related data only when it exists

I have A 'states' table and related 'status' table. 'State' can have 0 or 1 'status'. Currently I query it like this:
IQueryable<State> statesRecords = getStates();
var result = statesRecords
.Select(st => new StateModel()
{
HardwareId = st.HardwareId,
StateId = st.StateId,
Status = st.Status != null ? new StatusModel() // check whether related status exists
{
Comment = st.Status.Comment,
TimeStamp = st.Status.TimeStamp
}:null,
TimeStamp = st.TimeStamp
}).OrderByDescending(r => r.TimeStamp).ToList();
Is it a proper way, or there's any more elegant one?

C# LINQ: DbFunctions.TruncateTime throw error This function can only be invoked from LINQ to Entities

here is my code.
List<NPSEntity> Data = null; ;
using (var db = new NPSDbContext())
{
Data = (from n in db.NPSDatas
orderby n.AddDate, n.CountryCode
where DbFunctions.TruncateTime(n.NPSDate) >= StartDate.Date && DbFunctions.TruncateTime(n.NPSDate) <= EndDate
select n).ToList();
}
NPSData = Data.Select(n => new NPSEntity
{
NPSDate = DbFunctions.TruncateTime(n.NPSDate),
NPSAmount = n.NPSAmount,
AddDate = n.AddDate,
ModDate = n.ModDate,
UserID = n.UserID,
CountryCode = n.CountryCode
}).ToList();
if (NPSData!=null)
{
CountryCodes = NPSData.Select(e => e.CountryCode).Distinct();
CountryCount = CountryCodes.Count();
}
this line throwing error NPSDate = DbFunctions.TruncateTime(n.NPSDate), This function can only be invoked from LINQ to Entities
my objective is to remove time portion from NPSDate NPSDate = DbFunctions.TruncateTime(n.NPSDate) that is why i used DbFunctions.TruncateTime function.
tell me how could i fix this problem or tell me how could i remove time portion from NPSDate when fetching data from db. my NPSDate property look like public DateTime? NPSDate { get; set; } that is why i can not use NPSDate.Date
please guide me. thanks
The error is self explanatory enough, that you cannot use DbFunctions in any other context, the context should only be Linq to Entities and when you call ToList() on an IQueryable<T>, the query gets executed and results are brought in memory, you would need to use Date property of DateTime if truncating the time is needed here like:
NPSData = Data.Select(n => new NPSEntity
{
NPSDate = n.NPSDate.Date,

LINQ to Entities does not recognize the method

This query I wrote is failing and I am not sure why.
What I'm doing is getting a list of user domain objects, projecting them to a view model while also calculating their ranking as the data will be shown on a leaderboard. This was how I thought of doing the query.
var users = Context.Users.Select(user => new
{
Points = user.UserPoints.Sum(p => p.Point.Value),
User = user
})
.Where(user => user.Points != 0 || user.User.UserId == userId)
.OrderByDescending(user => user.Points)
.Select((model, rank) => new UserScoreModel
{
Points = model.Points,
Country = model.User.Country,
FacebookId = model.User.FacebookUserId,
Name = model.User.FirstName + " " + model.User.LastName,
Position = rank + 1,
UserId = model.User.UserId,
});
return await users.FirstOrDefaultAsync(u => u.UserId == userId);
The exception message
System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[WakeSocial.BusinessProcess.Core.Domain.UserScoreModel] Select[<>f__AnonymousType0`2,UserScoreModel](System.Linq.IQueryable`1[<>f__AnonymousType0`2[System.Int32,WakeSocial.BusinessProcess.Core.Domain.User]], System.Linq.Expressions.Expression`1[System.Func`3[<>f__AnonymousType0`2[System.Int32,WakeSocial.BusinessProcess.Core.Domain.User],System.Int32,WakeSocial.BusinessProcess.Core.Domain.UserScoreModel]])' method, and this method cannot be translated into a store expression.
Unfortunately, EF does not know how to translate the version of Select which takes a lambda with two parameters (the value and the rank).
For your query two possible options are:
If the row set is very small small, you could skip specifying Position in the query, read all UserScoreModels into memory (use ToListAsync), and calculate a value for Position in memory
If the row set is large, you could do something like:
var userPoints = Context.Users.Select(user => new
{
Points = user.UserPoints.Sum(p => p.Point.Value),
User = user
})
.Where(user => user.Points != 0 || user.User.UserId == userId);
var users = userPoints.OrderByDescending(user => user.Points)
.Select(model => new UserScoreModel
{
Points = model.Points,
Country = model.User.Country,
FacebookId = model.User.FacebookUserId,
Name = model.User.FirstName + " " + model.User.LastName,
Position = 1 + userPoints.Count(up => up.Points < model.Points),
UserId = model.User.UserId,
});
Note that this isn't EXACTLY the same as I've written it, because two users with a tied point total won't be arbitrarily assigned different ranks. You could rewrite the logic to break ties on userId or some other measure if you want. This query might not be as nice and clean as you were hoping, but since you are ultimately selecting only one row by userId it hopefully won't be too bad. You could also split out the rank-finding and selection of base info into two separate queries, which might speed things up because each would be simpler.

SolrNet OR query

I am trying to query records where ParentId = thread OR DataId = Thread. The query keeps timing out on me. Is there something wrong with the query below?
var Test = solr.Query(new SolrQueryByField("ParentId", Thread) ||
(new SolrQueryByField("DataId", Thread)));
I am not an expert in Solr.Net but I have used it for one project. I can only suggest you try couple of things.
First go to your SOLR Admin and try executing the query:
(ParentId:"Thread") OR (DataId:"Thread")
If you get any result back and its not timing out, you can use the same string in Solr.Net like:
string strQuery = "(ParentId:\"Thread\") OR (DataId:\"Thread\")";
// or use * for contains instead of double quotes
var query = new SolrQuery(strQuery);
SortOrder sortOrder = new SortOrder("ParentId");
var solrQueryResult = solr.Query(query, new QueryOptions
{
Rows = 100, //Max Rows returned
Start = 0,
OrderBy = new[] { sortOrder }, //If you want the ordered result
});
var list = solrQueryResult.ToList();//if you want list
You can do this without concatenating strings manually:
ISolrOperations<User> solr = ServiceLocator.Current.GetInstance<ISolrOperations<User>>();
var users = solr.Query(new SolrQuery("age:20") || new SolrQuery("age:30"), options);
More info here:
https://github.com/mausch/SolrNet/blob/master/Documentation/Querying.md

c# linq syntax slow due to multiple queries in single query

I am wondering if there is a better, more efficient way to re-code the linq syntax below to make the query run faster i.e. with a single call to the database. My database is located remotely which causes this to be quite slow:
var query = (from ticket in dataClassesDataContext.Tickets.Where(TicketsToShow.And(SearchVals))
select new
{
Priority = ticket.TicketPriority.TicketPriorityName,
Ticket = string.Format(TicketFormat, ticket.TicketID),
AssetId = ticket.Asset.Serial,
OpenDate = ticket.CheckedInDate,
OpenFor = CalculateOpenDaysAndHours(ticket.CheckedInDate, ticket.ClosedDate),
Account = ticket.Account.Customer.Name,
Description = ticket.Description.Replace("\n", ", "),
Status = ticket.TicketStatus.TicketStatusName,
Closed = ticket.ClosedDate,
THIS IS THE CAUSE ====>>> Amount = GetOutstandingBalanceForTicket(ticket.TicketID),
Paid = ticket.Paid,
Warranty = ticket.WarrantyRepair,
AssetLocation = GetAssetLocationNameFromID(ticket.Asset.LocationID, AssLocNames)
}).Skip(totalToDisplay * page).Take(totalToDisplay);
if (SortOrder.ToLower().Contains("Asc".ToLower()))
{
query = query.OrderBy(p => p.OpenDate);
}
else
{
query = query.OrderByDescending(p => p.OpenDate);
}//ENDIF
The main cause for the poor performance is the code in the function GetOutstandingBalanceForTicket below which calculates the sum of all items in an invoice and returns this as a total in a string:
public static string GetOutstandingBalanceForTicket(int TicketID)
{
string result = string.Empty;
decimal total = 0;
try
{
using (DataClassesDataContext dataClassesDataContext = new DataClassesDataContext(cDbConnection.GetConnectionString()))
{
var queryCustomerTickets = from ticket in dataClassesDataContext.Tickets
where
(ticket.TicketID == TicketID)
select ticket;
if (queryCustomerTickets != null)
{
foreach (var ticket in queryCustomerTickets)
{
var queryTicketChargeItems = from chargeItem in dataClassesDataContext.ProductChargeItems
where chargeItem.ChargeID == ticket.ChargeID &&
chargeItem.Deleted == null
select chargeItem;
foreach (var chargeItem in queryTicketChargeItems)
{
total += (chargeItem.Qty * chargeItem.Price);
}
}
}
}
}
catch (Exception ex)
{
}
return total.ToString("0.##");
}
Thank you in advance.
As you pointed out this code is quite slow as a query will be required for each ticket.
to eliminate the need for multiple queries you should look at applying an inner join between the ticketsToShow and the tickets entity (on the ticketid), using groupby to provide the sum of the charges for each ticket.
This is well illustrated in the answers to LINQ: Using INNER JOIN, Group and SUM
Ideally you would probably approach it more as an eager loading all at once type of setup. However, I do not think linq2sql supports that (I know EF does). One thing you can do is avoid the nested query though. Since you already have access to the ticket table, perhaps you should just issue a Sum() on it from your select statement. Hard for me to verify if any of this is an improvement so this code is kind of on the fly if you will.
//(from ticket in dataClassesDataContext.Tickets.Where(TicketsToShow.And(SearchVals))
(from ticket in dataClassesDataContext.Tickets
//this would be where you could eager load if possible (not entirely required)
//.Include is an EF method used only as example
/*.Include(t => t.TicketPriority)//eager load required entities
.Include(t => t.Asset)//eager load required entities
.Include(t => t.Account.Customer)//eager load required entities
.Include(t => t.TicketStatus)//eager load required entities
.Include(t => t.ProductChargeItems)//eager load required entities
*/
.Where(TicketsToShow.And(SearchVals))
select new
{
Priority = ticket.TicketPriority.TicketPriorityName,
Ticket = string.Format(TicketFormat, ticket.TicketID),
AssetId = ticket.Asset.Serial,
OpenDate = ticket.CheckedInDate,
OpenFor = CalculateOpenDaysAndHours(ticket.CheckedInDate, ticket.ClosedDate),
Account = ticket.Account.Customer.Name,
Description = ticket.Description.Replace("\n", ", "),
Status = ticket.TicketStatus.TicketStatusName,
Closed = ticket.ClosedDate,
//Use Sum and the foreign relation instead of a nested query
Amount = ticket.ProductChargeItems.Where(pci => pci.Deleted == null).Sum(pci => pci.Qty * pci.Price),
Paid = ticket.Paid,
Warranty = ticket.WarrantyRepair,
AssetLocation = GetAssetLocationNameFromID(ticket.Asset.LocationID, AssLocNames)
}).Skip(totalToDisplay * page).Take(totalToDisplay);
if (SortOrder.ToLower().Contains("Asc".ToLower()))
{
query = query.OrderBy(p => p.OpenDate);
}
else
{
query = query.OrderByDescending(p => p.OpenDate);
}
I think, you can make this query simplier. Somethink like this:
public static string GetOutstandingBalanceForTicket(DataClassesDataContext context, int TicketID)
{
decimal total = 0;
var total = (from ticket in context.Tickets
join chargeItem from context.ProductChargeItems on chargeItem.ChargeID == ticket.ChargeID
where (ticket.TicketID == TicketID && chargeItem.Deleted == null)
select chargeItem).Sum(chargeItem => chargeItem.Qty * chargeItem.Price);
return total.ToString("0.##");
}
/*...*/
Amount = GetOutstandingBalanceForTicket(dataClassesDataContext, ticket.TicketID),
Now, you can inline this methos in your query.
It can contains syntax errors, because I wrote it in notepad.

Categories

Resources