Excuse my confusing title, but its not easy to say it in short.
I'm having problems defining a many-to-many relationship in my code first model.
This is my first project using the code first framework, and I need some help.
I got two models, Item and Trade. And the story behind it all is that im building a item trading website.
A trade involve one or many items beeing sent from person A to person B, and one or many items are sent back from person B to person A, completing the trade.
One item belongs to one trade only, but one trade can have many items.
This is how my models look so far.
Item
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public int TradeId { get; set; }
[ForeignKey("TradeId")]
public virtual Trade Trade { get; set; }
}
Trade
public class Trade
{
public int TradeId { get; set; }
public virtual ICollection<Item> ItemsToSend { get; set; }
public virtual ICollection<Item> ItemsToReturn { get; set; }
}
But when trying to run update-database I get the following error.
System.Data.SqlClient.SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Items_dbo.Trades_TradeId". The conflict occurred in database "MyDB", table "dbo.Trades", column 'TradeId'.
Any help is much appreciated, thanks!
I solved it for now by just adding a flat structure of items and trades. Like a many to many on items and trades.
Since I know the owner of each item, I can also sort out sender and returner by adding senderId and returnerId on the trade.
This is how my model looks like now.
Item
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public virtual ICollection<Trade> Trades { get; set; }
}
Trade
public class Trade
{
public int TradeId { get; set; }
public virtual UserProfile SendUser { get; set; }
public virtual UserProfile ReturnUser { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
This works for now..
This: "One item belongs to one trade only, but one trade can have many items." sounds like one-to-many?
If you can do without the back-reference from Item to Trade then the mapping will be a lot simpler.
This model will map without any configuration:
public class Trade
{
public int Id { get; set; }
public virtual ICollection<Item> ItemsToSend { get; set; }
public virtual ICollection<Item> ItemsToReturn { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
Inserting data using this code:
var trade = new Trade
{
ItemsToReturn = new [] { new Item{ Name = "Item to return 1"}, new Item{ Name = "Item to return 2"}},
ItemsToSend = new [] { new Item{ Name = "Item to send 1"}, new Item{ Name = "Item to send 2"} }
};
context.Trades.Add(trade);
context.SaveChanges();
Will produce an Item table looking like this:
And a Trades table containing a single row with a single Id column.
If you need to navigate from an Item to the Trade it belongs to you could do that doing a query.
E.g to get the Trade that Item with id = 3 belongs to you can do this:
using (var db = new TradingContext())
{
var tradeOfItem3 = db.Trades.
FirstOrDefault(t => t.ItemsToReturn.Any(i => i.Id == 3) || t.ItemsToSend.Any(i => i.Id == 3));
}
Related
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;
});
Update: At this moment I can assign only one product to Recipe. What I want to do is add access to all products from db in recipe (controller create) - here Im using public int ProductId but it allow to save only one product. I want to choose a few products from this list and save foreign keys in database. photo
I also tried add public List < int > in ProductId but I got error from entity framework.
I will be grateful for any help.
public class Recipe
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int ProductId { get; set; }
public List<Product> Products { get; set; }
public Recipe()
{
this.Products = new List<Product>();
}
}
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Recipe? Recipes { get; set; }
}
If you want to create a one-to-many relationship you are almost in the correct direction, but you should remove the public int ProductId { get; set; } and re-arrange as like as below example.
Say you have the following classes:
public class Recipe
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<Product> Products { get; set; } = new();
}
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Recipe Recipe { get; set; }
}
You can instantiate and use as per below:
public static void Main()
{
var recipe = new Recipe
{
Name = "My Recipe",
Products = new List<Product>
{
new Product { Name = "Product 1" },
new Product { Name = "Product 2" },
new Product { Name = "Product 3" }
}
};
recipe.Products.ForEach(product =>
{
Console.WriteLine(product.Name);
});
}
It sounds like you are looking for a many-to-many relationship rather than a one-to-many.
If you are using Code-First and EF6 or EF Core 5+ then you can use:
public class Recipe
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; } = new List<Product>();
}
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; } = new List<Recipe>();
}
To understand what is happening behind the scenes, EF should create a joining table called ProductRecipe or RecipeProduct which contains two FKs. ProductId and RecipeId. These will also form a composite PK for this table. Using this table, EF can associate one product to several recipes while also associating one recipe to the various products. In the object model you get the collection of products for each recipe, and the collection of recipes for each product.
With earlier versions of EF Core you were limited to having to declare this linking entity so you would have something more like:
public class Recipe
{
// ...
public virtual ICollection<ProductRecipe> ProductRecipes { get; set; } = new List<ProductRecipe>();
}
public class Product
{
// ...
public virtual ICollection<ProductRecipe> ProductRecipes { get; set; } = new List<ProductRecipe>();
}
public class ProductRecipe
{
[Key, Column(Order = 0)]
public int ProductId { get; set; }
[Key, Column(Order = 1)]
public int RecipeId { get; set; }
public virtual Product Product { get; set; }
public virtual Recipe Recipe { get; set; }
}
This approach is still an option in the other versions of EF, and is required if you want to support adding any additional fields to the joining table. For instance if you want to track things like CreatedDate/ModifiedDate etc. to record when a product was associated to a recipe etc. To expose that information to an application through EF, EF needs to know about the ProductRecipe as an entity. The trade off is that this approach is "messier" when usually you care about the Products for a Recipe etc. To get a list of products for a recipe you would need:
var products = context.Products
.Where(p => p.ProductRecipes.Any(pr => pr.RecipeId == recipeId)
.ToList();
or
var products = context.Recipies
.Where(r => r.RecipeId == recipeId)
.SelectMany(r => r.ProductRecipies.Select(pr => pr.Product).ToList())
.ToList();
vs. the implied joining table in the first approach:
var produts = context.Recipes
.Where(r => r.RecipeId == recipeId)
.SelectMany(r => r.Products)
.ToList();
... which is arguably easier to read.
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
}
}
};
I have a domain called Item and a domain called Category. I want to add the PK of the category to the Item but I am not sure how to do this in EF 6
public class Item
{
[Key]
public Guid Id { get; set; } = GuidCombGenerator.GenerateComb();
public string Name { get; set; }
public Category Category { get; set; }
}
public class Category
{
[Key]
public Guid Id { get; set; } = GuidCombGenerator.GenerateComb();
public string Name { get; set; }
public virtual ICollection<Item> Items { get; set; } = new List<Item>();
}
I thought I could do this
var item = new Item
{
CategoryId = dto.CategoryId ,
Name = dto.Name.Trim(),
};
dbContext.StorageItems.Add(item);
dbContext.SaveChanges();
I added another property called CategoryId and filled this in with the correct Id. However this goes and make a new Category entry instead of just linking the 2 up.
I thought I could do this but maybe I am mixing it up with NHibernate.
I then tried this (did not think this would work):
var item = new item
{
Category = new Category { Id = dto.CategoryId, },
Name = dto.Name.Trim(),
};
dbContext.StorageItems.Add(item);
dbContext.SaveChanges();
It does not like this at all.
The only option I can think of is going to the db and getting the Category object back and then adding it to Item but I would like to prevent trips to the db just to get back the id I already know.
Mark your Category virtual, create the CategoryId property, and then specify it's the foreign key for Category.
public class Item
{
[Key]
public Guid Id { get; set; };
public string Name { get; set; }
public Guid CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
}
With this, your first code (assign the Id directly) should work unless you "touch" the Category property or reference.
For this to work, make sure you haven't disabled proxies on the context
Also, make sure you don't set your Id properties to anything custom, let EF do its job
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; }
}