I have the following tables : Products, Users and ProductApproval. Whenever user create a new product, the ProductApproval will have a new record with the product ID and a null ApprovedByUserId because it is not approve yet. User can have many ProductApproval but Product only can have one ProductApproval.
The structure is like this:
User.cs
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public virtual ICollection<ProductApproval> ProductApprovals { get; set; }
public User()
{
ProductApprovals = new Collection<ProductApproval>();
}
}
Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ProductApproval ProductApproval { get; set; }
}
ProductApproval.cs
public class ProductApproval
{
public int Id { get; set; }
public virtual Product Product { get; set; }
public int? ProductId { get; set; }
public virtual User User { get ;set; }
public int? ApprovedByUserId { get; set; }
}
DataContext.cs
modelBuilder.Entity<User>()
.HasMany(u => u.ProductApprovals)
.WithOne(pa => pa.User)
.HasForeignKey(pa => pa.ApprovedByUserId)
.IsRequired(false);
modelBuilder.Entity<Product>()
.HasOne(p => p.ProductApproval)
.WithOne(pa => pa.Product)
.HasForeignKey<ProductApproval>(pa => pa.ProductId);
I already declared the foreign key ApprovedByUserId as nullable, but i still get the following error when i insert a record into productapprovals :
MySqlException: Cannot add or update a child row: a foreign key constraint fails (mysite.productapprovals, CONSTRAINT FK_ProductApprovals_Users_ApprovedByUserId FOREIGN KEY (ApprovedByUserId) REFERENCES users (Id) ON DELETE RESTRICT)
Please advice is there any place i did wrong
Related
My Data Model
Table("People")]
public abstract class Person
{
public long Id { get; set; }
public string Name { get; set; }
[ForeignKey("ReferredBy")]
public long? ReferredById { get; set; }
public User ReferredBy { get; set; }
}
public class User : Person
{
public string UserName { get; set; }
}
public class Student : Person
{
public string RollNo { get; set; }
}
Fluent API to set null on delete
modelBuilder.Entity<Person>()
.HasOne(t => t.ReferredBy)
.WithOne()
.OnDelete(DeleteBehavior.SetNull);
Now when i try to insert a row with same ReferredByit throws an unique constraint error. Please help me with this.
I have the following table structure simplified
public class UserProfile
{
public int UserId { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
}
public class Buyer
{
public int BuyerId { get; set; }
public int UserId { get; set; }
public UserProfile User { get; set; }
public ICollection<Order> Order { get; set; }
}
public class Seller
{
public int SellerId { get; set; }
public int UserId { get; set; }
public UserProfile User { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public int BuyerId { get; set; }
public int SellerId { get; set; }
public string Address { get; set; }
public <OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public int OrderId { get; set; }
public int OrderLineNo { get; set; }
public int StockId { get; set; }
public int Quantity { get; set; }
}
When I run Update_Database command I get the following error
Introducing FOREIGN KEY constraint 'FK_dbo.Buyers_dbo.UserProfile_UserId' on table 'Buyers' may cause cycles or multiple cascade paths.
DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
My Buyer and Seller tables have a 1 to 1 relationship with to the UserProfile on UserId so I understand that when you delete a Buyer it can't delete the UserProfile since there might be a Seller and vice versa.
So I want to turn off cascading deletes for just the Buyer to the User and the Seller to the User using the fluid syntax but I can't get something to work so would appreciate if someone could help.
I have looked through the other StackOVerflow questions for the same error but couldn't find one that matches my scenario.
Extra: If I rename the UserId in my Buyer table to DummyUserId then the Uppdate-Database command works but this is not good.
Usually you can disable cascade deletes using .WillCascadeOnDelete(false);
So for your mapping, you may have something similar to the following:
modelBuilder.Entity<Buyer>()
.HasRequired(x => x.User)
.WithMany()
.HasForeignKey(x => x.UserId)
.WillCascadeOnDelete(false);
my code like below
public class User
{
public int ID { get; set; }
public int BillingAddressID { get; set; }
public Address BillingAddress { get; set; }
public IList<Shipment> Shipments { get; set; }
}
public class Address
{
public int ID { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
public class Shipment
{
public int ID { get; set; }
public string State { get; set; }
public int DeliveryAddressID { get; set; }
public Address DeliveryAddress { get; set; }
public User ShipUser { get; set; }
//[ForeignKey("ShipUser")]
public int ShipUserID { get; set; }
//public int UserId { get; set; }
}
public class TestContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<Shipment> Shipments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Shipment>().HasRequired(u => u.ShipUser)
.WithMany(d => d.Shipments)
.HasForeignKey(c => c.ShipUserID)
.WillCascadeOnDelete(false);
}
}
if i remove the override method,i will get an error "SqlException: Introducing FOREIGN KEY constraint 'FK_Shipments_Users_ShipUserID' on table 'Shipments' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors."
if i remove ShipUserID in Shipment Class,it will work ok,when i see the table that is created by ef,i found a column named Shipment_UserID in table Shipment.I don`t know why.
if rename the class indenty key to UserID,it also work ok.
I try it anyway,but I don`t know the reason, I need some books about EF associations.
If you don't have mapping specified without cascadeDelete=false for one relationship it will create multiple cascade paths if you have tow relationships to user from Shipment.
By convention you can use public
Public User ShipUser { get; set; }
public int ShipUserID { get; set; }
it will use ShipUserID as foreign key by convention.
If you remove ShipUserID Ef need to create his own foreign key to keep the relationship . that is your ' Shipment_UserID'
rename the class indenty key to UserID I don't understand what you meant.
Here is a good tutorial to start with
I have two tables (Table A, Table B) joined with a join table (TableAB) with 3 payload columns. By Payload I mean columns apart from Id, TableAId, and TableBId.
I can insert into all tables successfully, but I need to insert data into one of the payload columns on Insert. I'm using EF 4.3, Fluent API. Can anyone help? Thanks in advance.
public class Organisation : EntityBase<int>, IAggregateRoot
{
public string Name { get; set; }
public string Url { get; set; }
public int CountryId { get; set; }
public int? OwnershipTypeId { get; set; }
public int OrganisationStatusId { get; set; }
public virtual ICollection<Feature> Features { get; set; }
public virtual ICollection<OrganisationType> OrganisationTypes { get; set; }
public virtual ICollection<PricePlan> PricePlans { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User: EntityBase<Guid>, IAggregateRoot
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public int? PhoneCallingCodeId { get; set; }
public int? PhoneAreaCode{ get; set; }
public string PhoneLocal { get; set; }
public int? MobileCallingCodeId { get; set; }
public int? MobileAreaCode { get; set; }
public string MobileLocal { get; set; }
public virtual ICollection<Organisation.Organisation> Organisations { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int OrganisationRoleId {get; set;}//Foreign Key - have tried leaving it out, tried it as public virtual Organisation Organisation {get;set;
public bool IsApproved { get; set; }
}
public class SDContext : DbContext
{
public ObjectContext Core
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
public IDbSet<User> User { get; set; }
public IDbSet<Organisation> Organisation { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Organisation>().HasMany(u => u.Users).WithMany(o => o.Organisations).Map(m =>
{
m.MapLeftKey("OrganisationId");
m.MapRightKey("UserId");
m.ToTable("OrganisationUser");
});
//I have tried specifically defining the foreign key in fluent, but I really need to understand how I can add the payload properties once I access and edit them.
Your mapping is not correct for your purpose. If you want to treat OrganisationUser as an intermediate entity between Organisation and User you must create relationships between Organisation and OrganisationUser and between User and OrganisationUser, not directly between Organisation and User.
Because of the intermediate entity which contains its own scalar properties you cannot create a many-to-many mapping. EF does not support many-to-many relationships with "payload". You need two one-to-many relationships:
public class Organisation : EntityBase<int>, IAggregateRoot
{
// ...
// this replaces the Users collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class User : EntityBase<Guid>, IAggregateRoot
{
// ...
// this replaces the Organisations collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public int OrganisationId { get; set; }
public Organisation Organisation { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
// ... "payload" properties ...
}
In Fluent API you must replace the many-to-many mapping by the following:
modelBuilder.Entity<Organisation>()
.HasMany(o => o.OrganisationUsers)
.WithRequired(ou => ou.Organisation)
.HasForeignKey(ou => ou.OrganisationId);
modelBuilder.Entity<User>()
.HasMany(u => u.OrganisationUsers)
.WithRequired(ou => ou.User)
.HasForeignKey(ou => ou.UserId);
Your derived DbContext may also contain a separate set for the OrganisationUser entity:
public IDbSet<OrganisationUser> OrganisationUsers { get; set; }
It's obvious now how you write something into the intermediate table:
var newOrganisationUser = new OrganisastionUser
{
OrganisationId = 5,
UserId = 8,
SomePayLoadProperty = someValue,
// ...
};
context.OrganisastionUsers.Add(newOrganisastionUser);
context.SaveChanges();
If you want to make sure that each pair of OrganisationId and UserId can only exist once in the link table, it would be better to make a composite primary key of those two columns to ensure uniqueness in the database instead of using a separate Id. In Fluent API it would be:
modelBuilder.Entity<OrganisationUser>()
.HasKey(ou => new { ou.OrganisationId, ou.UserId });
More details about such a type of model and how to work with it is here:
Create code first, many to many, with additional fields in association table
I'm trying to map via DbModel this relationship present on the database.
CREATE TABLE core.Institutes
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(128) NOT NULL,
OldID INT NULL
)
GO
CREATE TABLE core.InstitutePlaces
(
FKInstituteID INT NOT NULL PRIMARY KEY REFERENCES core.Institutes(ID),
FKPlaceID INT NOT NULL REFERENCES core.Places(ID)
)
GO
CREATE TABLE core.Places
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(128) NOT NULL,
FKParentID INT NULL REFERENCES core.Places(ID),
OldID INT NULL
)
GO
on this model
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Place Parent { get; set; }
}
public class Institute
{
public int Id { get; set; }
public string Name { get; set; }
public Place Place { get; set; }
}
we're using something like this to do the mapping
modelBuilder.Entity<Institutes.Institute>().HasOptional(i => i.Place);
but it doesn't work :(
This scenario is perfectly managed by the EDML file, so the problem is only about the mapping.
Something like this will give you (almost) the desired schema with one caveat: Code First does not create a 1:1 relationship in entity splitting scenarios which your desired schema (creating a 1:* association using a join table) is a special case of it.
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Place Parent { get; set; }
}
public class Institute
{
[DatabaseGenerated(DatabaseGenerationOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public int? PlaceId { get; set; }
public Place Place { get; set; }
}
public class Context : DbContext
{
public DbSet<Place> Places { get; set; }
public DbSet<Institute> Institutes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Institute>().Map(mc =>
{
mc.Properties(p => new { p.Id, p.Name });
mc.ToTable("Institutes");
})
.Map(mc =>
{
mc.Properties(p => new { p.Id, p.PlaceId });
mc.ToTable("InstitutePlaces");
});
modelBuilder.Entity<Place>()
.HasOptional(p => p.Parent)
.WithMany()
.HasForeignKey(p => p.ParentId);
}
}
I had to switch off identity generation due to a bug that I explained here.