I'm trying to utilize EF Core to execute a Raw Sql statement and "serialize" it back to a recursive object structure.
My data looks like this
Folders -
Requests -
//Folder.cs
[Table("PsRequestFolder")]
public class PsRequestFolder : IWorkSpaceNode
{
[Key]
public int FolderId { get; set; }
public string FolderName = "";
public PsWorkSpace? FolderWorkSpace { get; set; }
public PsRequestFolder? ParentFolder { get; set; }
public List<PsHttpRequest> Requests { get; set; } = new List<PsHttpRequest>();
public List<PsRequestFolder> Folders { get; set; } = new List<PsRequestFolder>();
...
// Request.cs
[Table("PsHttpRequest")]
public class PsHttpRequest : IWorkSpaceNode
{
[Key]
public int RequestId { get; set; }
public string Url = "";
public string RequestName = "";
public PsWorkSpace RequestWorkSpace{ get; set; }
public int PsRequestFolderFolderId { get; set; }
public PsRequestFolder ContainingFolder { get; set; }
...
I'm executing this sql statement by calling FromSqlRaw
ws.RequestFolders = await context.RequestFolders.FromSqlRaw(
#"
WITH cte_ws AS (
SELECT
FolderId,
FolderName,
ParentFolderFolderId,
FolderWorkSpaceWorkSpaceId,
p1.*
FROM PsRequestFolder
LEFT JOIN PsHttpRequest AS p1 ON PsRequestFolder.FolderId = p1.PsRequestFolderFolderId
WHERE ParentFolderFolderId IS NULL
UNION ALL
SELECT
e.FolderId,
e.FolderName,
e.ParentFolderFolderId,
e.FolderWorkSpaceWorkSpaceId,
p1.*
FROM PsRequestFolder as e
LEFT JOIN PsHttpRequest AS p1 ON e.FolderId = p1.PsRequestFolderFolderId
INNER JOIN cte_ws o
on o.FolderId= e.ParentFolderFolderId
)
SELECT * FROM cte_ws;
"
).ToListAsync();
I would expect to get back something that looks like
One
RequestOne
RequestTwo
Two
Six
Eight
Nine
RequestThree
Seven
Three
Four
Five
Instead I get something like this back
Not sure why some of the things that should be nested underneath others aren't being nested as well as why the requests under folder Nine don't come through at all, as well folder Nine and some others being written twice to the list
The raw sql output just running it against the db is -
Any assistance is greatly appreciated!
Related
I need your help. I have a store procedure that executes perfectly in SQL but when I call her from an IActionResult from a .net controller with FromSqlRaw(), returns the following error :
System.InvalidOperationException: 'The required column 'SeasonGroupClientCode' was not present in the results of a 'FromSql' operation.'
in Action:
var sql = "EXECUTE sp_seasonDataGroupedHorizonatlallyByCompany";
var list = _context.SeasonGroupedByCompanies.FromSqlRaw(sql).AsNoTracking().ToList();
Store Procedure:
select * from
(SELECT [SeasonGroupSeasonDescription] AS FirstSeason,[SeasonGroupClientCode] AS
FirstSeasonClientCode,[SeasonGroupClientName] AS
FistSeasonClientName,SeasonGroupCompanyId],
SUM([SeasonGroupStartValue]) AS FiSTotalStartValue ,
SUM([SeasonGroupFinalValue]) AS FiSTotalFinalValue
FROM [dbo].[SeasonGroupedByCompanies]
WHERE [SeasonGroupSeasonDescription]='SS21'
GROUP BY [SeasonGroupClientCode],[SeasonGroupClientName],
[SeasonGroupSeasonDescription],[SeasonGroupCompanyId]) as td
full join
(SELECT [SeasonGroupSeasonDescription] AS SecondSeason,[SeasonGroupClientCode] AS
SecondSeasonClientCode,[SeasonGroupClientName] AS SecondSeasonClientName,
[SeasonGroupCompanyId] AS SecondId,
SUM([SeasonGroupStartValue]) AS SeSTotalStartValue ,
SUM([SeasonGroupFinalValue]) AS SeSTotalFinalValue
FROM [dbo].[SeasonGroupedByCompanies]
WHERE [SeasonGroupSeasonDescription]='FW21'
GROUP BY [SeasonGroupClientCode],[SeasonGroupClientName],[SeasonGroupSeasonDescription],
[SeasonGroupCompanyId]) as tf
on td.FistSeasonClientName = tf.SecondSeasonClientName
full join
(SELECT [SeasonGroupSeasonDescription] AS ThirdSeason,[SeasonGroupClientCode] AS
ThirdSeasonClientCode,[SeasonGroupClientName] AS ThirdSeasonClientName,
[SeasonGroupCompanyId] AS ThirdId,
SUM([SeasonGroupStartValue]) AS ThSTotalStartValue ,
SUM([SeasonGroupFinalValue]) AS ThSTotalFinalValue
FROM [dbo].[SeasonGroupedByCompanies]
WHERE [SeasonGroupSeasonDescription]='SS22'
GROUP BY [SeasonGroupClientCode],[SeasonGroupClientName],[SeasonGroupSeasonDescription],
[SeasonGroupCompanyId]) as ts
on td.FistSeasonClientName = ts.ThirdSeasonClientName
full join
(SELECT [SeasonGroupSeasonDescription] AS FourthSeason,[SeasonGroupClientCode]AS
FourthSeasonClientCode,[SeasonGroupClientName] AS FourthSeasonClientName,
[SeasonGroupCompanyId] AS FourthId,
SUM([SeasonGroupStartValue]) AS FoSTotalStartValue ,
SUM([SeasonGroupFinalValue]) AS FoSTtotalFinalValue
FROM [dbo].[SeasonGroupedByCompanies]
WHERE [SeasonGroupSeasonDescription]='FW22'
GROUP BY [SeasonGroupClientCode],[SeasonGroupClientName],[SeasonGroupSeasonDescription],
[SeasonGroupCompanyId]) as tx
on td.FistSeasonClientName = tx.FourthSeasonClientName
Entity
public class SeasonGroupByCompany
{
[Key]
public int SeasonGroupCompanyId { get; set; }
public string? SeasonGroupSeasonDescription { get; set; }
public string? SeasonGroupSalesman { get; set; }
public int SeasonGroupClientCode { get; set; }
public string? SeasonGroupClientName { get; set; }
public int SeasonGroupQuantity { get; set; }
public int SeasonGroupDiscount { get; set; }
public double SeasonGroupStartValue { get; set; }
public double SeasonGroupFinalValue { get; set; }
The same error comes up, even if I run the query directly from FromRawSql().
Thanks in advance, any help will be appreciated
It looks like in the SeasonGroupedByCompany entity you have a field named SeasonGroupClientCode, but in the SQL query you have renamed it into FirstSeasonClientCode:
...[SeasonGroupClientCode] AS FirstSeasonClientCode...
Either rename the field in the entity, or don't rename the column in SQL, because originally the naming looks to be consistent.
I have a get route a that is going to get data of wells and well tests, when I execute the call on swagger, it will take awhile and then give me a call stack error. My problem is I cant figure out how to get a log or idea of where this is happening. The best I have been able to do so far is use point breaks at every step to see how far it gets. I've gotten to the controller route so I know that its grabbing the data just fine, my understanding is that it now has the data, and should use the view model to match and display the data. I have gone through about 100 data samples in the view model and it seems fine but there is 2400 units, all with 5 arrays inside of them. However it will simply error out with no message. Any ideas of whats going on or how to debug this? Is there a way in VS Code so see a better log of something like this or another tool that will do that will help in this situation?
** Service Code: **
public async Task<IEnumerable<SapDispatchViewModel>> GetDispatchDeliveryForSap()
{
var result = await _dispatchRepo.GetDispatchDeliveryForSap(TenantId);
var view = new List<SapDispatchViewModel>();
foreach (SapDispatch row in result)
{
var sapView = _mapper.Map<SapDispatch, SapDispatchViewModel>(row);
var items = await _dispatchItemRepo.GetDispatchItemsByTruckForSap(row.DispatchTruckId);
var viewItems = _mapper.Map<IEnumerable<SapDispatchItem>, IEnumerable<SapDispatchItemViewModel>>(items);
sapView.Items = viewItems;
view.Add(sapView);
}
return view;
}
** It calls this GetDispatchDeliveryForSap first: **
public async Task<IEnumerable<SapDispatch>> GetDispatchDeliveryForSap(string TenantId)
{
string deliveryType = "Delivery";
//resort to raw SQL to assist with performance improvements
FormattableString sql = $#"
WITH cte_latestStatus AS
( SELECT * FROM (
SELECT
s.TenantId,
s.DispatchId,
s.DispatchHeaderId,
s.RequestedArrival,
s.EstimatedArrival,
s.Status,
u.FirstName + ' ' + u.LastName UserName,
s.CreateDate StatusChangeDate,
row_number() over(partition by DispatchHeaderId order by CreateDate desc) as rn
FROM
DispatchStatus s
JOIN AspNetUsers u on s.CreateUserId = u.Id
) t
WHERE t.rn = 1
)
select w.wellid,
w.wellname,
wo.ErpId,
wc.ContractorName + ' ' + w.RigNumber Rig,
w.CountyParish County,
w.State,
d.type DispatchType,
u.LastName + ',' + u.FirstName OrderedBy,
ds.RequestedArrival RequestedDate,
dt.DriverName,
dt.SwamperName,
dt.TicketNumber,
dt.DispatchTruckId
from well w
join Dispatch d on w.wellid = d.DestinationWellId
join cte_latestStatus ds on d.DispatchId = ds.DispatchId and d.HeaderId = ds.DispatchHeaderId
join DispatchTruck dt on d.DispatchId = dt.DispatchId
join AspNetUsers u on d.CreateUserId = u.Id
left join WellContractorRef wcr on w.WellId = wcr.WellId
left join Contractor wc on wcr.ContractorId = wc.ContractorId
left join WellOperatorRef wor on w.WellId = wor.WellId
left join Operator wo on wor.OperatorId = wo.OperatorId
--join DispatchItem di on dt.DispatchTruckId = di.DispatchTruckId
where d.TenantId = {TenantId}
and d.type = {deliveryType}
and (ds.Status = 'Completed' or dt.status = 'Completed')
order by w.wellname"
;
var result = await context.SapDispatches.FromSqlInterpolated(sql).AsNoTracking().ToListAsync();
return result;
}
}
}
** Then maps via the view model to create the list: **
namespace Mudman.Model.ViewModels
{
public class SapDispatchViewModel
{
public string WellId { get; set; }
public string WellName { get; set; }
public string ErpId { get; set; }
public string Rig { get; set; }
public string County { get; set; }
public string State { get; set; }
public string DispatchType { get; set; }
public string OrderedBy { get; set; }
public DateTime? RequestedDate { get; set; }
public string DriverName { get; set; }
public string SwamperName { get; set; }
public long? TicketNumber { get; set; }
public IEnumerable<SapDispatchItemViewModel> Items { get; set; }
}
public class SapDispatchItemViewModel
{
public string ErpId { get; set; }
public Decimal? Price { get; set; }
public Decimal? Quantity { get; set; }
public string Size { get; set; }
public string Unit { get; set; }
}
}
** From there, it runs the foreach on the GetDispatchItemsForTruckSap: **
public async Task<IEnumerable<SapDispatchItem>> GetDispatchItemsByTruckForSap(string dispatchTruckId)
{
//resort to raw SQL to assist with performance improvements
FormattableString sql = $#"
WITH cte as (
SELECT
COALESCE(ProductId, ExpenseId) AS SalesItemID,
Price,
Quantity
FROM DispatchItem
WHERE DispatchTruckId = {dispatchTruckId}
)
SELECT si.ErpId,
cte.Price,
cte.Quantity,
si.Size,
si.Unit
FROM SalesItem si
INNER JOIN cte on cte.SalesItemID = si.SalesItemId"
;
var result = await context.SapDispatchItems.FromSqlInterpolated(sql).AsNoTracking().ToListAsync();
return result;
}
}
}
** Maps with the Item View Model: **
public class SapDispatchItemViewModel
{
public string ErpId { get; set; }
public Decimal? Price { get; set; }
public Decimal? Quantity { get; set; }
public string Size { get; set; }
public string Unit { get; set; }
}
}
** Then it will hit the return and thats where it will error out.
Also, here is what the callstack is looking like when you hit that return.
Try turning on Break When Thrown on Common Language Runtime Exceptions and it should break at the error:
I have a model in Entity Framework Core that goes something like this:
public class Anime
{
public int EpisodeCount { get { return Episodes.Count() } }
public virtual ICollection<Episode> Episodes { get; set; }
}
I'm having the issue of EpisodeCount being 0. The solution currently is to run a .Include(x => x.Episodes) within my EF query, but that loads the entire collection of episodes where it's not needed. This also increases my HTTP request time, from 100ms to 700ms which is just not good.
I'm not willing to sacrifice time for simple details, so is there a solution where I can have EF only query the COUNT of the episodes, without loading the entire collection in?
I was suggested to do this
var animeList = context.Anime.ToPagedList(1, 20);
animeList.ForEach(x => x.EpisodeCount = x.Episodes.Count());
return Json(animeList);
but this also returns 0 in EpisodeCount, so it's not a feasible solution.
You need to project the desired data into a special class (a.k.a. ViewModel, DTO etc.). Unfortunately (or not?), in order to avoid N + 1 queries the projection must not only include the count, but all other fields as well.
For instance:
Model:
public class Anime
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public virtual ICollection<Episode> Episodes { get; set; }
}
ViewModel / DTO:
public class AnimeInfo
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public int EpisodeCount { get; set; }
}
Then the following code:
var animeList = db.Anime.Select(a => new AnimeInfo
{
Id = a.Id,
Name = a.Name,
EpisodeCount = a.Episodes.Count()
})
.ToList();
produces the following single SQL query:
SELECT [a].[Id], [a].[Name], (
SELECT COUNT(*)
FROM [Episode] AS [e]
WHERE [a].[Id] = [e].[AnimeId]
) AS [EpisodeCount]
FROM [Anime] AS [a]
I am working on an application for Windows Phone platform. Run into a question.
I'd 2 different List, 1 is direct read from xml file, and the other by some calculation. And I want to merge this two lists into 1, so I can display it out.
List 1:
public class studentClass1
{
public string studentID { get; set; }
public string studentFirstName { get; set; }
public string studentLastName { get; set; }
}
List 2:
public class studentClass2
{
public string studentID { get; set; }
public string studentGradePoint { get; set; }
}
First of all, I had readout the studentClass1 via
var studentList= from query in studentIndex.Descendants("student")
select new driversClass
StudentList1 = studentList.ToList();
Secondly, I process the student Grade Point calculation on the function and output to the 2nd list :
studentClass2 SG = new studentClass2
{
studentID = thestudentID ,
studentGradePoint = thestudentGradePoint .ToString()
};
StudentList2.Add(SG);
studentListAll = StudentList1 + StudentList2
now, I want to join this two list together so that I can output to screen by calling
studentResultListBox.Itemsource = StudentListAll;
any suggestion the code how would look like?
Thanks.
Assuming you just want to combine the appropriate info from both lists (it is not totally clear from your question) - introduce a third class studentClass3 that holds all the properties you want and use a join to match instances with a matching studentID:
var studentList3 = (from s1 in studentList1
join s2 in studentList2 on s1.studentID equals s2.studentID
select new studentClass3()
{
studentFirstName = s1.studentFirstName,
studentID = s1.studentID,
studentGradePoint = s2.studentGradePoint,
studentLastName = s1.studentLastName
}).ToList();
In general this problem should be rather solved when you read in the XML than trying to combine the lists later on - having three different classes for students might be confusing. Also take a look at the recommended naming conventions, they are a little off.
Use interface, e.g.
interface IStudent
{
string studentID { get; }
// other common properties below
}
public class StudentClass1 : IStudent
{
public string studentID { get; set; }
public string studentFirstName { get; set; }
public string studentLastName { get; set; }
}
public class StudentClass2 : IStudent
{
public string studentID { get; set; }
public string studentGradePoint { get; set; }
}
and then
studentResultListBox.Itemsource = list1.Cast<IStudent>()
.Concat(list2.Cast<IStudent>())
.ToList();
Or, if the inheritance is not the case, just cast everything to System.Object (+ override object.ToString)
studentResultListBox.Itemsource = list1.Cast<object>()
.Concat(list2.Cast<object>())
.ToList();
I'm using Entity Framework CTP5.
I have a schema like this:
A group contains many textdescriptions.
A textdescriptions has many texts.
A Language has many texts.
So there are 4 tables.
Groups one-to-many DEscriptions many-to-many Texts many-to-one Languages.
So I have a many-to-many relationship where the relation also holds data.
Definitions of Text and TextDescription ( since we can query on the Id for Group and Languages I havent added them here )
public class Text
{
public int TextID { get; set; }
public int TextDescriptionID { get; set; }
public int LanguageID { get; set; }
public string OriginalText { get; set; }
public bool IsValid { get; set; }
public DateTime Added { get; set; }
public DateTime Updated { get; set; }
public Language Language { get; set; }
public TextDescription TextDescription { get; set; }
public static Text GetMissingText(string input)
{
Text text = new Text();
text.OriginalText = "Missing: " + input;
text.IsValid = true;
text.TextDescription = new TextDescription()
{
IsStatic = true,
Name = input,
IsMultiline = false,
};
return text;
}
}
public class TextDescription
{
public int TextDescriptionId { get; set; }
public int TextDescriptionGroupId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool UseHtml { get; set; }
public bool IsMultiline { get; set; }
public bool IsStatic { get; set; }
public TextDescriptionGroup TextDescriptionGroup { get; set; }
public virtual ICollection<Text> Texts { get; set; }
public static TextDescription GetNewItem(int textDescriptionGroupId)
{
var item = new TextDescription();
item.Name = item.Description = "n/a";
item.UseHtml = item.IsMultiline = item.IsMultiline = false;
item.TextDescriptionGroupId = textDescriptionGroupId;
return item;
}
}
When adding either a new language or a new text is inserted ... the many to many relation is not inserted into the database. (Think it would be a bad idea, so in the end, if thats the only solution, I could be able to that)
So how do I handle this in a smart way when I need to fetch all the text for a specific group from the database, but also get the translation if there are one for that languages.
I can't start fra the translation object, since its possible its not there. If I start to query from the Text entity ... how do I only select one language without getting all languages first.
repo.Find(x =>
x.GroupId == groupId &&
x.Translation.Any(a => a.LanguageID == id.Value)
);
I'm lost here ... any there any smart way ... so I wont have to query the database for all the Texts ... and then a query for each item ... to see if there are a translation? or else just make a new empty one.
In SQL I would do it like this:
SELECT TD.Name, T.OriginalText FROM TextDescriptions TD
LEFT JOIN Texts T ON TD.TextDescriptionId = T.TextDescriptionId
WHERE TextDescriptionGroupId = 41 AND ISNULL(T.LanguageId, 1) = 1
The above SQL will give me the elements even if there is not record now, I get a NULL for these values. I could then handle that it my code and avoid lazy load.
But can I get the same behavior in Entity Framework. I could see there would be some problems maybe for EF4 to do the mapping ... since I'm going from TextDesciptions to Texts ... and TextDesciptions have a List of Texts ... but here ... I only want either 1 or NULL, or just a new Entity that havent been added to the database yet.
Looking forward to some interesting answers.
mvh
For now ... if no other solution is found I will be running the follow SQL script to insert empty records. This way I'm sure the record is there when a user wants to edit it and dont have to ensure its there before saving it. Maybe also avoiding some naste Linq query.
I only have to run this SQL 2 places. When adding a new Language or new a new TextDesciption.
INSERT INTO Texts
SELECT TD.TextDescriptionId, L.LanguageId, '', 0, GETDATE(), GETDATE(), L.TwoLetterISOLanguageName
FROM TextDescriptions TD
INNER JOIN Languages L ON 1 = 1
LEFT JOIN Texts T ON
T.TextDescriptionId = TD.TextDescriptionId AND
T.LanguageId = L.LanguageId
WHERE TextId IS NULL