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
Related
I'm having a problem with many-to-many relationships in a pizzeria system I'm developing.
I have an entity called "Payament" that has a list of Pizzas and a list of Drinks.
ex:
public class Payament
{
public string? PayamentId { get; set; }
public virtual List<Pizza> Pizzas { get; set; }
public virtual List<Drink> Drinks { get; set; }
public string? CPFId { get; set; }
[JsonIgnore]
public virtual Client? Client { get; set; }
public double? TotalPay { get; set; }
public DateTime DateTransaction { get; set; }
public virtual StatusOrder StatusOrder { get; set; }
public Payament()
{
Pizzas = new List<Pizza>();
Drinks = new List<Drink>();
DateTransaction = DateTime.Now;
StatusOrder = StatusOrder.CARRINHO;
}
}
Also, I have two entities Pizza and Drink.
ex:
public class Pizza : IItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public double Value { get; set; }
[JsonIgnore]
public virtual List<Payament> Payament { get; set; }
public Pizza()
{
Payament = new List<Payament>();
}
}
public class Drink : IItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public double Value { get; set; }
[JsonIgnore]
public virtual List<Payament> Payament { get; set; }
public Drink()
{
Payament = new List<Payament>();
}
}
These classes are configured as many-to-many in OnModelCreating()...
Drink:
builder.HasMany(h => h.Payament)
.WithMany(d => d.Drinks);
Pizza:
builder.HasMany(h => h.Payament)
.WithMany(p => p.Pizzas);
Payament:
builder.HasMany(p => p.Pizzas)
.WithMany(p => p.Payament);
builder.HasMany(d => d.Drinks)
.WithMany(d => d.Payament);
Migrations were generated, all right. But when I go to send a payment, I get this error:
MySqlException: Cannot add or update a child row: a foreign key constraint fails (pizzaroller.drinkpayament, CONSTRAINT FK_DrinkPayament_Payament_PayamentId FOREIGN KEY (PayamentId) REFERENCES payament (PayamentId) ON DELETE CASCADE)
What I believe it could be:
If I'm not mistaken, a drinkpayament table and a pizzapayament table are created where the primary keys between the relations are joined.
And in this case, "Payament" has a primary key of type string, while items "Pizza" and "Drink" have a primary key of type int.
The conflict is likely to be found in this divergence. I could be wrong, of course.
So I would like to know how I can solve this problem. And also, if possible, tips on how I can work with existing items for sale and relate them to a payment.
Project is on github if you want to take a look:
https://github.com/newhobbye/pizza-roller-api
This project is for the exercise of knowledge and some patterns that I will implement to practice. I accept any kind of criticism! A big hug and many thanks!
I managed to solve.
I was resending the client entity in update. And I hadn't turned off the AsNoTracking() option;
In addition to this problem, I was also resending the items instead of associating the existing ones by id.
Thank you all for your help!
I have a Model Rental in my ASP.Net 4.5 MVC Project. Rental has 2 foreign keys ProfileId(Profile Model) & ItemId(Item Model). Both are set properly when the Rental record is created.
In Controller #1 if i run:
var rentals = GetRentals(6);
public IQueryable<Rental> GetRentals(int parent)
{
return db.Rentals.Where(t => t.ProfileId.Equals(parent));
}
var rentalArray = rentals.ToArray();
rentalArray has the proper item id and FK index, allowing me to run rentalArray[i].Item.Name Good Response Pic from Code Above
Now In controller #2 if i run
var rentals = GetRentals(6);
public IQueryable<Rental> GetRentals(int parent)
{
return db.Rentals.Where(t => t.ProfileId.Equals(parent));
}
var rentalArray = rentals.ToArray();
In this query i loose the item FK property of the Rental, though the ItemId is still coming in properly. I am unable to run rentalArray[i].Item.Name as Item is null.
Both are the same database record, Im just querying in a different Controller/Method. Picture of bad response
I cannot determine why Rental.Item property comes in null on the second query? Database Diagram
Rental Model:
public class Rental
{
public int RentalId { get; set; }
public int ItemId { get; set; }
[IgnoreDataMember]
public virtual Item Item { get; set; }
public int ProfileId { get; set; }
[IgnoreDataMember]
public virtual Profile Profile { get; set; }
public DateTime RequestDate { get; set; }
public bool? Accepted { get; set; }
public bool? Complete { get; set; }
public DateTime BeginRental { get; set; }
public DateTime EndRental { get; set; }
public int ChargeAmount { get; set; }
public bool Canceled { get; set; }
public string ChargeId { get; set; }
}
You can use include method
using System.Data.Entity;
and you can use it like this:-
return db.Rentals.include(tt => tt.Profile).include(ttt => ttt.Item).Where(t => t.ProfileId.Equals(parent));
Thanks to bash.d for finding issue.
Have you noticed that in your bad picture you have
LibLob.Models.Profile instead of a reference to an System.Data.Entity
object? You said your DBContext was the same?
Controller #1 was using a ApplicationDBContext while controller#2 was using ProxylessApplicationDbContext, resulting in Rental.Item appearing but being null in controller #2. Fixed by adding a second dbcontext and using that only for method to retrieve rentals.
I have a UserProfile class
[Key]
public int UserProfileId { get; set; }
public string AppUserId { get; set; }
...code removed for brevity
[Required]
public NotificationMethod NotificationMethod { get; set; }
public List<PrivateMessage> PrivateMessages { get; set; }
public List<Machine> OwnedMachines { get; set; }
public bool IsProfileComplete { get; set; }
public byte[] Avatar { get; set; }
public string AvatarUrl { get; set; }
public string GetFullName()
{
return $"{FirstName} {LastName}";
}
}
I also have a PrivateMessage class
public class PrivateMessage
{
[Key]
public int Id { get; set; }
public int MessageToUserId { get; set; }
public int MessageFromUserId { get; set; }
public DateTime DateSent { get; set; }
public string Message { get; set; }
}
I set up a simple test to pull the user profile out with various includes. The PrivateMessages always errors. Here is a sample method that errors.
public static UserProfile GetUserProfileIncluding(string appUserId)
{
using (RestorationContext)
{
//RestorationContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
return RestorationContext.MemberProfiles.Where(m => m.AppUserId == appUserId)
.Include(m=> m.PrivateMessages)
.FirstOrDefault();
}
}
The error noted is
InnerException {"Invalid column name 'UserProfile_UserProfileId'.\r\nInvalid column name 'UserProfile_UserProfileId'."} System.Exception {System.Data.SqlClient.SqlException}
Which I don't understand, neither table has a column "UserProfile_UserProfileId"
If I use the property OwnedMachines instead of PrivateMessages, it works perfectly fine (well not really, its only pulling in 4 records when there are 6 that match but I can figure that out later).
public static UserProfile GetUserProfileIncluding(string appUserId)
{
using (RestorationContext)
{
return RestorationContext.MemberProfiles.Where(m => m.AppUserId == appUserId)
.Include(m=> m.OwnedMachines)
.FirstOrDefault();
}
}
And you can see below, Machine is set up exactly like PrivateMessage, albeit it has two UserProfiles instead of one
public class Machine
{
[Key]
public int MachineId { get; set; }
public int OwnerProfileId { get; set; }
public int SerialNumber { get; set; }
public string YearofManufacture { get; set; }
public string ModelName { get; set; }
public Manufacturer Manufacturer { get; set; }
public DateTime DateAcquired { get; set; }
}
I've spent far to much time on this now. Does it have something to do with the fact that I have two UserProfile Id int properties in PrivateMessage? (MessageToUserId & MessageFromUserId). I originally had these set as foreign keys with a UserProfile property in there as well like this
[ForeignKey("MessageToProfile")]
public int MessageToUserId { get; set; }
public UserProfile MessageToProfile { get; set; }
[ForeignKey("MessageFromProfile")]
public int MessageFromUserId { get; set; }
public UserProfile MessageFromProfile { get; set; }
But I removed them thinking they may have been the source of the error, but apparently not.
UPDATE:
After a bunch more trial and error, it is apparent that the current method will always err as the method is looking for a navigable property which doesn't exist. Since I have the two int properties in PrivateMessage, when trying to include those in the UserProfile object, I will need to filter then by MessageToUserId and then include them. Not sure how to filter and include.
Using this method should work;
public static UserProfile GetProfileForLoggedInUser(string appUserId)
{
using (RestorationContext)
{
RestorationContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
var profile= RestorationContext.MemberProfiles.Include(m => m.OwnedMachines)
.FirstOrDefault(m => m.AppUserId == appUserId);
var pms = RestorationContext.PrivateMessages.Where(m => m.MessageToUserId == profile.UserProfileId).ToList();
if (profile != null) profile.PrivateMessages = pms;
return profile;
}
}
But it gives the same invalid column error UserProfile_UserProfileID.
Here is the TSql
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[MessageToUserId] AS [MessageToUserId],
[Extent1].[MessageFromUserId] AS [MessageFromUserId],
[Extent1].[DateSent] AS [DateSent],
[Extent1].[Message] AS [Message],
[Extent1].[UserProfile_UserProfileId] AS [UserProfile_UserProfileId]
FROM [RestorationContext].[PrivateMessages] AS [Extent1]
WHERE [Extent1].[MessageToUserId] = #p__linq__0
Since this is just querying the PrivateMessage table WHY is it looking for that UserProfileId, it has nothing to do with this table. Here are the table properties from SSMS
Where is that UserProfileID crap coming from?
Your Machine inclusion works because the Machine class has only one foreign key of UserProfile.
You have 2 foreign keys to the same table in PrivateMessage class, naturally, you would need 2 ICollection navigation properties in your UserProfile class. EntityFramework didn't know which foreign key to use in your PrivateMessage class for loading your ICollection<PrivateMessage> property in your UserProfile class.
public ICollection<PrivateMessage> FromPrivateMessages { get; set; }
public ICollection<PrivateMessage> ToPrivateMessages { get; set; }
In your context class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PrivateMessage>()
.HasRequired(m => m.MessageFromProfile)
.WithMany(t => t.FromPrivateMessages)
.HasForeignKey(m => m.MessageFromUserId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<PrivateMessage>()
.HasRequired(m => m.MessageToProfile)
.WithMany(t => t.ToPrivateMessages)
.HasForeignKey(m => m.MessageToUserId)
.WillCascadeOnDelete(false);
}
UPDATE
EF uses convention over configuration, and by having navigation properties of UserProfile in your PrivateMessage class will imply a relationship and EF will try to find a foreign key in the form of <Navigation Property Name>_<Primary Key Name of Navigation Property type>, which gives you UserProfile_UserProfileId.
You should be wondering why UserProfile_UserProfileId instead of UserProfile_MessageToUserId or UserProfile_MessageFromUserId at this point. That's because of your foreign key attribute, telling EF to use the UserProfileId property in your UserProfile class.
What you can do now is, remove the foreign key attributes like this
public int MessageToUserId { get; set; }
public UserProfile MessageToProfile { get; set; }
public int MessageFromUserId { get; set; }
public UserProfile MessageFromProfile {get; set; }
and add another ICollection and do the modelBuilder configuration like how I stated before the update.
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 have the following two models that I'm trying to setup, but cannot figure out how to do it using data annotations or fluent API. Can anyone advise either the proper annotations or Fluent API code?
public class Vehicle
{
public int ID { get; set; }
public int JobID { get; set; }
public int StatusID { get; set; }
public string Name { get; set; }
public virtual Job Job { get; set }
public virtual Status Status { get; set; }
}
public class Job
{
public int ID { get; set; }
public int VehicleID { get; set; }
public int StatusID { get; set; }
public string Description { get; set; }
public virtual Vehicle Vehicle{ get; set; }
public virtual Status Status { get; set; }
}
The problem I'm having is the reference to the Job model from Vehicle, and from Vehicle to Job. In the database, the Jobs tables holds all jobs pending or completed. There may or may not be a vehicle associated with it (when a job is in progress or complete, a vehicle will be associated with it). For the vehicles table, the JobID represents the current job assigned to the vehicle (if it's assigned a job) and will constantly change through the day, but should have no effect on the Jobs table.
The following snippet outlines a fluent code-first example of the relationship you describe:
public class Vehicle
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Job Job { get; set; }
}
public class Job
{
public int ID { get; set; }
public string Description { get; set; }
}
public class AppDataContext : DbContext
{
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Job> Jobs { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Job>().HasKey(x => x.ID);
mb.Entity<Vehicle>().HasKey(x => x.ID);
mb.Entity<Vehicle>().HasOptional(x => x.Job).WithOptionalDependent();
// ... other config, constraints, etc
}
}
And to get Jobs associated with vehicles:
using (var context = new AppDataContext())
{
var query = context.Vehicles.Where(x => x.Job != null).Select(x => x.Job);
// ...
}
IMHO, the explicit foreign key properties should be kept out of the model unless strictly needed. This tends to make life easier and keeps the code clean.
Hope this is along the lines of what you're looking for...
P.S. - At the risk of 'just posting links' - If you already have a database (as implied in your question), it might be worth considering a Database First approach: http://msdn.microsoft.com/en-gb/data/jj206878.aspx ..or Code First with an existing database: http://msdn.microsoft.com/en-us/data/jj200620