I have two tables Category and Document. See relationships in picture
See picture
I wrote the following query to select data from both tables based on relationship
public List<DocumentViewModel> All()
{
var docs = _context.Document.ToList();
List<DocumentViewModel> docList = docs.Select(x => new DocumentViewModel
{ DocumentId = x.DocumentId,
DocumentPath = x.DocumentPath,
CategoryId = x.CategoryId,
CategoryName = x.Category.CategoryName }).ToList();
return docList;
}
when this function is called , I get the following error
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Here are my modals
document
public class Document
{
[Key]
public int DocumentId { get; set; }
[Required]
public string DocumentPath { get; set; }
public Nullable<int> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
Category
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual ICollection<Document> Documents { get; set; }
}
DocumentViewModel
public class DocumentViewModel
{
public int DocumentId { get; set; }
public string DocumentPath { get; set; }
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
}
Any Idea where am doing mistake?
In this case there is no reason to get a List in memory and then do the projection, you can do this directly from EF instead. Even if there is no relationship defined EF will return null for CategoryName if you project the the results. If you go to memory first then an NRE is expected if there is no Category relationship.
public List<DocumentViewModel> All()
{
return _context.Document.Select(x => new DocumentViewModel
{ DocumentId = x.DocumentId,
DocumentPath = x.DocumentPath,
CategoryId = x.CategoryId,
CategoryName = x.Category.CategoryName}).ToList();
}
Original reason why it is failing.
There is at least one entity that does not have a corresponding relationship with Category.
You do not have lazy loading enabled (which is a good thing) and if that is the case you should use Include to return the relationship.
Related
I have a web api where I am trying to get sum and count of a related table. Using .net core 3 and EF Core 3.1.8.
This is what I have tried:
_context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings.GroupBy(g => g.Bookid).Select(s => new { SumAllVotes = s.Sum(item => item.Rating) }))
.ToListAsync();
But I just get an error message. (see below).
I find it difficault debugging with EF Core as I dont know where it is going wrong. Have been trying a couple of hours, but whatever I write I get the same error message.
Thought maybe you guys were able to see what was wrong.
What I want
I am trying to get Sum of all Rating inside table Ratings.
Rating contains only 0 or 1. And I am trying to sum ratings on each bookid. I wanted to have it in this class public int SumAllVotes { get; set; }.
Because I list out all Books...and one of the properties will then be SumAllVotes. (And also CountAllVotes, when I have finished this problem).
By the end I will have a SumAllVotes and CountAllVotes and can calculate the percentage of how many have pressed "1".
Error message:
An unhandled exception occurred while processing the request.
InvalidOperationException: Lambda expression used inside Include is
not valid.
Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ProcessInclude(NavigationExpansionExpression
source, Expression expression, bool thenInclude)
What I have tried:
[HttpGet]
public async Task<ActionResult<IEnumerable<Books>>> GetBooks()
{
Guid userid = Guid.Parse(this.User.FindFirst(ClaimTypes.NameIdentifier).Value);
return await _context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings.GroupBy(g => g.Bookid).Select(s => new { SumAllVotes = s.Sum(item => item.Rating) }))
.ToListAsync();
}
Books and Ratings are defined as -
public partial class Books
{
public Books()
{
Bookmarks = new HashSet<Bookmarks>();
Comments = new HashSet<Comments>();
Favourites = new HashSet<Favourites>();
BookCategories = new HashSet<BookCategories>();
Resources = new HashSet<Resources>();
Ratings = new HashSet<Ratings>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public byte Scaleoffun { get; set; }
public byte Scaleoflearning { get; set; }
public int? Goal { get; set; }
public int? Secondgoal { get; set; }
public int? Thirdgoal { get; set; }
public int? Subjectid { get; set; }
public int? Categoryid { get; set; }
public string Language { get; set; }
public string Estimatedtime { get; set; }
public string Image { get; set; }
public int? File { get; set; }
public int? Ownerid { get; set; }
public DateTime Createdon { get; set; }
public DateTime? Lastmodifiedon { get; set; }
public string Active { get; set; }
public string Url { get; set; }
public Guid Userid { get; set; }
public byte? Grade { get; set; }
[NotMapped]
public int SumAllVotes { get; set; }
[NotMapped]
public int CountAllVotes { get; set; }
public virtual Categories Category { get; set; }
public virtual Curriculum GoalNavigation { get; set; }
public virtual Users Owner { get; set; }
public virtual Curriculum SecondgoalNavigation { get; set; }
public virtual Subjects Subject { get; set; }
public virtual Curriculum ThirdgoalNavigation { get; set; }
public virtual ICollection<Bookmarks> Bookmarks { get; set; }
public virtual ICollection<Comments> Comments { get; set; }
public virtual ICollection<Favourites> Favourites { get; set; }
public virtual ICollection<BookCategories> BookCategories { get; set; }
public virtual ICollection<Resources> Resources { get; set; }
public virtual ICollection<Ratings> Ratings { get; set; }
}
public partial class Ratings
{
public int Id { get; set; }
public int? Bookid { get; set; }
public string Type { get; set; }
public Int16? Rating { get; set; }
public Guid Userid { get; set; }
public string Subject { get; set; }
public DateTime Createdon { get; set; }
public DateTime? Modifiedon { get; set; }
public byte? Active { get; set; }
public virtual Books Book { get; set; }
//public virtual Users User { get; set; }
}
These are some other solutions I have tried, but got the same error message:
.Include(r=> r.Ratings.Sum(i=>i.Rating))
and
.Include(r => new { m = r.Ratings.GroupBy(g => g.Bookid) })
You don't need to group child entities by parent's Id. When you Include one-to-many child entities, they are added to their parent's child list, and hence grouped by their parent's identity, based on the relationship between them. All you need to do is tell EF what values you want from that child list.
Change your query to -
_context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings)
.Select(p => new
{
// set ALL the primitive properties from Books entity
Id = p.Id,
Title = p.Title,
// etc ...
// set the computed properties
CountAllVotes = p.Ratings.Count,
SumAllVotes = p.Ratings.Sum(x => x.Rating)
// set the related entities
BookCategories = p.BookCategories,
Resources = p.Resources
})
.ToListAsync();
AutoMapper has a ProjectTo method that generates the required query and does the projection (the Select part) automatically. You can use that to avoid the hassle of setting all those properties manually.
I suggest you don't use Include with Select. Read article how to make queries with Projection (Select). Note, that Rating.Rating is nullable and you need to handle this. Here is a possible code sample:
var view = await _context.Books
.Where(your condition)
.Select(item => new
{
//Todo: fill other props
SumAllVotes = item.Ratings.Sum(rating => (Int16?) rating.Rating),
CountAllVotes = item.Ratings.Count,
})
.ToListAsync()
I have two models Invoice and Client which has one to many relation. My problem is when I try to create a new invoice the modelstate becomes false because the required fields of the Client object are not filled. I tried to set default values e.g. int property {get; set;} = 1; but this causes to override the property value when I try to retrieve the relational model. I'm new to asp.net any help would be appreciated.
Here is my Client model code:
public class Client
{
public int ClientId { get; set; }//Primary key
[Required]
[Display(Name = "Client/Company Name")]
public string Name { get; set; }
//.....
public List<Invoice> Invoices { get; set; }
}
Invoice Model:
public class Invoice
{
public int InvoiceId { get; set; }//Primary key
[Required]
[Display(Name = "Client/Company Name")]
public int ClientId { get; set; }//Foreign key
public Client client { get; set; }
//....
}
My code to save an invoice:
[HttpPost]//Save quote & redirect to edit quote
public IActionResult Create(Invoice quote)
{
ViewData["Title"] = "Create Quotation";//Set title of the view!
if (ModelState.IsValid)//If quote is validated to true.
{
_context.Invoices.Add(quote);//insert quote
_context.SaveChanges();
//Redirect to edit page to add items to the invoice
return RedirectToAction("Edit", new { id = quote.InvoiceId });
}
//...
}
My code to retrieve invoices with their client names
public IActionResult Index()
{
ViewData["Title"] = "Quotations";//Set title of the view!
//Return with array of all quotations.
return View(_context.Invoices.Include(i => i.client).Where(i => i.isQuote == true).ToArray());
}
Any hints or help would be appreciated.
Found a simpler solution:
Just add virtual keyword and GG
in Invoice Model:
public class Invoice
{
public int InvoiceId { get; set; }//Primary key
[Required]
[Display(Name = "Client/Company Name")]
public int ClientId { get; set; }//Foreign key
public virtual Client client { get; set; }
//....
}
Hi every one I am new to ASP.Net Web API and I want to Post JSON array data any get there response.
My JSON POST Array format is
{
"User_Id":"admi12n#1234","Key_Code":"3F-47-AB-84-9F-EB-D6-6B-9C-62-CC-85-98-4D-28-6B",
"ProductDetails": [
{"Product_Id":"ELT-7035","Price":"999","Quantity":"5"},
{"Product_Id":"ELT-1254","Price":"1024","Quantity":"3"}
]
}
And I want response as follows
{
"User_Id":"admi12n#1234","Key_Code":"3F-47-AB-84-9F-EB-D6-6B-9C-62-CC-85-98-4D-28-6B",
"OrderID":"Ord-021","Name":"Sabyasachi"
"ProductDetails": [
{"Product_Id":"ELT-7035","Price":"999","Quantity":"5"},
{"Product_Id":"ELT-1254","Price":"1024","Quantity":"3"}
]
}
I generate OrderID as Random and Name from posted User_Id. Here I want to post multiple product in one order.
My Order class is as follows
public class Order
{
[Key]
public long ID { get; set; }
public string Order_Id { get; set; }
public string Product_Id { get; set; }
public long Quantity { get; set; }
public long Amount { get; set; }
public string User_Id { get; set; }
public string Key_Code { get; set; }
public DateTime Order_Date { get; set; }
public DateTime Modified_Date { get; set; }
}
And my Product class as follows
public class Product
{
[Key]
public int Id { get; set; }
public string Product_Code { get; set; }
public string Product_Name { get; set; }
public string Product_Category { get; set; }
public string Product_Description { get; set; }
public string Quantity { get; set; }
public string Price { get; set; }
public string Image { get; set; }
public DateTime Created_Date { get; set; }
public DateTime Modified_Date { get; set; }
}
I am not able to ind the best way to post the order
public Order Add(Order odrerDetails) //This will not give array of data for products
{
using (var context = new EcommerceDBContext())
{
odrerDetails.Order_Id = Helper.Random(7); //Generate random orderID from my class
odrerDetails.Created_Date = DateTime.Now;
odrerDetails.Modified_Date = DateTime.Now;
//How to Save other details
context.objOrderListing.Add(odrerDetails);
context.SaveChanges();
return odrerDetails;
}
}
In API controllers my code is as follows
public HttpResponseMessage PostOrder([FromBody] Order_Listing orderData)
{
orderData = repository.Add(orderData);
var response = Request.CreateResponse<Order_Listing>(HttpStatusCode.Created, orderData);
string uri = Url.Link("DefaultApi", new { customerID = orderData.ID });
response.Headers.Location = new Uri(uri);
return response;
}
Please help me how to achieve this.
There are several issues with your code:
Your Order and Product classes do not reflect the structure of
your JSON.
The Order class contains product details in a 1:1
relationship. Based on the JSON I assume you want a 1:n relationship.
Properties in your JSON need to have the same name as
in your classes or they won't be mapped.
Change your classes to the following and it should work.
Of course you could also change the property names in your JSON.
If you can't or don't want to change your property names, consider using DTOs
public class Order
{
public string User_Id { get; set; }
public string Key_Code { get; set; }
public string OrderID { get; set; }
public string Name { get; set; }
public List<Product> ProductDetails { get; set; }
// add the rest of your properties
}
public class Product
{
public string Product_Id { get; set; }
public string Price { get; set; }
public string Prd_Qty { get; set; }
// add the rest of your properties
}
Update: added code for Add method and Api method
Your Add method would look like this:
public Order Add(Order orderWithDetails)
{
using (var context = new EcommerceDBContext())
{
orderWithDetails.Order_Id = Helper.Random(7); //Generate random orderID from my class
orderWithDetails.Created_Date = DateTime.Now;
orderWithDetails.Modified_Date = DateTime.Now;
context.objOrderListing.Add(orderWithDetails);
// Save each Product
foreach (var detail in orderWithDetails.ProductDetails)
{
//whatever you need to do in your db-context
context.objOrderDetails.Add(detail); // just an example
}
context.SaveChanges();
return orderWithDetails;
}
}
The signature of your Api method looks wrong. What is Order_Listing? This should be Order, unless it's a DTO, in wich case you need a method to get an Order from Order_Listing.
public HttpResponseMessage PostOrder([FromBody] Order orderData)
{
orderData = repository.Add(orderData);
var response = Request.CreateResponse<Order_Listing>(HttpStatusCode.Created, orderData);
string uri = Url.Link("DefaultApi", new { customerID = orderData.ID });
response.Headers.Location = new Uri(uri);
return response;
}
A few more remarks:
If it is indeed a 1:n relationship, you probably need a property Product.OrderId.
The Order class should not have any reference to Product except for the list.
Quantity and Price should most likely not be String but numerical values, e.g. decimal.
If Order.ID is your primary key, then having Order.Order_ID is really confusing. Consider renaming it to Order.Order_Number.
public class Order
{
public string User_Id { get; set; }
public string Key_Code { get; set; }
public string OrderID { get; set; }
public string Name { get; set; }
public Product[] ProductDetails { get; set; }
}
I'm having a little of trouble with the following classes:
public class TwoVariableDetails
{
public TwoVariableDetails()
{
MovementsPerBlocks = new HashSet<MovementsRow>();
MovementsPerShiftTypes = new HashSet<MovementsRow>();
MovementsPerMachines = new HashSet<MovementsRow>();
MovementsPerShifts = new HashSet<MovementsRow>();
}
[Key]
public Guid TwoVariableDetailsId { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[Required]
[MaxLength(1000)]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
public virtual ICollection<MovementsRow> MovementsPerBlocks { get; set; }
public virtual ICollection<MovementsRow> MovementsPerShiftTypes { get; set; }
public virtual ICollection<MovementsRow> MovementsPerMachines { get; set; }
public virtual ICollection<MovementsRow> MovementsPerShifts { get; set; }
}
[Table("Movement")]
public class MovementsRow
{
public MovementsRow()
{
MovementsCells = new HashSet<MovementsCell>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[CsvField(Ignore = true)]
public Guid MovementId { get; set; }
[Required]
public int RowNo { get; set; }
[Required]
[CsvField(Ignore = true)]
public Guid ModelId { get; set; }
[ForeignKey("ModelId")]
[CsvField(Ignore = true)]
public virtual TwoVariableDetails Model { get; set; }
[TypeConverter(typeof(MovementsCellTypeConverter))]
public virtual ICollection<MovementsCell> MovementsCells { get; set; }
}
[Table("MovementCell")]
public class MovementsCell
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[CsvField(Ignore = true)]
public Guid CellId { get; set; }
[Required]
public int ColumnNo { get; set; }
[Required]
public int Count { get; set; }
[Required]
[CsvField(Ignore = true)]
public Guid MovementId { get; set; }
[ForeignKey("MovementId")]
[CsvField(Ignore = true)]
public virtual MovementsRow Model { get; set; }
}
When I try to save it to the database I get the following error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Movement_dbo.TwoVariableDetails_ModelId". The conflict occurred in database "aspnet-GreenCranes.UI-20130516", table "dbo.TwoVariableDetails", column 'TwoVariableDetailsId'.
The statement has been terminated.
This is the code I'm using for saving:
twoVariableDetails.TwoVariableDetailsId = Guid.NewGuid();
_context.TwoVariableDetailsModels.Add(twoVariableDetails);
_context.SaveChanges();
My table looks like this:
Movement
- Column
- MovementId
- RowNo
- ModelId(FK, uniqueidentifier, not null)
- TwoVariableDetails_TwoVariableDetailsId(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId2(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId3(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId4(FK, uniqueidentifier, null)
- Keys
- FK_dbo.Movement_dbo.TwoVariableDetails_ModelId
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId1
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId2
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId3
I'm not sure what is the problem with my approach. Should I change the MovementsRow class to have four Model properties and four modelid fk and then use InverseProperty attribute?
MovementsRow.Model belongs to another relationship than the four collections in TwoVariableDetails. That's the reason why you don't have four, but five foreign keys in the database table. When you insert twoVariableDetails into the DB and it contains a MovementRow instance in one of the collections EF expects that its ModelId is set to a Guid that references an existing TwoVariableDetails row - which it doesn't apparently. Hence the exception.
Should I change the MovementsRow class to have four Model properties
and four modelid fk and then use InverseProperty attribute?
I'd say yes. It's probably the best solution. The alternative is to have no Model property at all in MovementRow. It's working but you would not be able to navigate from MovementRow to TwoVariableDetails then.
Your FK_dbo.Movement_dbo.TwoVariableDetails_ModelId is being violated, simply put - the ModelId that the Movement record is using doesn't yet exist in TwoVariableDetails.
If you wanted to keep it simple, and transactional, then you could use TransactionScope along with your database context, save the TwoVariableDetails first in the transaction, and then the records that relate back to it:
using (var context = new MyDbContext())
using (var tranScope = new TransactionScope(TransactionScopeOption.Required) {
// don't save the Movement records yet
twoVariableDetails.TwoVariableDetailsId = Guid.NewGuid();
_context.TwoVariableDetailsModels.Add(twoVariableDetails);
_context.SaveChanges();
// now create the movement records, add them to twoVariableDetails
...
_context.SaveChanges();
// commit the transaction
scope.Complete();
}
This is a follow up to an earlier question.
I want to populate a ViewModel, which has 3 properties, and one list of Occ class (which also has 3 properties.
public class RatesViewModel
{
public string TypeName { get; set; }
public long TypeID { get; set; }
public int TypeCount { get; set; }
public virtual IQueryable<Occ> Occs { get; set; }
}
public class Occ
{
public string occ { get; set; }
public decimal ratetocharge { get; set; }
public int numOfOcc { get; set; }
public virtual RatesViewModel RatesViewModel { get; set; }
}
When I run the following Linq query in LinqPad:
var rooms = tblRoom
.GroupBy(p => p.tblType)
.Select(g => new
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count(),
Occs = rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
});
rooms.Dump();
...as before, it correctly returns the data model I'm looking for:
...and when I click on Occs it drills down into the Occs class:
The complete view in LinqPad is:
My query in Visual Studio is:
var rooms = dbr.Rooms
.GroupBy(p => p.RoomTypes).Select(g => new RatesViewModel
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count()
,
Occs = db.Rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new Occ
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
})
.ToList();
However when running this, I get an error:
The specified LINQ expression contains references to queries that are associated with different contexts.
I think I understand the error - but I'm not sure how to separate the query into 2 separate queries, and then join those query results together again to get my original results set.
My model classes are:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
public List<RoomType> Type { get; set; }
}
public class Rental
{
[Key]
public long rental_id { get; set; }
public long room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public virtual Room Room { get; set; }
}
public class Room
{
[Key]
public long room_id { get; set; }
public long type_id { get; set; }
public virtual RoomType RoomTypes { get; set; }
public virtual ICollection<Rental> Rentals { get; set; }
}
public class RoomType
{
[Key]
public long type_id { get; set; }
public string type_name { get; set; }
public IQueryable<Rates> Rates { get; set; }
public virtual ICollection<Room> Room { get; set; }
}
Can anyone help either review my query or models, so it works with one query, or show me how to separate the query into two, and then combine the result sets?
Thank you,
Mark
apitest.Models.RoomContext' does not contain a definition for 'Rates'...
(your comment on hydr's answer)
Well, there you go: not only two different context instances but two different context classes. I suspect your linqpad query was directly against the database connection, which means it used one linq-to-sql DataContext (created on the fly).
You need to use one context class (and one instance of it) in your query. And connect to it in Linqpad to make sure you test the same query provider as Visual Studio.
dbr and db seem to be two different instances of the same context. But in one query you should only use one context. So I would suggest the following:
Occs = dbr.Rates.Where(rt => rt.type_id == g.Key.type_id && ....
If this doesn't help can you quote the lines where you initialize the contexts?