I have List of Orders, which have the property "Status" which is an int. For each status I have a translations in different languages. I want to sort my list by the selected translation and not by numeric status value. What is the best practice here?
public record OrderTranslation
{
public string OrderStatus { get; set; }
public string StatusDescription { get; set; }
public Language Language { get; set; }
}
public record Order
{
public int? Id { get; set; }
public int Status { get; set; }
// I have added a new value to set the translated value and I want to order by this
public string TranslatedStatusValue { get; set;}
}
my function:
public async Task<FilterResult> FilterAsync(FilterRequest filterRequest, List<string> filterProperties, Language selectedLanguage)
{
var orderTranslations = dataContext
.OrderTranslations
.Where(ot => ot.Language == selectedLanguage)
.ToList();
var orders = dataContext.Orders.AsNoTracking();
foreach (var order in orders)
{
var description = orderTranslations
.Single(x => x.OrderStatus == serviceContract.Status)
.StatusDescription;
serviceContract.TranslatedValue = description;
}
// The TranslatedValue is always empty here
// This is not working, but I want to Order by the translation. Is there another possibility to to this, not using an extra property?
IQueryable<ServiceContractOrder> query = orders
.OrderBy("TranslatedStatusValue", filterRequest.IsSortAscending)
.WhereMatchesFilter(filterRequest, filterProperties);
result.FilterHits = await query
.Skip(filterRequest.ItemsToSkip())
.Take(filterRequest.ItemsPerPage)
.Cast<object>()
.ToListAsync();
result.TotalCount = await query.CountAsync();
result.ObjectType = typeof(Order).AssemblyQualifiedName;
result.FilteredProperties = filterProperties;
}
It all depends on your size of data and what you want to achieve.
If you have small data set, without pagination, you can sort them in client side ( in your dotnet code).
If you have a large dataset, and/or you need pagination, then you will need to apply the sorting to the DB. In such case, I would suggest you to store the translated values in Same table as Owned Entity or maybe different table. And then you can apply sorting in your LINQ query.
Two benefits you get is,
Sorting is absolute, and order is maintained across queries.
Performance, as sorting on client-side hurts for large data sets.
What you lose,
Any change to translation has to be applied to DB. This makes your database complex.
If the order status is a fixed set of data, like enum, then you can chose to have a denormalized design. i.e., to have a dedicated OrderStatus table with translations and then join them to your Order table.
Your domain will be somewhat like,
public record OrderStatus
{
int Id{get; set;}
public ISet<OrderTranslation> Translations { get; set; }
}
public record OrderTranslation(Language Language, string)
{
public string OrderStatus { get; set; }
public string StatusDescription { get; set; }
public Language Language { get; set; }
}
public record Order
{
public int? Id { get; set; }
public OrderStatus Status { get; set; }
}
Related
As of now, I am trying to create a list that groups based on certain criteria and then display that list in the view.
I have two database tables and one is an association table.
First Table
public partial class InitialTraining
{
public InitialTraining()
{
InitialTrainingAssociations = new HashSet<InitialTrainingAssociation>();
}
public int Id { get; set; }
[ForeignKey("MedicInfo")]
public int TfoId { get; set; }
[ForeignKey("InstructorInfo")]
public int? InstructorId { get; set; }
[ForeignKey("PilotInfo")]
public int? PilotId { get; set; }
public DateTime DateTakenInitial { get; set; }
public decimal FlightTime { get; set; }
public bool Active { get; set; }
[StringLength(2000)]
public string Narrative { get; set; }
[Required]
[StringLength(20)]
public string TrainingType { get; set; }
[ForeignKey("CodePhase")]
public int PhaseId { get; set; }
[ForeignKey("PhaseTrainingType")]
public int PhaseTrainingTypeId { get; set; }
public string EnteredBy { get; set; }
public DateTime? EnteredDate { get; set; }
public virtual MedicInfo MedicInfo { get; set; }
public virtual MedicInfo InstructorInfo { get; set; }
public virtual MedicInfo PilotInfo { get; set; }
public virtual Code_Phase CodePhase { get; set; }
public virtual Code_PhaseTrainingType PhaseTrainingType { get; set; }
public virtual ICollection<InitialTrainingAssociation> InitialTrainingAssociations { get; set; }
}
Second Table (Association Table)
public class InitialTrainingAssociation
{
public int Id { get; set; }
[ForeignKey("InitialTraining")]
public int InitialTrainingId { get; set; }
[ForeignKey("CodePerformanceAnchor")]
public int? PerformanceAnchorId { get; set; }
[ForeignKey("GradingSystem")]
public int? GradingSystemId { get; set; }
public virtual AviationMedicTraining.CodePerformanceAnchor CodePerformanceAnchor { get; set; }
public virtual InitialTraining InitialTraining { get; set; }
public virtual GradingSystem GradingSystem { get; set; }
}
Here is my GroupBy in C#.
// get list of initial training record ids for statistics
var lstInitialTrainings = db.InitialTrainings.Where(x => x.TfoId == medicId && x.Active).Select(x => x.Id).ToList();
// get list of initial training performance anchors associated with initial training records
var lstPerformanceAnchors = db.InitialTrainingAssociations
.Where(x => lstInitialTrainings.Contains(x.InitialTrainingId)).GroupBy(t => t.PerformanceAnchorId)
.Select(s => new MedicStatistic()
{
PerformanceAnchorName = db.CodePerformanceAnchor.FirstOrDefault(v => v.Id == s.Key).PerformanceAnchor,
AnchorCount = s.Count()
}).ToList();
My Goal
Obviously from my code I want to group by the performance anchor in the association table, but I need more information from the Initial Training table to include in my ViewModel MedicStatistic, but I am having trouble figuring out the best way to do it.
My overall goal is to be able to get the most recent time a performance anchor was completed from the Initial Training table.
Visual
Initial Training Table (not all fields were captured in snippet b/c they're not important for the purpose of this question)
Initial Training Association Table
What I expect
So, from the pictures provided above as you can see there are multiple 1's for performance anchor id's in the association table, but they each have different InitialTrainingId. So, this specific performance anchor has been done multiple times, but I need to get the most recent date from the Initial Training table. Also, I need to get the corresponding grade with the anchor from the Grading System table, based on the most recent date.
So, for the performance anchor that equals 1.. I would want the grade that corresponds to the InitialTrainingId of 17 because that record was the most recent time that the performance anchor of 1 was done.
If you have any questions please let me know.
You want the data grouped by CodePerformanceAnchor, so the most natural way to start the query is at its DbSet which immediately eliminates the necessity of grouping:
from pa in db.CodePerformanceAnchors
let mostRecentInitialTraining
= pa.InitialTrainingAssociations
.Select(ita => ita.InitialTraining)
.OrderByDescending(tr => tr.DateTakenInitial)
.FirstOrDefault()
select new
{
pa.PerformanceAnchor,
mostRecentInitialTraining.DateTakenInitial,
mostRecentInitialTraining. ...
...
AnchorCount = pa.InitialTrainingAssociations.Count()
}
As you see, only navigation properties are used and the query as a whole is pretty straightforward. I assume that the PerformanceAchor class also has an InitialTrainingAssociations collection.
I can't guarantee that EF will be able to execute it entirely server-side though, that's always tricky with more complex LINQ queries.
I'm going to ignore the virtual properties in your InitialTrainingAssociation class, since you didn't mention anything about them and it's not immediately apparent to me whether they actually contain data, or why they are virtual.
It seems like IQueryable.Join is the easiest way to combine the data you want.
In the following example, we will start with the entries from the InitialTrainings table. We will then Join with the InitialTrainingAssociations table, which will result in a collection of paired InitialTraining and InitialTrainingAssociation objects.
var initialTrainingResults =
// Start with the InitialTrainings data.
db.InitialTrainings
// Add association information.
.Join(
// The table we want to join with
db.InitialTrainingAssociations,
// Key selector for the outer type (the type of the collection
// initiating the join, in this case InitialTraining)
it => it.Id,
// Key selector for the inner type (the type of the collection
// being joined with, in this case InitialTrainingAssociation)
ita => ita.InitialTrainingId,
// Result selector. This defines how we store the joined data.
// We store the results in an anonymous type, so that we can
// use the intermediate data without having to declare a new class.
(InitialTraining, InitialTrainingAssociation) =>
new { InitialTraining, InitialTrainingAssociation }
)
From here, we can add data from the PerformanceAnchors and GradingSystems tables, by performing more Joins. Each time we perform a Join, we will add a new entity to our anonymous type. The result will be a collection of anonymous types representing data we retrieved from the database.
// Add performance anchor information.
.Join(
db.PerformanceAnchors,
x => x.InitialTrainingAssociation.PerformanceAnchorId,
pa => pa.Id,
(x, PerformanceAnchor) =>
new { x.InitialTrainingAssociation, x.InitialTraining, PerformanceAnchor }
)
// Add grading system information.
.Join(
db.GradingSystems,
x => x.InitialTrainingAssociation.GradingSystemId,
gs => gs.Id,
// No need for InitialTrainingAssociation anymore, so we don't
// include it in this final selector.
(x, GradingSystem) =>
new { x.InitialTraining, x.PerformanceAnchor, GradingSystem }
);
(This was a verbose example to show how you can join all the tables together. You can use less Joins if you don't need to access all the data at once, and you can filter down the InitialTrainings collection that we start with if you know you only need to access certain pieces of data.)
At this point, initialTrainingResults is an IEnumerable containing one entry for each association between the InitialTrainings, PerformanceAnchors, and GradingSystems tables. Essentially, what we've done is taken all the InitialTrainingAssociations and expanded their Ids into actual objects.
To get the most recent set of data for each performance anchor:
var performanceAnchors = initialTrainingResults
// Group by each unique Performance Anchor. Remember, the IEnumerable
// we are operating on contains our anonymous type of combined Training,
// Performance Anchor and Grading data.
.GroupBy(x => x.PerformanceAnchor.Id)
// Order each Performance Anchor group by the dates of its training,
// and take the first one from each group
.Select(g => g.OrderByDescending(x => x.InitialTraining.DateTakenInitial).First());
In the Select you can order the group result to get the most recent associated InitialTraining by DateTakenInitial, and from there get the desired data
//...omitted for brevity
.GroupBy(t => t.PerformanceAnchorId)
.Select(g => {
var mostRecent = g.OrderByDescending(_ => _.InitialTraining.DateTakenInitial).First();
// get the corresponding grade with the anchor from the Grading System table
var gradeid = mostRecent.GradingSystemId;
var gradingSystem = mostRecent.GradingSystem;
//get the most recent date from the Initial Training
var mostRecentDate = mostRecent.InitialTraining.DateTakenInitial
//..get the desired values and assign to view model
var model = new MedicStatistic {
//Already have access to CodePerformanceAnchor
PerformanceAnchorName = mostRecent.CodePerformanceAnchor.PerformanceAnchor
AnchorCount = g.Count(),
MostRecentlyCompleted = mostRecentDate,
};
return model;
});
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 have two models:
public class HouseType
{
public int Id { get; set; }
public string TypeName { get; set; }
public virtual IEnumerable<HouseModel> HouseModels { get; set; }
}
and
public class HouseModel
{
public int Id { get; set; }
public string ModelName { get; set; }
[DisplayFormat(DataFormatString = "{0:n2}")]
public double StandardPrice { get; set; }
[ForeignKey("HouseType")]
public int HouseTypeID { get; set; }
public virtual HouseType HouseType { get; set; }
public virtual IEnumerable<HouseUnit> HouseUnits { get; set; }
}
I am returning a JSON result, so as expected I cannot manipulate it in a view, because the display is handled by a javascript file that I made.
I am trying to retrieve the number of HouseModel that is contained by HouseType. I have tried:
db.HouseTypes.Select(h => new
{
HouseCount = h.HouseModels.Count()
}).ToList();
But Entity Framework complains about it. How can I access the count of related records inside an entity? Any help will be much appreciated. Thanks.
Use
public virtual ICollection<HouseUnit> HouseUnits { get; set; }
instead of
public virtual IEnumerable<HouseUnit> HouseUnits { get; set; }
Hope this helps.
Simply speaking, the trouble is that EF is trying to execute the .Select() statement on the db server but, of course, the db server does not know how to create a new object.
You first need to bring back the counts then create your objects so something like this should work better:
var listOfCounts = db.HouseTypes
.Select(h => h.HouseModels.Count())
.ToList()
.Select(c => new
{
HouseCount = c
})
.ToList();
in this example when the first .ToList() is executed the db needs only return a set of numbers (the counts of HouseModels in each HouseType) then we have a List<int> in local memory from which we can create our objects with the second Select statement.
As an aside...
It wasn't part of your original question but maybe you'd want to consider a dictionary rather than a list so you have some means of identifying which count of HouseModels belonged to each HouseType? in which case we could do something like:
Dictionary<int,string> houseModelCounts = db.HouseTypes
.ToDictionary(h => h.Id, h => h.HouseModels.Count());
which would give a dictionary keyed with the HouseType Id with values for the count of HouseModels in each type. I don't know your context though so maybe unnecessary for you?
I currently have an Entity Framework model that collects data from a legacy database and I am currently using an int on my Id properties
I am attempting to build a search box with autocomplete capabilities and want to have the autocomplete function to return a subset of records based on whether the sample id either contains or starts with (final design decision not made yet) and I am running into problems with converting the integer id to a string as I would normally use a recs.Id.toString().StartsWith(recordId) but this is apparently not supported by the Entity Framework
Is there a way around this limitation ?
My code looks like the following
Model:
public class Sample
{
public Sample()
{
Tests = new List<Test>();
}
public int Id { get; set; }
public DateTime SampleDate { get; set; }
public string Container { get; set; }
public string Product { get; set; }
public string Name { get; set; }
public string Status { get; set; }
public virtual SamplePoint SamplingPoint { get; set; }
public virtual SampleTemplate SampleTemplate { get; set; }
public Customer ForCustomer { get; set; }
public virtual ICollection<Test> Tests { get; set; }
}
and the query I am currently trying to apply to this model
[HttpGet]
public JsonResult AutoComplete(string partialId)
{
var filteredSamples =
repo.AllSamples.Where( s =>
String.Compare(s.Status, "A", false) == 0
&& (s.Id.ToString()).StartsWith(partialId)
).ToList();
return Json(filteredSamples, JsonRequestBehavior.AllowGet);
}
Any ideas would be awesome I am out of ideas at this point
No matter what you do, this is going to result in some awful performance on large datasets, because you will not be able to use any indices. My recommendation would be to use a trigger or scheduled task to store the leading digit in a separate field and filter on that.
I ended up adding a view for autocomplete data and converting the data to string in the select statement and this solved my issue
Wild thought: how about your create a computed, persisted column on your database table, that converts your ID (INT) into a string?
Then you could:
put an index on that column
use a simple string comparison on that string column
Basically, you need this:
ALTER TABLE dbo.YourTable
ADD IDAsText AS CAST(ID AS VARCHAR(10)) PERSISTED
Now update you EF model - and now you should have a new string field IDAsText in your object class. Try to run your autocomplete comparisons against that string field.
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