Hopefully someone can show me how to implement this many-to-may relation...
I have two classes, Car and Owner. Each Car can be owned by many Owners. And each owner can own many cars. Note that I only want a navigationproperty from car to owners.
public partial class Car
{
public int CarId { get; set; }
public string Name { get; set; }
public ICollection<Owner> Owners { get; set; }
}
public partial class Owner
{
public int OwnerId { get; set; }
public string Name { get; set; }
}
In my DbInitializer I seed the b like this.
Owner owner1 = new Owner() { OwnerId = 10, Name = "Erik" };
context.Owners.Add(owner1);
Car b1 = new Car() { CarId = 1, Name = "Volvo", Owners = new List<Owner>(new Owner[] { owner1 }) };
Car b2 = new Car() { CarId = 2, Name = "Saab", Owners = new List<Owner>(new Owner[] { owner1 }) };
context.Cars.Add(b1);
context.Cars.Add(b2);
context.SaveChanges();
The result is that owner1 only owns b2. Why? And how can it be changed to a many to many relation?
Thanks!
In you context add this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Car>().HasMany(c => c.Owners).WithMany();
}
I tells EF that there is a many-to-many association between Car and Owner without a navigation property in Owner.
Related
I would like to add an object to a related entity without loading them.
I have Company entity defined like this:
public class Company
{
public int ID { get; set; }
public List<Employee> EmployeeList{ get; set; }
}
And Employee entity like this
public class Employee
{
public int ID { get; set; }
public String Name{ get; set; }
}
I want to add an employee to a list placed in that company object without loading all the employees.
I know I can use this expression
Company myCompany= systemBD.Companies.Include("EmployeeList").Find(1) myCompany.EmployeeList.Add(newEmployee)
but I'm afraid that this would consume a lot of time since I have thousands of employees in my database.
Is there a way to add a new employee to an existing company without loading the list of Employees?
I was looking into the Attach method but it does not seem to work.
using (var systemDB = new CompanyDB())
{
Employee employee = new Employee ();
Company companySearch = systemDB.Companies.Where(d => d.Name.Equals("John")).SingleOrDefault();
if (companySearch != null)
{
if (companySearch.EmployeeList != null)
{
systemDB.Companies.Attach(companySearch );
companySearch.EmployeeList.Add(employee);
systemDB.SaveChanges();
}
}
I tried that code but it doesn't work.
Assuming you have your Company and Employee entities defined to have both a navigation property from a Company to the collection of all of its associated Employees and a property from an Employee to its single associated Company, you can accomplish creating a new Employee and associating it with an existing Company from the Employees DB set.
[Table("Company")]
public partial class Company
{
public Company()
{
this.Employees = new HashSet<Employee>();
}
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
[Table("Employee")]
public partial class Employee
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
public partial class Database : DbContext
{
public Database()
: base("name=Database")
{
}
public virtual DbSet<Company> Companies { get; set; }
public virtual DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<Company>()
.HasMany(e => e.Employees)
.WithRequired(e => e.Company)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Employee>()
.Property(e => e.Name)
.IsUnicode(false);
}
}
Then assuming you already have a Company in the system with an Id of 1, you can do the following:
using (var database = new Database())
{
var company = database.Companies.Find(1);
if (company != null)
{
var employee = new Employee
{
Name = "John Doe",
Company = company
};
database.Employees.Add(employee);
database.SaveChanges();
}
}
OR...if you are sure that Company Id 1 definitely exists...
using (var database = new Database())
{
var employee = new Employee
{
Name = "John Doe",
CompanyId = 1
};
database.Employees.Add(employee);
database.SaveChanges();
}
I think you would need to change your Database Design to accomplish what you want.
Employee table
ID (Primary key)
Name
Company table
ID (Primary key)
Name
EmployeeCompany table
IDCompany (Foreign Key)
IDEmployee (ForeignKey)
This way you will accomplish what you want
I'm having some unexpected results when using code first and I know it has to do with how EF is creating relationships but I can't figure it out. Here is what I have.
Product class, KitComponent class (hold a product, and a qty, etc)
A Product has many KitComponents
A KitComponent has many Products
End result is to show a product, and this product has some other products included as a kit. It may contain 1 of a product or 2 pieces, thats why I need a KitComponent class / table, to hold the other info.
For the longest time I struggled with the ef relationships. The database would put the ParentProductId and Parent Product into the IncludedProductID and IncludedProduct. NOW i fixed that but don't know how but the IncludedProduct field is not being populated. The IncludedProductID is now correct in database. What Am I missing so that when I pull a product and cycle through the kitCOmponents that the IncludedProduct is not null.
AND on top of that, have I correctly described the relationships in the config?
Product:
public class Product
{
public Product()
{
this.KitComponents = new HashSet<KitComponent>();
}
public int Id { get; set; }
public string PartNumber { get; set; }
public virtual ICollection<KitComponent> KitComponents { get; set; }
}
KitComponent:
public class KitComponent
{
public int Id { get; set; }
public string UOM { get; set; }
public int QTY { get; set; }
public int ParentProductId { get; set; }
public Product ParentProduct { get; set; }
public int IncludedProductId { get; set; }
public Product IncludedProduct { get; set; }
}
DBContext With fluent API code:
public class ProductContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<KitComponent> KitComponents { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasMany(p => p.KitComponents)
.WithRequired(k => k.ParentProduct)
.HasForeignKey(p => p.ParentProductId);
modelBuilder.Entity<KitComponent>().HasRequired(k => k.IncludedProduct)
.WithMany()
.HasForeignKey(k => k.IncludedProductId)
.WillCascadeOnDelete(false);
}
}
Simple Seed Method to Demo Data:
public class ProductContextInitializer : DropCreateDatabaseAlways<ProductContext>
{
private ProductContext db = new ProductContext();
protected override void Seed(ProductContext context)
{
Product prod1 = new Product { PartNumber = "P1" };
Product prod2 = new Product { PartNumber = "P2" };
Product prod3 = new Product { PartNumber = "P3" };
Product prod4 = new Product { PartNumber = "P4" };
Product prod5 = new Product { PartNumber = "P5" };
Product prod6 = new Product { PartNumber = "P6" };
Product prod7 = new Product { PartNumber = "P7" };
db.Products.Add(prod1);
db.Products.Add(prod2);
db.Products.Add(prod3);
db.Products.Add(prod4);
db.Products.Add(prod5);
//db.Products.Add(prod6);
//db.Products.Add(prod7);
db.SaveChanges();
var kitComp = new KitComponent() { IncludedProduct = prod2, QTY = 1, UOM = "EA" };
prod1.KitComponents.Add(kitComp);
db.SaveChanges();
}
}
In order for the property to load, you have to make the navigation property virtual:
public virtual Product IncludedProduct { get; set; }
All the code first conventions can be found here: https://msdn.microsoft.com/en-us/data/jj679962.aspx
So I have Work and Person models as follows:
public class Work{
public int ID
public string Name
public virtual ICollection<Person> CurrentWorkers
public virtual ICollection<Person> ProjectOwners
}
public class Person{
public int ID
public string UserID
public virtual ICollection<Work> CurrentWork
}
Now when I go into the db to get my person (whom submitted the Work request):
//if exists
var person = db.Person.Find(id)
//else
Person person = new Person()
and try to assign an owner to the Work:
Work work = new Work()
work.ProjectOwners.Add(person) //NULL REF ERROR HERE
db.Work.Add(work)
I get a Null Reference error, which I guess is because work isn't in the db yet? So do I have to insert work and then go find it and then attach the Person? I don't quite understand how EF Many-Many works and links for additional reading into this would also be appreciated.
UPDATE
So the below works once but when I create a new Work, whatever was saved with any previous work.ProjectOwners gets wiped out. Any help with this, I understand WHY (because the FK column in People is being overwritten with the next Work PK) but I have no idea how to fix it?
work.ProjectOwners = new List<Person>();
Take a look at this example:
public class Work
{
public int WorkId { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> CurrentWorkers { get; set; }
public virtual ICollection<Person> ProjectOwners { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public string UserId { get; set; }
public virtual ICollection<Work> CurrentWork { get; set; }
}
public class WorkContext : DbContext
{
public DbSet<Work> Works { get; set; }
public DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Work>().
HasMany(w => w.ProjectOwners).
WithMany(p => p.CurrentWork).
Map(
m =>
{
m.MapLeftKey("WorkId");
m.MapRightKey("PersonId");
m.ToTable("WorkPersons");
});
}
}
And here a simple test:
var personJohn = new Person() {UserId = "John Doe"};
var personJane = new Person() { UserId = "Jane Dee" };
var work = new Work();
work.ProjectOwners = new List<Person>();
work.ProjectOwners.Add(personJohn);
db.Works.Add(work);
db.SaveChanges();
work.ProjectOwners.Add(personJane);
db.SaveChanges();
Edit:
Variation where you add a Person that already exists in the database (e.g. Jane):
var john = new Person() {UserId = "JohnDoe"};
var jane = new Person() { UserId = "JaneDee" };
//Add Jane to DB
db.Persons.Add(jane);
db.SaveChanges();
//Create new Work and initialize ProjectOwners collection
var work = new Work();
work.ProjectOwners = new List<Person>();
//Add John (doesn't exist in DB yet)
work.ProjectOwners.Add(john);
db.Works.Add(work);
db.SaveChanges();
//Look for Jane in DB
jane = db.Persons.FirstOrDefault(x => x.UserId == "JaneDee");
if (jane != null)
{
//Add existing Jane if found to work Project Owners
work.ProjectOwners.Add(jane);
}
db.SaveChanges();
I have two objects (WishListItem and Product) in a one-to-many relationship. WishListItem has to have a product. Each Product can be in 0 - n WishListItems.
public class WishListItem
{
public int Id { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
// ... other properties
}
The Product has no knowledge of WishListItems. All of the Products exist. I just want to add a new WishListItem. My WishListItem model for the relationship is this:
HasRequired(p => p.Product).WithMany().HasForeignKey(p => p.ProductId);
When I try to add a new item like this:
WishListItem item = new WishListItem();
// ... sets properties
WishListItems.Add(item); // WishListItems is of type DbSet<WishListItem>
SaveChanges();
This code seems to try to also add a Product. I don't want to add a new Product (or even update it). The Product property is set to null. How do I tell Entity Framework that I only want to add the WishListItem? Do I need to Ignore the Product property (by doing Ignore(p => p.Product); in my WishListItem model) and load the Product separately whenever I load my WishListItem objects?
I have solved my issue. The problem came from another property on the Product object.
private bool _isFreeShippingInitialValue;
public bool IsFreeShipping
{
get
{
return _isFreeShippingInitialValue ||
computedValueFromAnotherChildObject;
}
set
{
_isFreeShippingInitialValue = value;
}
}
We noticed that when you first get the Product object, the IsFreeShipping.get is called (not sure why) before any child objects are loaded. For example, if _isFreeShippingInitialValue is false and computedValueFromAnotherChildObject is true, IsFreeShipping first returns false (because computedValueFromAnotherChildObject is first false since no child objects have been loaded), then true the next time you try to get IsFreeShipping. This makes EF think the value has changed.
The first item we added to WishListItems worked fine. It was the second item that broke. We believe SaveChanges() (or something prior to it) loaded the Product for the first WishListItem. The SaveChanges() was breaking on the Product of the first WishListItem when we were adding the second item.
So, in short, be careful when computing values in a Property.get using child objects because it can bite you in the butt.
This works for me without adding any new Addresses records. In this model, Person has an optional home address, but address doesn't have any knowledge of the person.
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual Address HomeAddress { get; set; }
public int HomeAddress_id { get; set; }
}
public class Address
{
public int Id { get; set; }
public string PhoneNumber { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
In the DbContext override, I have the below code
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(t => t.HomeAddress).WithMany()
.HasForeignKey(t => t.HomeAddress_id);
}
I can write a unit test like this.
var addressId = 0;
using (var db = new DataContext())
{
var address = new Address { City = "test", Country = "test", PhoneNumber = "test", State = "test", Street = "test" };
db.Addresses.Add(address);
db.SaveChanges();
addressId = address.Id;
}
using (var db = new DataContext())
{
var person = new Person { Email = "test#test.com", FirstName = "Testy", LastName = "Tester", HomeAddress_id = addressId };
db.Persons.Add(person);
db.SaveChanges();
}
Confusing Situation
I have a situation where I have 2 entities where 1 inherits from the other, that need to map to 2 separate tables, but code use should be around the base of the 2 entities.
Details
public class Team
{
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
We have an existing database schema where Employee and EmployeeInfo are separate tables with a FK between EmployeeInfo_Id and Employee_Id.
In our system "managers" will be adding Employee's to the system, with a set of private information (more properties than listed above) like pay, and add them to a Team. Other areas of the system will be using the Team or Employee objects for various other things. We would like to have to code super simple if the mapping can be done.
When a manager creates a new employee we would like the code to look something like this:
public void Foo(string name, decimal pay)
{
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Pay = pay;
// add him/her to the team
_team.Employees.Add(employee); // the idea being that consumers of the Team entity would not get the separate employee info properties
// save the context
_context.SaveChanges();
}
The end result would be that the EmployeeInfo properties entered into the EmployeeInfo table and the base Employee data is entered into the Employee table and added to the Team via the association table TeamEmployees.
So far I'm trying the current mappings, and I get an invalid column named "Discriminator." When just adding an employee to a team.
public class TeamConfiguration : EntityTypeConfiguration<Team>
{
public TeamConfiguration()
{
ToTable("Team");
HasKey(t => t.Id);
HasMany(t => t.Members).WithMany(m => m.Teams)
.Map(m =>
{
m.MapLeftKey("Team_Id");
m.MapRightKey("Employee_Id");
m.ToTable("TeamEmployees");
});
}
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
ToTable("Employee");
ToTable("EmployeeInfo");
HasKey(t => t.Id);
Property(p => p.Name);
HasMany(m => m.Teams)
.WithMany(t => t.Members)
.Map(m =>
{
m.MapLeftKey("Employee_Id");
m.MapRightKey("Team_Id");
m.ToTable("TeamEmployees");
});
}
}
Also, if I take the many-to-many between team and employee out of the mix I get a FK exception on Employee_Id to EmployeeInfo_Id.
Thanks, JR.
Discriminator is a column that's being added to your table when you use Table Per Hierarchy approach.
I think what you're looking for is "Table per Type (TPT)". Decorate your EmployeeInfo class as follows:
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
Or add below to your OnModelCreating event:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<EmployeeInfo>().ToTable("EmployeeInfo");
...
}
Or, create the following class and use it like modelBuilder.Configurations.Add(new EmployeeInfoConfiguration()); in OnModelCreating method:
public class EmployeeInfoConfiguration : EntityTypeConfiguration<EmployeeInfo>
{
public EmployeeInfoConfiguration()
{
ToTable("EmployeeInfo");
}
}
This will cause EF to create EmployeeInfo table with necessary constraints.
Also, it's good to initialize your collections in your objects' constructors to prevent null exception. For example in Team class:
public Team()
{
this.Employees = new HashSet<Employee>();
}
I copied your code exactly, and changed the following parts:
public class Team
{
public Team()
{
this.Members = new HashSet<Employee>();
}
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public Employee()
{
this.Teams = new HashSet<Team>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
In the DbContext, no changes:
public partial class TestEntities : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<EmployeeInfo> Employee_Info { get; set; }
public DbSet<Team> Teams { get; set; }
}
and your working Foo method:
public static void Foo(string name, decimal pay)
{
var _team = new Team();
var context = new TestEntities();
context.Teams.Add(_team);
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Amount = pay;
context.Employees.Add(employee);
context.SaveChanges();
// add him/her to the team
_team.Members.Add(employee);
// save the context
context.SaveChanges();
}
Finally, remove ToTable("EmployeeInfo"); part from EmployeeConfiguration since you have mentioned this correctly in your mode creating event.
For more info about Table Per Type approach, check out this great article.