I have a 0-to-many relationship between Product and Category, configured as follows:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int? CategoryId { get; set; }
public Category Category { get;set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
When I try to manipulate data (and save), I have a strange behaviour with the CategoryId
// ...
var cat1 = this.Context.Categories.Find(1);
var cat2 = this.Context.Categories.Find(2);
var product1 = new Product();
product1.Name = "Test";
product1.Category = cat1; // the CategoryId property is NOT set
this.Context.Products.Add(product1); // the CategoryId property is set
this.Context.SaveChanges();
product1.Category = cat2; // the CategoryId property is NOT updated
this.Context.SaveChanges(); // the CategoryId property is updated
Is this behaviour correct? Because I would have expected that, once in tracker, the CategoryId field to be updated when the Category field is updated...
Am I wrong or am I doing something wrong? I cannot find anything in the docs about this...
Thanks in advance
This is the correct behavior. Entity is updated only after SaveChanges.
But if you need to update CategoryId immediately
product1.CategoryId = cat2.Id;
and it is a better way to update. Sometimes you will need to add after this , before SaveChanges:
Context.Entry(product1).State = EntityState.Modified;
And by the way, to get 0-to-many you have to fix your Category class
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Related
I have two tables with one-to-one relationship.
public class Name
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Category { get; set; }
public int? NameId { get; set; }
[ForeignKey("NameId ")]
public virtual Name Name { get; set; }
}
I already have data in those tables.
I know the database relations are not supported to be changed.
Is it possible to change one-to-one relationships to many-to-many relationships?
What is the most suitable approach to overcome this requirement?
Yes, you can still change that, using migrations.
Step 1 is to create a linking table, like NameCategories, which looks something like this:
public class NameCategories
{
public int Id { get; set; }
public int NameId { get; set; }
public Name Name { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Step 2 is to reference this table in the tables you already have. In Name it would look like this
public class Name
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<NameCategory> Categories { get; set; }
}
Step 3 is to add a migration. You'll have some AddColumn() and some DropColumn() statements. In between them, when all the add stuff was executed but the drops not yet, you can add SQL code to carry over all the existing relations into the newly created table. After that, the old data will be deleted by the DropColumn() code. In your example, this would look something like this
INSERT INTO NameCategories (NameId, CategoryId)
SELECT (n.Id, c.Id) FROM Names n
JOIN Categories c on c.NameId = n.Id
WHERE ..
You can execute the SQL in the migration like this:
var sql = #"...";
Sql(sql);
I hope this helps you out!
I have the following class:
public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
[MaxLength(255)]
public string Name { get; set; }
[Required]
public ItemCategory Category { get; set; }
}
and an ItemCategory class:
public class ItemCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public string Name { get; set; }
}
I want each item to have one category and a category to be able to have many items. I am using code first and so far everything looks good - using migrations EF created an "Items" and "ItemCategories" tables with the appropriate foreign keys ("Items" table has a column "CategoryID" that references the "ID" column of the "ItemCategories" table). My problem is that if I have a category of, say, "Book" with ID 1 and I add a new Item with category "Book" in the "ItemCategories" a second entry is inserted that has ID 2 and name "Book". I want to change this behaviour to the following:
When a new Item is added, check if its Category.Name already exists in "ItemCategories". If it does set the "CategoryID" to its ID. If it does not - add a new record to "ItemCategories" and set "CategoryID" to its ID.
How do I achieve that?
I would create a function that gets or creates the unique ItemCategory. You did not post the full code so I am not sure how your repository looks like. But here is an idea:
// assuming _dbServices is the injected dbcontext
public void AddItem()
{
var item = _dbServices.Context.Entry(new Item()
{
Name = "First book",
Category = GetOrCreateItemCategory(new ItemCategory() { Name = "Book" })
}).State = EntityState.Added;
_dbServices.Context.SaveChanges();
}
public ItemCategory GetOrCreateItemCategory(ItemCategory category)
{
var findCategory = _dbServices.Context.Set<ItemCategory>().Where(x => x.ID == category.ID).FirstOrDefault();
if (findCategory == null)
{
_dbServices.Context.Entry(category).State = EntityState.Added;
_dbServices.Context.SaveChanges();
return category;
}
return findCategory;
}
I havent tested this but should be ok. If not, please comment and I would fix it.
Also I would create unique index for ItemCategory.Name field.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ItemCategory>()
.HasIndex(p => new { p.Name })
.IsUnique(true);
}
You must define foreign key property in the dependent entity class And when you add a new item, set the CategoryID instead of Category .
public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
[MaxLength(255)]
public string Name { get; set; }
public int CategoryID { get; set; }
public ItemCategory Category { get; set; }
}
Use a navigation property in the Item class.
public class ItemCategory
{
[Key]
public Guid ID { get; set;}
public string Name { get; set; }
}
public class Item
{
[Key]
public Guid ID { get; set; } // Primary Key
[Required]
public string Name { get; set; }
public virtual ItemCategory ItemCategory { get; set; } //Navigation Property
}
I'm having trouble adding multiple entities with multiple children at once in foreach loop.
Its obvious ef cannot track ids in foreach loop. But maybe there are other solutions that you can guide me.
The error when I tried to add multiple entities with a child is:
The instance of entity type cannot be tracked because of another instance
with the same key value for {'Id'} is already being tracked. When
attaching existing entities, ensure that only one entity instance with
a given key value is attached.
For example:
public class Order
{
public Order()
{
OrderDetails = new HashSet<OrderDetail>();
}
[Key]
public int Id { get; set; }
[StringLength(50)]
public string Code { get; set; }
public int? CompanyId { get; set; }
public int? PartnerId { get; set; }
public decimal TotalNetPrice { get; set; }
public decimal TotalPrice { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
[ForeignKey("PartnerId")]
public virtual Partner Partner { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
[Key]
public int Id { get; set; }
public int OrderId { get; set; }
[StringLength(50)]
public string Code { get; set; }
public int LineNumber { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
[ForeignKey("OrderId")]
public virtual Order Order { get; set; }
[ForeignKey("ProductId")]
public virtual Product Product { get; set; }
}
Here is my code in the method:
foreach (var order in orderList)
{
// consider we create/cast object to Order class.
_orderService.Add(order);
// in here we don't have id, because we didn't call savechanges.
foreach(var orderDetail in order.orderDetailList)
{
// consider we create/cast object to OrderDetail class.
orderDetail.orderId = order.Id;
// in here, order id is always 0 as expected.
_order.OrderDetails.Add(orderDetail);
}
}
try
{
await uow.SaveChangesAsync();
}
catch(Exception exception)
{
var msg = exception.Message;
}
I tried to use [DatabaseGenerated(DatabaseGeneratedOption.Identity)] attribute for the identity columns.
According to documentation (https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations):
Depending on the database provider being used, values may be generated
client side by EF or in the database. If the value is generated by the
database, then EF may assign a temporary value when you add the entity
to the context. This temporary value will then be replaced by the
database generated value during SaveChanges().
So it should give at least temp id to track it. But it didn't work with my case.
I also tried the same approach on model creating a part in context. But again the same result. No success.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.Property(x => x.Id)
.ValueGeneratedOnAdd();
}
It seems like the best solution is to make a transaction and save order before details and get real id and then add details. But it has performance issues as you know.
I'm wondering if there is any other best practice for that issue?
Thank you.
Try this:
foreach (var order in orderList)
{
_orderService.Add(order);
foreach(var orderDetail in order.orderDetailList)
{
// Add reference Of Order to OrderDetails, not an id
orderDetail.Order = order;
_order.OrderDetails.Add(orderDetail);
}
}
In this case EF will know how to connect Order and OrderDetail on SaveChangesAsync
I have a DbContext which I via the developer command prompt and creating a migrations schema turn in to my database. But if you look at the product object I have a dictionary object named Parts. That property does not get added to the Product table when the database is updated in the command prompt. I don't even know if it is possible what I am trying to do.
I want to add a table in the database named Parts and then add a foreign key to the Product table which connects the Parts dictionary object in the Product table, and the the new Parts table. Is this possible with Entity Framework Core?
public class ShoppingDbContext : IdentityDbContext<User>
{
public ShoppingDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
Dictionary<string, Part> Parts { get; set; }
}
EF Core can't currently map a dictionary property directly. If you want to create an association between Products and Parts, then define each of them as an entity. You can then create navigation properties between them--a reference from Part to the Product which it belongs, and a collection of Parts on Product. For example:
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
public ICollection<Part> Parts { get; set; }
}
public class Part
{
public int PartId { get; set; }
public int ProductId { get; set; }
public Product Product { get; set;}
}
Part also defines a property ProductId that acts as the FK to the Product entity. You don't need to add that property--EF will simulate it for you if you don't want it, but usually it is easier to deal with entities if the FK is mapped to a property.
Relationships are tracked through object references instead of foreign key properties. This type of association is called an independent association.
More Details Here:
https://msdn.microsoft.com/en-us/data/jj713564.aspx
Sample code:
public partial class Product
{
public Product()
{
this.Parts = new HashSet<Part>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
public virtual ICollection<Part> Parts { get; set; }
}
Basically like what Arthur said, EF Core does not support it yet.
However, another way is to create a composite table should you want to or if it's viable for your use.
Here's a simple example:
// -------------- Defining BrandsOfCategories Entity --------------- //
modelBuilder.Entity<BrandCategory>()
.HasKey(input => new { input.BrandId, input.CatId })
.HasName("BrandsOfCategories_CompositeKey");
modelBuilder.Entity<BrandCategory>()
.Property(input => input.DeletedAt)
.IsRequired(false);
// -------------- Defining BrandsOfCategories Entity --------------- //
public class BrandCategory
{
public int CatId { get; set; }
public int BrandId { get; set; }
public DateTime? DeletedAt { get; set; }
public Category Category { get; set; }
public Brands Brand { get; set; }
}
The DeletedAt is optional of course. This handles M-M Relationships.
I had the same issue, I resolved it by removing the keyword virtual on the navigation properties and with in the ApplicatinDbContext
I am learning Entity Framework in asp.net mvc application. I have 3 models -
AppModel, CategoryModel and App_CategoryModel (to specify many to many relationship between AppModel and CategoryModel). A snippet of this is:
public class CategoryModel
{
[Key]
public int id { get; set; }
public string Name {get; set; }
public virtual ICollection<App_CategoryModel> mapping { get; set; }
}
public class AppModel
{
[Key]
public int id { get; set; }
public string Name { get; set; }
public virtual ICollection<App_CategoryModel> mapping { get; set; }
}
public class App_CategoryModel
{
[Key]
public int id {get; set;}
public int AppId {get; set; }
public int CategoryId {get; set; }
public virtual AppModel App {get; set;}
public virtual CategoryModel Category {get; set;}
}
I was following 'Code-first' approach and the tables got created successfully. But, now I am stuck at how to populate and display this information.
I have the following as input data:
List<AppModel>
List<CategoryModel> and Dictionary<"appname", List<CategoryModel>>
How do I move on from here so that I can update the mapping table?
Also, wanted to understand whether this is the correct approach to represent data. Since an App can have multiple categories - I expect the output as a collection of unique Apps along with a list of categories for each app, something like:
Dictionary<AppModel, List<CategoryModel>>
Edit:
This is what I tried as per suggestion from smoksnes-
List<CategoryModel> cat_list = new List<CategoryModel>();
CategoryModel c1 = new CategoryModel();
c1.Name = "string1";
cat_list.Add(c1);
CategoryModel c2 = new CategoryModel();
c2.Name = "string2";
cat_list.Add(c2);
List<AppModel> app_list = new List<AppModel>();
AppModel a1 = new AppModel();
a1.Name = "app1";
app_list.Add(a1);
AppModel a2 = new AppModel();
a2.Name = "app2";
app_list.Add(a2);
a1.mapping.Add(c1);
a1.mapping.Add(c2);
a2.mapping.Add(c1);
a2.mapping.Add(c2);
db.categories.AddRange(cat_list);
db.apps.AddRange(app_list);
db.SaveChanges();
After this, EF worked as expeted - 2 categories , 2 apps and 4 entries in mapping table.
Although this worked, but not sure who is stopping EF to create 4 entries for categories?
Just as Barry O´Kane mentioned in your comment there's no reason to keep the App_CategoryModel model. EF will manage this for you. You should only keep it if it contains any extra information regarding the relation between the two tables. But according to your example, there's no reason to keep it.
public class CategoryModel
{
public CategoryModel()
{
AppModels = new List<AppModel>();
}
[Key]
public int id { get; set; }
public string Name {get; set; }
public virtual ICollection<AppModel> AppModels { get; set; }
}
public class AppModel
{
public AppModel()
{
// Not sure if this is actually needed anymore. But EF usually adds it.
CategoryModels = new List<CategoryModel>();
}
[Key]
public int id { get; set; }
public string Name { get; set; }
public virtual ICollection<CategoryModel> CategoryModels { get; set; }
}
And regarding your question about representation, I don't think it's necessary. Since the AppModel already has the connected CategoryModel on it's model there's no reason for a Dictionary. You can store it in a List<AppModel> instead.
IList<AppModel> myApps = context.AppModels.ToList();
foreach (var myApp in myApps)
{
Console.Writeline("App {0} has the following categories:", myApp.id);
foreach (var category in myApp.CategoryModels)
{
Console.Writeline(category.Name);
}
}
And when you want to add a category to an app:
// I don't know how you create your Context, so below it's just called context.
var newCategoryModel = new CategoryModel
{
Name = "SO is awesome!"
};
var appModel = context.AppModels.FirstOrDefault(x => x.id == 1);
appModel.CategoryModels.Add(newCategoryModel); // EF will automatically set foreign keys for you...
context.SaveChanges();
And if you want to make sure that no category is added twice:
public void AddCategory(int appId, string categoryName)
{
using(var context = new DbContext())
{
var category = context.CategoryModels.FirstOrDefault(x => x.Name == categoryName);
if(category == null)
{
// Only create new CategoryModel if it doesn't exist.
category = new CategoryModel
{
Name = categoryName
};
}
var appModel = new AppModel
{
id = appId
};
// Attach to save on db-trip
context.AppModels.Attach(appModel);
//TODO: Possibly check if this particular appModel already has this category?
appModel.CategoryModels.Add(category);
context.SaveChanges();
}
}