I'm trying to use the foreign key association approach to achieve a one-to-one association in EF. In my case there's an association between a User and a Team, and I need a navigation property in each of them.
I bumped into a problem when trying to save data.
This is what the models look like:
public class Team
{
public int ID { get; set; }
public string Name { get; set; }
public int OwnerID { get; set; }
public virtual User Owner { get; set; }
}
public class User
{
public int ID { get; set; }
public string UserName { get; set; }
public int TeamID { get; set; }
public virtual Team Team { get; set; }
}
I added these bits to the DbContext OnModelCreating(), as instructed in the blog post referenced above:
modelBuilder.Entity<User>()
.HasRequired(u => u.Team)
.WithMany()
.HasForeignKey(u => u.TeamID);
modelBuilder.Entity<Team>()
.HasRequired(t => t.Owner)
.WithMany()
.HasForeignKey(t => t.OwnerID)
.WillCascadeOnDelete(false);
And now when adding data like this:
User u = new User();
u.UserName = "farinha";
Team t = new Team("Flour Power");
u.Team = t;
t.Owner = u;
context.Users.Add(u);
context.Teams.Add(t);
context.SaveChanges();
or even like this:
User u = new User();
u.UserName = "farinha";
u.Team = new Team("Flour Power");
context.Users.Add(u);
context.SaveChanges();
I'm getting the following error:
Unable to determine a valid ordering
for dependent operations. Dependencies
may exist due to foreign key
constraints, model requirements, or
store-generated values.
Any idea of how to solve this? Am I saving the data in a wrong way?
Thanks in advance
You are not saving data wrong way but it simply cannot work because you defined bidirectional dependency. Team can be saved only if related to already saved user and user can be saved only if related to existing team. You must make one relation optional by marking foreign key property nullable (for example TeamId in User):
public class User
{
public int ID { get; set; }
public string UserName { get; set; }
public int? TeamID { get; set; }
public virtual Team Team { get; set; }
}
Then you must save the User entity first and then you can save Team.
User u = new User();
u.UserName = "farinha";
context.Users.Add(u);
context.SaveChanges();
u.Team = new Team { Name = "Flour Power", OwnerID = u.ID };
context.SaveChanges();
Related
Trying to create simple CRUD app using Asp Net Core. I have 2 entities:
Department and Employee( one to many ). I need to delete record from Department table. But when Im trying to delete record using OnDelete(DeleteBehavior.Restrict) or OnDelete(DeleteBehavior.ClientSetNull) i have exception:
UPDATE or DELETE in table"Departments" violates foreign key constraint
"FK_Employees_Departments_DepartmentCode" table"Employees"
How can i fix this problem ?
Entity Employee:
public class Employee
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required(ErrorMessage = "Input fullname of employee")]
public string FullName { get; set; }
[Required(ErrorMessage = "Input date of birth")]
public DateTime DateOfBirth { get; set; }
[Required(ErrorMessage = "Input code")]
public string Code { get; set; }
[Required(ErrorMessage = "Input fullname of employee")]
public int Salary { get; set; }
public string DepartmentCode { get; set; }
public Department Department { get; set; }
}
Entity Department:
public class Department
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required(ErrorMessage = "Input name of department")]
public string Name { get; set; }
[Required(ErrorMessage = "Input code of department")]
public string Code { get; set; }
public ICollection<Employee> Employees { get; set; }
public Department()
{
Employees = new List<Employee>();
}
}
Context class settings:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>()
.HasMany<Employee>(d => d.Employees)
.WithOne(e => e.Department)
.HasForeignKey(e => e.DepartmentCode)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Department>()
.HasKey(d => d.Code);
modelBuilder.Entity<Employee>()
.HasKey(e => e.Code);
modelBuilder.Entity<Department>()
.HasIndex(d => d.Name).IsUnique();
}
Of all fields in the DeleteBehavior enum only two actually add cascaded foreign key behavior to the database: Cascade and SetNull. All other options create foreign keys with no action on delete, but differ in what EF will do to its tracked entities.
In your case it should probably be SetNull because I assume that Employees can exist without Department. This setting will allow you to delete a Department object without loading its Employees. The database will set their DepartmentCode to null.
The delete behavior configured in EF can only be applied tho Entities that are tracked by EF change tracking. So you would need to load all Employees that belong to the department to make this work as expected.
BUT The database foreign key definition also defines the on delete action (cascading, set null, do nothing) So even if you code within your context a set null strategy, the DB still might apply different strategy for on delete. EF core defaults to cascade delete.
I need to insert a new entity without knowing it's PK. The parent entity has another property which is a guid and unique which is what we use to do cross db references and this is all I have. I have done it in the past but can't find a reference on how to do it again.
[Table("School")]
public class SchoolEntity
{
public SchoolEntity()
{
Students = new HashSet<StudentEntity>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public Guid ExternalId { get; set; }
[ForeignKey("SchoolId")]
public virtual ICollection<StudentEntity> Students { get; set; }
}
[Table("Student")]
public class StudentEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public SchoolEntity School { get; set; }
}
//ExternalId won't work cause is not the primary key.
var school = new School { ExternalId = Guid.Parse('68e05258-550a-40f3-b68a-5d27a0d825a0') };
context.Attach(school);
context.Schools.Add.Add(new Student());
context.SaveChanges();
Well, the PK of the referenced entity is required in order to set properly the FK of the referencing entity.
If you don't have it, apparently you should find it (get it from the database) based on what you have (secondary identifier in your case). For instance:
var school = context.Schools.Single(e => e.ExternalId == externalId);
var student = new Student { School = school, ... };
context.Students.Add(student);
context.SaveChanges();
There is no way to get that working without fetching. If you don't want to fetch the whole referenced entity (and you are sure it's not tracked by the context), then you can fetch the PK only and Attach a stub entity:
var schoolId = context.Schools.Where(e => e.ExternalId == externalId)
.Select(e => e.Id).Single();
var school = new School( Id = schoolId);
context.Attach(school);
// ...
I have the following model:
public class User
{
public int Id { get; set; }
public Customer Customer { get; set; }
...
}
public class Customer
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
...
}
What I want to have is the Customer has to have an User, but can only have one, and the User does not have to have a Customer.
I would like to do it with the Fluent API, but I can't manage to get it to work so that both Customer and User have their Id properties be Identity Fields.
When you are configuring an one-to-one relationship, Entity Framework requires that the primary key of the dependent also be the foreign key, in your case it would be:
public class User
{
public int Id { get; set; }
public virtual Customer Customer { get; set; }
...
}
public class Customer
{
[Key, ForeignKey("User")]
public int UserId { get; set; }
public virtual User User { get; set; }
...
}
But you want each entities with its own PK, so, EF lets you do that but you should delete the UserId property from Customer entity, because, as I said before, in this kind of relationship the FK must be PK too. To configure properly your relationship use the Required data annotation as #Claies recommend you in his comment:
public class User
{
public int Id { get; set; }
public virtual Customer Customer { get; set; }
...
}
public class Customer
{
[Key]
public int Id { get; set; }
[Required]
public virtual User User { get; set; }
...
}
Or you can use Fluent Api, the configuration would be:
modelbuilder.Entity<Customer>().HasRequired(c=>c.User).WithOptional(u=>u.Customer);
Another thing, I recommend you define the navigation properties as virtual. This way, when you consult those properties the first time, they will be lazy loaded. Check this post for more info.
Update 1:
When the key property is an integer, Code First defaults to
DatabaseGeneratedOption.Identity. If you want, you can configure explicitly what you need using the [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)] attributes on the Customer Id.
public class Customer
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
Or you can use Fluent Api:
modelbuilder.Entity<Customer>().HasKey(t => t.Id)
.Property(t => t.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Update 2:
I don't understand why is throwing you that exception. I just tested with both variants (Data Annotations and Fluent Api) and everything works well. This is the code generated by Migrations:
public partial class changeCustomerIdToIdentity : DbMigration
{
public override void Up()
{
DropIndex("dbo.Customers", new[] { "Id" });
DropPrimaryKey("dbo.Customers");
AlterColumn("dbo.Customers", "Id", c => c.Int(nullable: false, identity: true));
AddPrimaryKey("dbo.Customers", "Id");
CreateIndex("dbo.Customers", "Id");
}
public override void Down()
{
DropIndex("dbo.Customers", new[] { "Id" });
DropPrimaryKey("dbo.Customers");
AlterColumn("dbo.Customers", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.Customers", "Id");
CreateIndex("dbo.Customers", "Id");
}
}
I'm afraid your error is happened due to your DB schema. The Id on your Customers table must be FK too. The error means that you have some relation between your entities where foreign key property in dependent entity is defined as store generated, and that is because you are trying change the Id of your Customer entity as Identity, which is FK in your DB.
I have a problem with the Entity Framework.
public class User : Receiver
{
public User()
{
if (Groups == null)
Groups = new List<Group>();
if (Buddies == null)
Buddies = new List<User>();
}
[Required]
public string PhoneNumber { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<Group> Groups { get; set; }
}
public class Receiver
{
public Receiver()
{
Guid = Guid.NewGuid();
Created = DateTime.Now;
}
[Key]
public Guid Guid { get; set; }
[Required]
public DateTime Created { get; set; }
}
When i try to add a user...
User user = new User
{
Guid = new Guid("8cd094c9-e4df-494e-b991-5cf5cc03d6e3"),
PhoneNumber = "+4991276460"
};
cmc.Receivers.Add(user);
... it ends in follogwing error.
The object of the Type "System.Collections.Generic.List`1[Project.Models.User]" can't be converted to "Project.Models.User".
When i comment out following two lines:
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...the programm runs fine.
I hope someone can help me to fix this problem.
Otherwise it runs into an error at this line : cmc.Receivers.Add(user);
In your mapping...
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...you specify that User.Buddies is part of a one-to-many relationship and that User.Guid (=Receiver.Guid) is the foreign key in this relationship. But User.Guid is also the primary key, hence it must be unique. As a result a User cannot have a list of Buddies but only a single reference.
The mapping makes no sense but the exception is not very helpful and difficult to understand. (Somehow EF seems to recognize internally that the Buddies cannot be a list with that mapping and wants to cast the list to a single reference. It should detect in my opinion that the mapping is invalid in the first place.)
For a correct one-to-many mapping you need a foreign key that is different from the primary key. You can achieve that by either removing the [ForeignKey] annotation altogether...
[JsonIgnore]
public IList<User> Buddies { get; set; }
...in which case EF will create a default foreign key in the Receivers table (it will be some column with an underscore in its name, but you can rename that with Fluent API if you don't like the default name) or by adding your own foreign key property to the User class:
public Guid? BuddyGuid { get; set; }
[ForeignKey("BuddyGuid"), JsonIgnore]
public IList<User> Buddies { get; set; }
I have following classes
public class Employer
{
[Key]
public Int64 EmployerID { get; set; }
public String CompanyName { get; set; }
public virtual List<Employee> Employees { get; set; }
}
public class Employee
{
[Key]
public Int64 EmployeeID { get; set; }
public String EmployeeName { get; set; }
public virtual Employer EmployerInfo { get; set; }
}
In the Database context I have set the relation as
modelBuilder.Entity<Employer>()
.HasMany(p => p.Employees)
.WithRequired()
.Map(x => x.MapKey("EmployerID"));
After executing some actions, database gets created with Employee table having EmployerID as foreign key and one extra key EmployerInfo_EmployerID.
Now when I fetch employer data, I am getting employee details with it.
But when I tried to fetch employee data I am getting EmployerInfo as null. This is because I need relationship from Employee to EmployerInfo.
How do I set the bi-directional relationship in this context?
You need to update your fluent so your relationship mapping contains both ends:
modelBuilder.Entity<Employer>()
.HasMany(p => p.Employees)
.WithRequired(e => e.EmployerInfo)
.Map(x => x.MapKey("EmployerID"));