EF Core - Create report table from multiple tables - c#

I have a report that I need to send to my react frontend that needs to be easily queried and searched. The problem is with the current method we need to pull all the entire database before performing a query due to nested objects and other factors.
To significantly speed up the process I want to create a Report Table/View to query from that stays up to date as the other tables change.
Here is a small example of the models:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int TypeId { get; set; }
public ItemType Type { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
}
public class ItemType
{
public int Id { get; set; }
public string Name { get; set; }
public List<Item> Items { get; set; }
}
public class Order
{
public int Id { get; set; }
public Customer Customer { get; set; }
public List<Item> Items { get; set; }
}
public class ItemReport
{
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public string Customer { get; set; }
public ItemReport(Item item)
{
Name = item.Name;
Description = item.Description;
Type = item.Type.Name;
Customer = item.Order.Customer.Name;
}
}
ItemReport is the model I use to send to the frontend.
I've read a fair amount on Keyless Entities and Views, but am in need of a little guidance on putting all the pieces together.
Currently we would pull all Items and required fields like Name from customer and turn it into an IEnumerable list of ItemReport to then be filtered/sorted or searched.
As a side note, there may be other solutions than the on I'm posting for that I would be open to as well.
I've looked into this quite a bit, but I don't think I'm finding the right solutions as an example when reading on Views they mention how they cannot be inserted into or updated with EF Core.

It is simple projection. Passing item in Constructor is not right way, because EF Core cannot look into compiled method body.
var query = context.Items
.Select(item => new ItemReport
{
Name = item.Name;
Description = item.Description;
Type = item.Type.Name;
Customer = item.Order.Customer.Name;
});

Related

How to save an object containing a list of objects to database in ASP.NET MVC with EF?

I know this question exists ( I literally copy pasted it) but its answer is unclear and doesnt work for me I have a model which Ive tried adding to a database, it consist of complex datatypes and lists and the database has stored it totally not the way I intended
this is my class im trying to store:
'''
`
enter code here
`[Table("Recipes")]
public class Recipe
{
public int Id { get; set; }
public string Title { get; set; }
[DisplayName("Descrption")]
public string Description { get; set; }
public Tags RTags { get; set; }
[ForeignKey("Id")]
public List<Ingredient> Ingredients { get; set; }
[ForeignKey("Id")]
public List<Instruction> Instructions { get; set; }
public string Image { get; set; }
}
'''
this is my class ingredient:
'''`
public class Ingredient
{
[ForeignKey("recipe Id")]
public int Id { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
public MeasurementType MeasurementUnit { get; set; }
}`
`
`
'''
here is what it is stored like in the db:
as you can see in the image it has only saved an object with id, title, description, RTagsid and image
database without lists or complex data
You can store changes to a child object like following:
using (var context = new MyContext())
{
ctx.Recipe.Attach(recipe);// Parent
ctx.Entry(recipe).State = EntityState.Added;
ctx.Ingredient.Attach(ingredient);// Child
ctx.Entry(ingredient).State = EntityState.Added; // or EntityState.Modified
ctx.SaveChanges();
}

In EF Core, how to solve an Include ThenInclude chain circular reference?

Consider the following scenario. I have 3 classes, representing a many-to-many (N-to-N) relationship between Student and Subject:
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public long RegistrationNumber { get; set; }
public virtual ICollection<Grade> Grades { get; set; }
}
public class Grade
{
public long Id { get; set; }
public int Value { get; set; }
public virtual Student Student { get; set; }
public virtual Subject Subject { get; set; }
}
public class Subject
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Grade> Grades { get; set; }
}
I want to retrieve the list of all students, with their grades, for each subject. To do so, I use:
// context being DbContext
var res = context.Student.Include(s => s.Grades).ThenInclude(g => g.Subject);
As properties are lazy-loaded, I expected each subject to only contain their "Name" property. However, upon inspection, I found that the "Grades" list is also set, with a list of all the grades assigned to the subject. This, of course, causes an object cycle.
I want to avoid that circular referencing, i.e. obtain a list where each subject's only set property is "Name". How can I do it?
If you use asp.net core 3.0 MVC/Web API, just follow below steps to overcome circular reference using NewtonsoftJson.
1.Install Microsoft.AspNetCore.Mvc.NewtonsoftJson package(version depends on your project)
Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.0.0
2.Add below code in startup
services.AddControllersWithViews().AddNewtonsoftJson(x =>
{
x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
You can always manually select the name only, like
context.Student.Select(x => x.Name);
But this does not work with the Navigation properties and the automaticly generated Joins between the tables. There it's 'all or nothing'.
Or you have to do the join completly manually, without navigation properties.
But your structure isn't that complicated and not vulnerable to circularities.
Just start of with the Grade, with the anchor element in the middle.
context.Grade.Include(x => Subject).Include(x =>Student)
This is at least the easier way to load your entire structure and may be an approach for a starting point for manual joins.
Maybe you add a
.GroupBy(x => x.Student)
To get closer to your list of students.
You cannot skip the "loading" of the collection, cause it's the grades, that is loaded first. So first there are the elements of the collection, than there is the subject entity. It makes no sense not to put the data in the collection.
Following Jawad's advice, I ended up using LINQ Select statements.
First, I wrote some DTO's:
public class StudentDTO
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
public long RegistrationNumber { get; set; }
public IEnumerable<GradeDTO> Grades { get; set; }
}
public class GradeDTO
{
public int Value { get; set; }
public virtual StudentDTO Student { get; set; }
public virtual SubjectDTO Subject { get; set; }
}
public class SubjectDTO
{
public string Name { get; set; }
public virtual IEnumerable<GradeDTO> Grades { get; set; }
}
And then:
var res = from student in context.Student
select new StudentDTO
{
Name = student.Name,
Birthday = student.Birthday,
RegistrationNumber = student.RegistrationNumber,
Grades = from grade in student.Grades
select new GradeDTO
{
Value = grade.Value,
Subject = new SubjectDTO
{
Name = grade.Subject.Name
}
}
};

Entity Framework Core filter related entities and get top 2 for each group

I am using Entity Framework Core 2.0.1 and I have the following models
public class Article
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string Slug { get; set; }
public int Approved { get; set; }
public DateTime ArticleDate { get; set; }
// ... some other fields
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategory
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
//... soem other fields
[ForeignKey("ArticleCategoryParent")]
public int? ArticleCategoryParentID { get; set; }
public virtual ArticleCategory ArticleCategoryParent { get; set; }
public virtual ICollection<ArticleCategory> SubCategories { get; set; }
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategoryRelation
{
[Column(Order = 0)]
public int ArticleId { get; set; }
public Article Article { get; set; }
[Column(Order = 1)]
public int ArticleCategoryId { get; set; }
public ArticleCategory ArticleCategory {get; set;}
}
Every article belongs to one or more categories. Categories might have parent category.
I want to get from database last two articles (where Approved = 1) with related category details, for each category that belongs to a parent category which id is given as input.
I have tried but with no success. I can't filter results of an .Include() entity. Is it possible... or I don't know how to do it?
All my data are accessed through entity framework with appContext (the context used to get entities from database). Can I achieve what I want through entity framework core (lambda expression is preferred over Linq if possible), or should I use ADO.NET library (which I know how to execute custom queries).
P.S. I want to get data only to show in the view... no edit is needed.
You don't actually need to include here at all, as far as I can tell. Whenever you use data from a nav property, EF will go get the data from that table, as best it can filter it.
var CategoriesUnderParent = AppContext.ArticleCategories
.Where(c => c.ArticleCategoryParent == {parent});
foreach(var category in CategoriesUnderParent)
{
var ArticlesAllowed = category.ArticleCategoryRelations
.Where(acr => acr.Article.Approved == 1).Select(a => a.Article);
var ArticlesPicked = ArticlesAllowed
.OrderByDescending(ar => ar.ArticleDate)
.Take(2);
// Do something with your data
}

Retrieving Array or list from object in database

I'm having trouble when I pull an object from the database with getting the framework to also get an an array in the object as well. I found that for sub objects the .Include("subobject") seems to work, but I can't get it to work for arrays or lists.
My Model:
public class RunData
{
[Key]
[Required]
public int id { get; set; }
public List<RunElement> Runs { get; set; }
public string[] DataLabels { get; set; }
}
List of Entities:
public class ProgramEntities:DbContext
{
public DbSet<RunData> RunData { get; set; }
public DbSet<RunElement> RunElement { get; set; }
}
Controller Code:
public ViewResult Details(int id)
{
RunData rundata = (from RunData in db.RunData.Include("Runs").in where RunData.id == id select RunData).First();
return View(rundata);
}
I did have all kinds of trouble with it not returning the list of Runs objects, but when I did the .Include("Runs") that fixed the problem. So, now my trouble is the DataLabels string array. If I try .Include("DataLabels") the program fails and says:
A specified Include path is not valid. The EntityType
'Program_Dataviewer.Models.RunData' does not declare a navigation
property with the name 'DataLabels'.
I have searched online some, I'm not seeing any clear cut answers. Thank you for the help.
You can not have collections of primitives in your data model, since each collection must be mapped to a table in the relational space (think about it - how is the database going to organize/save your collection?). What you can do is introduce a table / entity for DataLabels, e.g. something like this:
public class RunData
{
[Key]
[Required]
public int id { get; set; }
public List<RunElement> Runs { get; set; }
public List<DataLabel> DataLabels { get; set; }
}
public class DataLabel
{
[Key]
public int id { get; set; }
public string LabelName { get; set; }
}

In ASP.NET MVC, How do I synchronise or combine two collections of different types

I have two collections, OrderItems and Items. The reason there are two is because in my model I have an Item which is fixed, and an OrderItem which relates to an Item and adds order specific information such as quantity and a property (OrderID) that relates back to my Order object.
In Entity Framework 4, in order to have a collection of objects in a model you need to relate back to that model in the collection type's object. This makes the OrderID property in Item necessary.
Here are the POCO's in code:
public class Order {
public int OrderID { get; set; }
public DateTime DatePlaced { get; set; }
public bool Filled { get; set; }
public string Comment { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem {
public int OrderItemID { get; set; }
public int OrderID { get; set; }
public int Quantity { get; set; }
public int ItemID { get; set; }
}
public class Item {
public int ItemID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Size { get; set; }
}
So now I have my models, and I want to display an Order Details page. I can send over the Order to the ViewModel (MVC 3) or ViewData (MVC 2/1) from the controller which works fine, and I can send a collection of Item as well, but how do I display a list of Items that contain both the Item properties and the Quantity from the OrderItem? Since both are collections, I could OrderBy ItemID and loop over both at the same time in the view, but that seems really messy.
I fiddled with creating an anonymous class that combined both Items and Order but that didn't really work when it came to combining the collections.
I basically want to loop through each Item and OrderItem and display the following properties, without doing it in the view:
Name
Description
Size
Quantity
Ideas?
I believe, forgive me if I'm wrong, but with EntityFramework (assuming code first, but I think db first you can still do it) you can set the following:
public class OrderItem {
public int OrderItemID { get; set; }
public int OrderID { get; set; }
public int Quantity { get; set; }
public virtual Item Item { get; set; }
}
And then when you refer to Item.Name etc. it will lazily load the data in. I don't have access at the moment to test.
Hope this helps, or at least guides you in the right direction.
Edit
Thinking about it, I believe if you're doing db first then as long as you've got the relationship defined in the database then you should be able to access the associated Item from the OrderItem through the relationship property.
Is Item not supposed to derive from OrderItem and you only show Item which has all info for OrderItem and a bit more?
So you will only get the items from database and display using templates, ...
public class OrderItem {
public int OrderItemID { get; set; }
public int OrderID { get; set; }
public int Quantity { get; set; }
public int ItemID { get; set; }
}
public class Item : OrderItem {
// public int ItemID { get; set; } not anymore needed
public string Name { get; set; }
public string Description { get; set; }
public int Size { get; set; }
}

Categories

Resources