I'm using Entity Framework 6 and I keep getting the following error:
Violation of PRIMARY KEY constraint 'PK_AssignmentType'. Cannot insert duplicate key in object 'dbo.AssignmentType'.
As you see I tried to change the state of the entity AssignmentType to unchanged. When I assign the entity Assignment the foreign key of AssignmentType and then set the navigation property AssignmentType to null, I still get the same error (in the foreach loop of Assignments, not in the Comments foreach loop).
Where does Entity Framework keeping track of the entity AssignmentType and why does it still think it's a new entity and not an existing one?
There is a many-to-many relationship between the entity Reporting and Assignment.
[Route("")]
[HttpPost]
public IHttpActionResult Add(ReportingDTO data)
{
Reporting reporting = new Reporting { ID = Guid.NewGuid(), Date = DateTime.Now };
foreach (Assignment assignment in data.Assignments)
{
_db.Entry(assignment.AssignmentType).State = EntityState.Unchanged;
if (assignment.Reporting.Count > 0)
{
_db.Entry(assignment).State = EntityState.Added;
_db.Entry(assignment).State = EntityState.Modified;
}
reporting.Assignment.Add(assignment);
}
foreach (Comment comment in data.Comments)
{
comment.ID = Guid.NewGuid();
comment.AssignmentID = comment.Assignment.ID;
comment.Assignment = null;
comment.ReportingID= reporting.ID;
}
using(var transaction = _db.Database.BeginTransaction())
{
_db.Reporting.Add(reporting);
_db.Comments.AddRange(data.Comments);
_db.SaveChanges();
transaction.Commit();
}
return Ok(reporting);
}
Reporting.cs
[Table("Reporting")]
public partial class Reporting
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Reporting()
{
Assignment= new HashSet<Assignment>();
}
[Key]
public Guid ID{ get; set; }
[Column(TypeName = "date")]
public DateTime Date{ get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Assignment> Assignment{ get; set; }
}
Assignment.cs
[Table("Assignment")]
public partial class Assignment
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Assignment()
{
Reporting = new HashSet<Reporting>();
}
[Key]
public Guid OID { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
public Guid AssignmentTypeID { get; set; }
[Required]
[StringLength(100)]
public string Project { get; set; }
public bool Completed { get; set; }
[ForeignKey("AssignmentTypeID")]
public virtual AssignmentType AssignmentType { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Reporting> Reporting { get; set; }
}
AssignmentType
[Table("AssignmentType")]
public partial class AssignmentType
{
[Key]
public Guid ID { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
}
Try something like this:
public IHttpActionResult Add(ReportingDTO data)
{
Reporting reporting = new Reporting { ID = Guid.NewGuid(), Date = DateTime.Now };
foreach (Assignment assignment in data.Assignments)
{
if (assignment.ID != null)
{
_db.Assignments.Attach(assignment);
_db.Entry(assignment).State = EntityState.Modified;
}
else
{
assignment.ID = Guid.NewGuid();
}
if (assignment.AssignmentType != null)
{
assignment.AssignmentTypeID = assignment.AssignmentType.ID;
assignment.AssignmentType = null;
}
}
reporting.Assignment.AddRange(data.Assignments);
foreach (Comment comment in data.Comments)
{
comment.ID = Guid.NewGuid();
comment.AssignmentID = comment.Assignment.ID;
comment.Assignment = null;
comment.ReportingID = reporting.ID;
}
_db.Reporting.Add(reporting);
_db.Comments.AddRange(data.Comments);
_db.SaveChanges();
return Ok(reporting);
}
All of your Entry() operations are useless in your context, except if you are updating an entity. In that case, you must Attach() the updated entity before updating its state.
Plus, to answer your initial question: EF adds a new AssignmentType because it has lost tracks of the existing one. Your Controller is constructed every time a request hit it, so your DbContext is also constructed every time. When you create a new DbContext, it has no knowledge of previous operations, like returning an Assignment or whatever (it's also the case if you specify AsNoTracking() during your query). In other words, if the entity is not tracked by the DbContext, then EF will try to add it.
To prevent that, you can (and really should) use the foreign keys instead of the navigation properties. Or you can Attach() every known entity (but it's useless if you have foreign keys). For ManyToMany relationships, you have to query the database before adding/updating a new entity, otherwise, you will have doubloons.
Or, you can also use what I call "link tables". It's just a class that's between your two primary classes. EF already does it for all your ManyToMany relationships (check your database). Basicaly, it goes something like :
public class A
{
public int Id {get; set;}
public virtual List<AB> ABs { get; set; }
}
public class B
{
public int Id {get; set;}
public virtual List<AB> BAs {get; set; }
}
public class AB
{
public int AId {get;set;}
public virtual A {get; set;}
public int BId {get;set;}
public virtual B {get; set;}
}
With that way, you can just add a new AB (or delete one) with _db.ABs.Add(new AB{AId = 1, BId = 2}) without taking care of the rest.
Related
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
There are numerous questions in varying contexts regarding this error and so far I've been unable to find one that applies to my situation.
I have a many to many relationship I'm trying to establish with a 3rd table. It's basically a person to manager association. All managers are people but not all people are managers and also, every person has many managers and every manager has many people. So in the manager's table are PersonIds. In the people controller under edit is the option to associate people with managers. When clicking the available managers and clicking save, I get the error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.ManagerToEngineer_dbo.Manager_ManagerId". The conflict occurred in database "PROJECTNAME_16ce3f6a2a6c4ff0b1ce147d126984ba", table "dbo.Manager", column 'ManagerId'. The statement has been terminated.
The SaveChanges method results in this error:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(PersonViewModel person)
{
if (ModelState.IsValid)
{
//var MyPerson = db.people.Find(person.PersonId);
//MyPerson.FirstName = person.FirstName;
//MyPerson.LastName = person.LastName;
foreach (var item in db.ManagersToEngineers)
{
if (item.EngineerId == person.PersonId)
{
db.Entry(item).State = EntityState.Deleted;
}
}
foreach (var item in person.EngineerManagers)
{
if (item.Checked)
{
db.ManagersToEngineers.Add(new ManagerToEngineer() { EngineerId = person.PersonId, ManagerId = item.Id });
}
}
//db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
Relevant models:
public class ManagerToEngineer
{
[Key]
public int ManagerToEngineerId { get; set; }
[Required]
[ForeignKey("engineer")]
public int EngineerId { get; set; }
[Required]
[ForeignKey("manager")]
public int ManagerId { get; set; }
public virtual Person engineer { get; set; }
public virtual Manager manager { get; set; }
}
public class Manager
{
[Key]
public int ManagerId { get; set; }
[Required]
[ForeignKey("person")]
public int EngineerId { get; set; }
public virtual Person person { get; set; }
public ICollection<ManagerToEngineer> ManagersToEngineers { get; set; }
}
public class PersonViewModel
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Display(Name = "Managers")]
public List<CheckBoxViewModel> EngineerManagers { get; set; }
}
public class CheckBoxViewModel
{
public int Id { get; set; }
public string FullName { get; set; }
public bool Checked { get; set; }
}
First, you should reconsider your models. The phrase:
All managers are people but not all people are managers
does not translate into a many-to-many relationship. A student has many courses and a course has many students is a many-to-many relationship. Your situation translated better as: every manager is a person which hints at inheritance rather than object composition. However, for learning purposes let's go ahead with your example.
The following code (I've changed naming a little bit, please don't use Engineer and Person interchangeably because it adds confusion):
public class Person
{
public Person()
{
Managers = new HashSet<Manager>();
}
[Key]
public int PersonId { get; set; }
public string Name { get; set; }
public virtual ICollection<Manager> Managers { get; set; }
}
public class Manager
{
public Manager()
{
Persons = new HashSet<Person>();
}
[Key]
public int ManagerId { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
public class CompanyContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Manager> Managers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(p => p.Managers).WithMany(m => m.Persons)
.Map(t => t.MapLeftKey("PersonID")
.MapRightKey("ManagerID")
.ToTable("PersonToManager"));
}
}
You will notice why this is not a good design when performing an insertion:
static void Main(string[] args)
{
using (var db = new CompanyContext())
{
// add a person
var person = new Person() { Name = "Joe" };
db.Persons.Add(person);
// "make" him manager
var manager = new Manager();
manager.Persons.Add(person);
db.Managers.Add(manager);
db.SaveChanges();
}
Console.ReadKey();
}
The above code compiles and should run (but I could not test it because I'm having some SqlLocalDb issues). Notice that I don't need to explicitly create a class for the association tables, Entity Framework can deduce that by itself provided that your modelBuilder has the correct definition.
Please remember that the above code is only an example of a many-to-many relationship and not an actually good database design, but I've posted it anyway instead of just giving you a link to see the difference between your code and mine. Please follow the tutorial here for a better understanding and if this is a project meant to (at some point) be more than a learning exercise please read a few tutorials on how database design translates to code (e.g how to design an inheritance relationship in SQL).
You have a FK constraint on ManagersToEngineer so before CUDing anything on ManagersToEngineer, make sure the item referred on foreign tables Engineer and Manager (in your case, Manager) is valid.
To be more specific, does item.Id exist in your Manager table?
I'm using asp.net mvc to make a simple application where i have 3 pages divisions, employees and coffees.
database models
public abstract class BaseModel
{
[Key]
public int Id { get; set; }
}
public class Division : BaseModel
{
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee : BaseModel
{
public string Name { get; set; }
public string FamilyName { get; set; }
public string Function { get; set; }
public Division Division { get; set; }
public virtual ICollection<CoffeeBehavior> CoffeeBehaviors { get; set; }
}
frontend viewmodels
public class DivisionViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<EmployeeViewModel> Employees { get; set; }
}
public class EmployeeViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string FamilyName { get; set; }
public string Function { get; set; }
public DivisionViewModel Division { get; set; }
//public List<CoffeeBehaviorViewModel> CoffeeBehaviors { get; set; }
public int SelectedId { get; set; }
public List<DivisionListViewModel> divisions = new List<DivisionListViewModel>();
public IEnumerable<SelectListItem> DivisionItems => new SelectList(divisions, "Id", "Name");
}
i can create divisions edit them whatsoever.
However when i create an employee this results in a duplicate enty for divisions in my database.
instead of adding the employee to my existing division a new division is created with the same name and the employee is added to that one
[HttpPost]
public ActionResult Create(EmployeeViewModel employee)
{
if (ModelState.IsValid)
{
employee.Division = divisionService.GetDivision(employee.SelectedId);
DivisionViewModel dv = divisionService.GetDivision(employee.SelectedId);
employee.Id = employeeService.Create(employee);
dv.Employees.Add(employee);
divisionService.Edit(dv);
return RedirectToAction("Index");
}
return View(employee);
}
this is the create method.
i figured it was because the employee didn't have a generated key yet so i created the employee first and then added it to the division followed by updating the divison in the database.
this resulted in the same duplication but i received an error on updating the division
Attaching an entity of type 'CoffeeManager.DAL.Models.Division' failed
because another entity of the same type already has the same primary
key value. This can happen when using the 'Attach' method or setting
the state of an entity to 'Unchanged' or 'Modified' if any entities in
the graph have conflicting key values. This may be because some
entities are new and have not yet received database-generated key
values. In this case use the 'Add' method or the 'Added' entity state
to track the graph and then set the state of non-new entities to
'Unchanged' or 'Modified' as appropriate.
i know i'm missing something but i have no clue what it is.
So i figured out what was wrong and it was a really dumb mistake. my logic is in the controllers on viewmodels which is totally wrong.
employeeService.Create(employee);
this should be the only thing that is here. the rest of the logic should be in the employeeservice.create method.
instead it should've been done directly on the database model.
public int Create(EmployeeViewModel employeeVm)
{
var employee = mapper.Map<Employee>(employeeVm);
employee.DivisionId = employeeVm.SelectedDivisionId;
return employeeRepository.Insert(employee);
}
like that.
the reason why this is so is because of the tracking of entities. once the model is converted to a viewmodel EF can't track the entity anymore and as such it will be regarded as a new entity once it is converted back.
I'm wondering if this scenario is possible using Entity Framework.
I am using Code-First and have defined a Domain Model as follows:
public class PrintJob
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public virtual ICollection<StockItem> stockItemstoPrint { get; set; }
}
If I leave the above to Entity Framework and add the migration updating the database without adding the Foreign key in the StockItems Model (Which I don't want as I'd rather not have a two-way link) it will create a table for me named PrintJobStockItems which will hold PrintJobID and StockItemID
- This however is fine but I was wondering if I wanted to add a property to the PrintJobStockItems with a bool 'Printed' can it be done and have logic to update that bool value? The reason is, I want to be able to set for each individual stock item whether or not it has been printed - of course not against the stockItem Model as it should not know about PrintJobs.
If I can't achieve this, it means I will have to create a print job for every stock item, which to me isn't ideal.
You can't access the behind the scenes join table, but the workaround is to create 2 one to manys:
public class StockItem
{
public int Id { get; set; } // Identity, Key is default by convention so annotation not needed
public virtual ICollection<StockItemPrintJob> StockItemPrintJobs { get; set; }
}
public class PrintJob
{
public int Id { get; set; } // Identity, Key is default by convention
public virtual ICollection<StockItemPrintJob> StockItemPrintJobs { get; set; }
}
public class StockItemPrintJob
{
[Key, Column(Order = 0)]
public int StockItemId { get; set; }
[Key, Column(Order = 1)]
public int PrintJobId { get; set; }
public bool IsPrinted { get; set; }
public virtual StockItem StockItem{ get; set; }
public virtual PrintJob PrintJob { get; set; }}
}
Then you can do something like
var item = context.StockItemPrintJob.First(sp => sp.StockItemId == stockId && sp.PrintJobId == printJobId);
item.IsPrinted = true;
context.SaveChanges();
I am apparently having a real devil of a time understanding Entity Framework 6 which I am using with ASP.NET MVC 5.
The core of the matter is that I have a really quite simple data model that is typical of any real world situation where I have various business objects that have other business objects as properties (and of course they child objects may in turn have other child business objects) and also various types of lookup/type data (Country, State/Province, LanguageType, StatusType etc.) and I cannot figure out how to save/update it properly.
I keep going back and forth between two error states:
1) I either run into the situation where saving a parent business object results in unwanted duplicate values being inserted into my lookup/type tables (for example saving a business object that has been assigned an existing LanguageType of 'English' will result in another LanguageType for 'English' being inserted into the LanguageType table), or
2) I use some of the suggestions I've seen here and elsewhere on the net (e.g. Saving Entity causes duplicate insert into lookup data, Prevent Entity Framework to Insert Values for Navigational Properties ) to solve issue 1 and then find myself fighting against this same issue: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key .
I will now provide a few code snippets to help build the picture of what I am trying to do and what I am using to do it. First, an example of the entities involved:
public class Customer : BaseEntity
{
public string Name { get; set; }
[LocalizedDisplayName("Contacts")]
public virtual List Contacts { get; set; }
}
public class Contact : BaseEntity
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
[Required]
[ForeignKey("LanguageTypeID")]
public virtual LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
[LocalizedDisplayName("CultureName")]
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
In my controller, I have some code like the following:
foreach(Contact contact in lstContacts)
{
customer.Contacts.Add(contact);
}
if (ModelState.IsValid)
{
repository.Add(customer);
}
Let us suppose that each of the contacts has the same LanguageType of 'English' assigned (and in this example it is the fact that I am trying to save multiple contacts that have the same LanguageType that triggers the ObjectStateManager error). Initially, the repository.Add() code just did a context.SaveChanges() which did not work as expected, so now it looks something like this (Entity variable is a Customer):
try
{
if(Entity.Contacts != null)
{
foreach(Contact contact in Entity.Contacts)
{
var entry = this.context.Entry(contact.Language);
var key = contact.Language.ID;
if (entry.State == EntityState.Detached)
{
var currentEntry = this.context.LanguageTypes.Local.SingleOrDefault(l => l.ID == key);
if (currentEntry != null)
{
var attachedEntry = this.context.Entry(currentEntry);
//attachedEntry.CurrentValues.SetValues(entityToUpdate);
attachedEntry.State = EntityState.Unchanged;
}
else
{
this.context.LanguageTypes.Attach(contact.Language);
entry.State = EntityState.Unchanged;
}
}
}
}
context.Customers.Add(Entity);
context.SaveChanges();
}
catch(Exception ex)
{
throw;
}
Is it fundamentally wrong to expect this to have worked? How am I supposed to save and example like this? I have similar problems saving similar object graphs. When I look at tutorials and examples for EF, they are all simple and they all just call SaveChanges() after doing something very similar to what I am doing here.
I've just recently been using the ORM capabilities of ColdFusion (which is hibernate under the covers) and there are would simply load the LanguageType entity, assign it to the Contact entity, save the Contact entity, assign it to the Customer and then save the Customer.
In my mind, this is the most basic of situations and I cannot believe that it has caused me so much pain - I hate to say it, but using plain old ADO.NET (or heaven forbid, ColdFusion which I really don't enjoy) would have been MUCH simpler. So I am missing SOMETHING. I apparently have a key flaw in my understanding/approach to EF and If somebody could help me to make this work as expected and help me to figure out just where my misunderstanding lies, I would greatly appreciate it. I have spend too many hours and hours on this and it is a waste of time - I have/will have countless examples just like this one in the code I am building so I need to adjust my thinking with respect to EF right now so I can be productive and do approach things in the expected way.
Your help will mean so much and I thank you for it!
Let's consider the following object graph in which a teacher instance is the root object,
Teacher --[has many]--> courses
Teacher --[Has One]--> Department
In entity framework's DbContext, each instance of an object has a State indicating whether the object is Added, Modified, Removed or Unchanged. What happens apparently is the following :
Creating the root object for the first time
In this case, in addition to the newly created root object Teacher, ALL the child objects in the graph will have the State Added as well even if they're already created. The solution for this problem is to include the foreign key property for each child element and use it instead, i.e. Teacher.DepartmentId = 3 for example.
Updating the root object and one of its child elements' properties
Suppose you fetch a teacher object from the db, and you change the Teacher.Name property as well as the Teacher.Department.Name property; in this case, only the teacher root object will have the State marked as Modified, the department's State on the other hand remains Unchanged and the modification won't be persisted into DB; Silently without any warning.
EDIT 1
I used your classes as follows and I don't have a problem with persisting the objects :
public class Customer : BaseEntity
{
public string Name { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Contact : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
public Customer Customer { get; set; }
[ForeignKey("LanguageTypeID")]
public LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
public class ApplicationUser
{
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And used the following Context :
public class Context : DbContext
{
public Context() : base("name=CS") { }
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<LanguageType> LanguageTypes { get; set; }
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//I'm generating the database using those entities you defined;
//Here we're demanding not add 's' to the end of table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Then I created a unit tests class with the following :
[TestMethod]
public void TestMethod1()
{
//our context
var ctx = new Infrastructure.EF.Context();
//our language types
var languageType1 = new LanguageType { ID = 1, Name = "French" };
var languageType2 = new LanguageType { ID = 2, Name = "English" };
ctx.LanguageTypes.AddRange(new LanguageType[] { languageType1, languageType2 });
//persist our language types into db before we continue.
ctx.SaveChanges();
//now we're about to start a new unit of work
var customer = new Customer
{
ID = 1,
Name = "C1",
Contacts = new List<Contact>() //To avoid null exception
};
//notice that we're assigning the id of the language type and not
//an object.
var Contacts = new List<Contact>(new Contact[] {
new Contact{ID=1, Customer = customer, LanguageTypeID=1},
new Contact{ID=2, Customer = customer, LanguageTypeID=2}
});
customer.Contacts.AddRange(Contacts);
//adding the customer here will mark the whole object graph as 'Added'
ctx.Customers.Add(customer);
//The customer & contacts are persisted, and in the DB, the language
//types are not redundant.
ctx.SaveChanges();
}
It all worked smoothly without any problems.
As far as i know there is no build in support for reattaching modified graphs (like the SaveOrUpdate method of nHibernate). Perhaps this or this can help you.