My problem is to save existing objects that are part of a many-to-many relationship in a code-first database with EF6.
I am receiving objects from a web service and they look (simplified) like:
public class Car
{
[DataMember]
public virtual string CarId { get; set; }
[DataMember]
public virtual ICollection<Contract> Contracts { get; set; }
}
public class Contract
{
[DataMember]
public virtual string ContractId { get; set; }
[DataMember]
public virtual ICollection<Car> Cars { get; set; }
}
I have a code-first database and set the relationship with:
modelBuilder.Entity<Contract>().HasKey(t => new {t.ContractId});
modelBuilder.Entity<Car>().HasKey(t => new {t.CarId})
.HasMany(c => c.Contracts)
.WithMany(c => c.Cars)
.Map(x =>
{
x.ToTable("CarContracts");
x.MapLeftKey("CarId");
x.MapRightKey("ContractId");
});
When I get a list of cars I can save the first car and EF creates the relation table and the contracts successfully. On the second car the save fails saying "constraint failed UNIQUE constraint failed: Contracts.ContractId", since I'm trying to insert a Contract with the same Id as an already existing Contract.
The solutions I found to this problem is to set the Contract in the dbContext to Attached:
foreach (var contract in car.Contracts)
{
context.Contract.Attach(contract);
}
This throws the same exception as the previous save. When I try to modify the Contract-list of the second car I get a NotSupportedException, the only solution I can think of is recreating the car objects and attach the same Contract-object to them, which seems unnecessarily complicated.
Is there any way to tell EF that my two different Contract-objects actually are the same?
Is there any way to tell EF that my two different Contract-objects
actually are the same?
The only way is they actually are the same object. If they are different instances even having all the same data EF won't recognize them as same entity.
So the only solution is to replace all same id contract instances by a single one.
Or simpler: create an explicit entity representing the N:N relation and then simply build a list to insert as follows:
var toInsert = new List<CarContract>();
foreach(var car in cars)
{
toInsert.AddRange(car.Select(x=>new CarContract {CarId=car.Id,ContractId=x.Id}));
}
I have a situation with EF5 and a complex object. The basics is that I have a parent to child complex object, but the child refers back to the parent, more than once. I have tried various options but am not finding a solution that answers the question. The closest I have got is this answer (option 2c)
My model looks like below:
public class StaffMember
{
public virtual Guid StafId { get; set; }
// other props
// List of leave apps (Approved/Cancelled etc)
public virtual ICollection<StaffLeaveApp> LeaveApps { get; set; }
}
//Staff Leave Application
public class StaffLeaveApp
{
public virtual Guid LeaveId { get; set; }
public virtual Guid StaffId { get; set; }
// other props...
// Leave approved by? (2 approvals required)
public virtual StaffMember ApprovedBy1 { get; set; }
public virtual StaffMember ApprovedBy2 { get; set; }
}
my mappings look like this
public class StaffMap : EntityTypeConfiguration<StaffMember>
{
public StaffMap()
{
ToTable("tblStaffMembers");
HasKey(x => x.StaffId);
// other mappings...
HasMany(x => x.LeaveApps);
}
}
public class StaffLeaveAppMap: EntityTypeConfiguration<StaffLeaveApp>
{
public StaffLeaveAppMap()
{
ToTable("tblStaffMembersLeaveApps");
HasKey(x => x.LeaveId);
Property(x => x.StaffId).HasColumnName("StaffID");
//Child Relationships
HasOptional(x => x.ApprovedBy1).WithMany().Map(m => m.MapKey("LeaveApprovedBy1"));
HasOptional(x => x.ApprovedBy2).WithMany().Map(m => m.MapKey("LeaveApprovedBy2"));
}
}
Table (sorry, no images)
StaffID uniqueidentifier (FK - tblStaffMembers)
LeaveID uniqueidentifier (PK)
LeaveApprovedBy1 uniqueidentifier (FK - tblStaffMembers)
LeaveApprovedBy2 uniqueidentifier (FK - tblStaffMembers)
The business rule says: a staff member has "many" leave applications and a leave application belongs to a single staff member. Each application requires the approval of 2 staff members (managers) before it is "approved".
How would I map the above using EF so that a single staff member has a "many" leave applications (working already) and a leave application is mapped back to a staff member whom approved it for the first approval and then again for the seond approval. If I use the one mapping for "ApprovedBy1" only then EF is happy and all works as expected. The moment I add the second approval mapping EF struggles with the SQL queries it generates.
I am not sure how to tell EF to map back to the StaffMembers table to specify whom approved the application at level 1 and whom approved it at level 2. It almost ends up being a many to many relationship.
Any ideas?
You are looking for the inverse property, which is the property at the other end of an association. In EF, there are two way to mark a property as inverse.
Data annotations: InversePropertyAttribute.
Fluent mapping
As you already have fluent mapping I'll show you how you'd do it there:
HasOptional(x => x.ApprovedBy1).WithMany(x => x.LeaveApps)
.HasForeignKey(s => s.StaffId);
HasOptional(x => x.ApprovedBy2).WithMany()
.Map(m => m.MapKey("LeaveApprovedBy2"));
The HasOptional(...).WithMany(...) pair is a way to map inverse properties. Coming from the other side you can use e.g. HasMany(....).WithOptional(...).
I have two POCO classes:
Order Class:
public class Order
{
public int Id { get; set; }
public int? QuotationId { get; set; }
public virtual Quotation Quotation { get; set; }
....
}
Quotation Class:
public class Quotation
{
public int Id { get; set; }
public virtual Order Order { get; set; }
....
}
Each Order may be made from one or zero quotation, and
each quotation may cause an order.
So I have an "one or zero" to "one or zero" relation, how can I implement this, in EF Code first by Fluent API?
By changing pocos to:
public class Order
{
public int OrderId { get; set; }
public virtual Quotation Quotation { get; set; }
}
public class Quotation
{
public int QuotationId { get; set; }
public virtual Order Order { get; set; }
}
and using these mapping files:
public class OrderMap : EntityTypeConfiguration<Order>
{
public OrderMap()
{
this.HasOptional(x => x.Quotation)
.WithOptionalPrincipal()
.Map(x => x.MapKey("OrderId"));
}
}
public class QuotationMap : EntityTypeConfiguration<Quotation>
{
public QuotationMap()
{
this.HasOptional(x => x.Order)
.WithOptionalPrincipal()
.Map(x => x.MapKey("QuotationId"));
}
}
we will have this DB(that means 0..1-0..1):
with special thanks to (Vahid Nasiri)
#Masoud's procedure was:
modelBuilder.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal()
.Map(o => o.MapKey("OrderId"));
modelBuilder.Entity<Quotation>()
.HasOptional(o => o.Order)
.WithOptionalPrincipal()
.Map(o => o.MapKey("QuotationId"));
It gives:
By changing the code to:
modelBuilder.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal(o=> o.Order);
It gives:
See http://msdn.microsoft.com/en-us/data/jj591620 EF Relationships
An excellent Book
http://my.safaribooksonline.com/book/-/9781449317867
Here is a post from developer from Dec 2010. But still relevant
http://social.msdn.microsoft.com/Forums/uk/adonetefx/thread/aed3b3f5-c150-4131-a686-1bf547a68804
The above article is a nice summary or the possible combinations here.
A solution where dependant Table has key from Primary table is possible.
If you Want Independent Keys where both are Principals in a PK/FK scenario, i dont think you can do it in Code first with Fluent API. If they share a Key, You are OK.
1:1 optional assumes the dependent uses the key from Primary.
But since you need to save one of the tables before the other. You can check one of the Foreign Keys with code. OR add teh second Foreign to Database after Code first has created it.
You will get close. But EF will complain about conflicting Foreign keys if you want both to be Foreign keys. Essentially the A depends on B depends A EF doesnt like, even if the columns are nullable and technically possible on the DB.
Here use this test program to try it. Just comment in an out the Fluent API stuff to try some options.
I could NOT get EF5.0 to work with INDEPENDENT PK/FK 0:1 to 0:1
But of course there are reasonable compromises as discussed.
using System.Data.Entity;
using System.Linq;
namespace EF_DEMO
{
class Program
{
static void Main(string[] args) {
var ctx = new DemoContext();
var ord = ctx.Orders.FirstOrDefault();
//. DB should be there now...
}
}
public class Order
{
public int Id {get;set;}
public string Code {get;set;}
public int? QuotationId { get; set; } //optional since it is nullable
public virtual Quotation Quotation { get; set; }
//....
}
public class Quotation
{
public int Id {get;set;}
public string Code{get;set;}
// public int? OrderId { get; set; } //optional since it is nullable
public virtual Order Order { get; set; }
//...
}
public class DemoContext : DbContext
{
static DemoContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DemoContext>());
}
public DemoContext()
: base("Name=Demo") { }
public DbSet<Order> Orders { get; set; }
public DbSet<Quotation> Quotations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasKey(t => t.Id)
.HasOptional(t => t.Quotation)
.WithOptionalPrincipal(d => d.Order)
.Map(t => t.MapKey("OrderId")); // declaring here via MAP means NOT declared in POCO
modelBuilder.Entity<Quotation>().HasKey(t => t.Id)
.HasOptional(q => q.Order)
// .WithOptionalPrincipal(p => p.Quotation) //as both Principals
// .WithOptionalDependent(p => p.Quotation) // as the dependent
// .Map(t => t.MapKey("QuotationId")); done in POCO.
;
}
}
}
Adapted from this answer, try this.
First, fix your classes:
public class Order
{
public int Id {get; set;}
public virtual Quotation Quotation { get; set; }
// other properties
}
public class Quotation
{
public int Id {get; set;}
public virtual Order Order { get; set; }
// other properties
}
Then use the fluent API like that:
modelBuilder.Entity<Quotation>()
.HasOptional(quote => quote.Order)
.WithRequired(order=> order.Quotation);
Basically, for 1:1 or [0/1]:[0/1] relationships, EF needs the primary keys to be shared.
public class OfficeAssignment
{
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public virtual Instructor Instructor { get; set; }
}
The Key Attribute
There's a one-to-zero-or-one relationship between the Instructor and the OfficeAssignment entities. An office assignment only exists in relation to the instructor it's assigned to, and therefore its primary key is also its foreign key to the Instructor entity. But the Entity Framework can't automatically recognize InstructorID as the primary key of this entity because its name doesn't follow the ID or classnameID naming convention. Therefore, the Key attribute is used to identify it as the key:
https://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application
using DataAnnotations:
public class Order
{
[Key]
public int Id {get; set;}
public virtual Quotation Quotation { get; set; }
}
public class Quotation
{
[Key, ForeignKey(nameof(Order))]
public int Id {get; set;}
public virtual Order Order { get; set; }
}
(Note that this is using EF 6.4.4.)
It's fairly straightforward to specify, as long as you don't want foreign key properties:
modelBuilder
.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal(q => q.Order);
modelBuilder
.Entity<Quotation>()
.HasOptional(q => q.Order)
.WithOptionalDependent(o => o.Quotation);
Notice here the usage of both WithOptionalPrincipal and WithOptionalDependent. This should give you a single foreign key column on the dependent side (Quotation in the example), but with no foreign key properties. If you want the foreign key on the other side, switch "Dependent" and "Principal" around.
(Note that it is not necessary to have both definitions above; WithOptionalDependent will imply the other side is the principal and vice-versa, so you can use only one of them if you wanted, but I find specifying the relationships from both sides helps prevent errors by double declaring things; any conflict will result in a model error to let you know you missed something.)
While there is an index on the foreign key column, the index does not have a unique constraint. While it is possible to add your own unique constraint (which would require a Key IS NOT NULL filter), it doesn't seem to work and you will get exceptions when updating relationships in some cases. I think this is related to the "swapping problem" where EF will perform its updates in separate queries, so enforcing uniqueness would prevent EF from "moving" a key in two steps.
EF seems to handle the association itself internally, without a unique DB constraint:
On either side, assigning an already used reference results in the other usage of the reference being removed automatically. (So if it is already the case that A1 <=> B1 when you opened the context, and then you write A1 => B2, then A1 <=> B1 is removed and A1 <=> B2 is added, regardless of which side you're on.)
If you try to create a duplicate key by assigning the same reference more than once, EF will throw an exception saying "multiplicity constraint violation". (So in the same context, you wrote both A1 => B1 and A2 => B1, or some similar conflicting mapping.)
If you update the DB manually to create a duplicate key situation, when EF encounters this it will throw an exception saying "A relationship multiplicity constraint violation occurred...this is a non-recoverable error."
It does not seem possible in EF6 to map a property to the foreign key column (at least with Fluent API). Attempting to do so results in a non-unique column name exception since it tries to use the same name for both the property and the association separately.
Note also that it is technically incorrect to have two foreign keys (ie: one on both sides). Such an arrangement would actually be two 0..1 to 0..1 associations since there would be nothing to say that keys on both ends should match. This could maybe work if you enforce the relationship some other way, through the UI and/or possibly a database constraint of some kind.
I also notice that there may be a misunderstanding/miscommunication of exactly what a 0..1 to 0..1 association is. What this means, from my understanding and the way EF seems to consider it as well, is that it is a 1 to 1 association that is optional on both sides. So, you can have objects on either side with no relationship. (Whereas a 1 to 0..1 assocation, objects on one side could exist without a relationship, but objects on the other side would always need an object to relate to.)
But 0..1 to 0..1 does not mean that you can have the association travel in one direction and not the other. If A1 => B1, then B1 => A1 (A1 <=> B1). You cannot assign B1 to A1 without also making A1 relate to B1. This is why it is possible for this association to use only a single foreign key. I think some people may be trying to have an association in which this is not true (A1 relates to B1 but B1 does not relate to A1). But that is really not one association but two 0..1 to 0..1 associations.
Update:
It appears that changing my mapping from Cascade.All() to Cascade.AllDeleteOrphan() fixes most of my issues. I still have to explicitly set the Company property on the OperatingState, which seems unnecessary as it's being added to the Company entity, but at least I can work with that during an update. I still need to test that with a create.
If any one can explain that, that would be a big help.
Update 2: After playing with it some more, it appears I don't always have to specify the parent entity.
Original Post
I have 2 related entities
public class Company {
//... fields
public virtual IList<OperatingState> OperatingStates { get; set; }
}
public class OperatingState {
public virtual Company Company { get; set; }// Mapped on CompanyID
public virtual string State { get; set; }
}
And they are mapped like this:
public class CompanyMap : ClassMap<Company> {
public CompanyMap() {
//... fields
HasMany(x => x.OperatingStates)
.Cascade.All()
.Table("OperatingState");
}
}
public class OperatingStateMap : ClassMap<OperatingState> {
public OperatingStateStateMap() {
Id(x => x.ID);
References(x => x.Company);
Map(x => x.State);
}
}
So all is well until I try to update Company with new Operating States
Company company = _repo.GetSingle(123);
company.OperatingStates.Clear();
foreach(string state in form["OperatingStates"].Split(',')) {
company.OperatingStates.Add(new OperatingState(state));
}
_repo.Save(company); // calls ISession.SaveOrUpdate
It bombs out with:
Cannot insert the value NULL into
column 'CompanyID', table
'ConsumerCartel.dbo.CompanyOperatingState';
column does not allow nulls. INSERT
fails. The statement has been
terminated.
However, if I make 2 changes it kind of works
Company company = _repo.GetSingle(123);
// don't clear the list this time;
foreach(string state in form["OperatingStates"].Split(',')) {
OperatingState os = new OperatingState(state);
// explicitly setting the company
os.Company = company;
company.OperatingStates.Add(os);
}
_repo.Save(company); // calls ISession.SaveOrUpdate
Which will add the new states in addition to the old ones, which is not what I want. However, even when explicitly setting the company (which I shouldn't have to do when it's added to a mapped list?) it doesn't work if the list is cleared.
This code has worked on other entities, but not on this one, so I think this should work as written. What am I doing wrong?
Have you tried using Inverse()?
HasMany(x => x.OperatingStates)
.Inverse()
.Cascade.All()
.Table("OperatingState");
I've got the following basic domain model for my MVC website accounts:
public class Account
{
public Account()
{
Details = new AccountDetails( this );
Logon = new LogonDetails(this);
}
public virtual int Id { get; private set; }
public virtual AccountDetails Details { get; set; }
public virtual LogonDetails Logon { get; set; }
...
}
public class AccountDetails
{
// Primary Key
public virtual Account Account { get; set; }
public virtual DateTime Created { get; set; }
...
}
public class LogonDetails
{
// Primary Key
public virtual Account Account { get; set; }
public virtual DateTime? LastLogon { get; set; }
...
}
Both AccountDetails and LogonDetails use a mapping like this:
public class AccountDetailsOverride : IAutoMappingOverride<AccountDetails>
{
public void Override( AutoMap<AccountDetails> mapping )
{
mapping
.UseCompositeId()
.WithKeyReference( x => x.Account, "AccountId" );
mapping.IgnoreProperty( x => x.Account );
}
}
I've split the account details and logon details into separate models since I rarely need that information, whereas I need the userid and name for many site operations and authorization. I want the Details and Logon properties to be lazy-loaded only when needed. With my current mapping attempts I can get one of two behaviors:
# 1 Create table and load successfully, cannot save
Using this mapping:
public class AutoOverride : IAutoMappingOverride<Account>
{
public void Override( AutoMap<Account> mapping )
{
mapping.LazyLoad();
mapping
.References( x => x.Details )
.WithColumns( x => x.Account.Id )
.Cascade.All();
mapping
.References( x => x.Logon )
.WithColumns( x => x.Account.Id )
.Cascade.All();
}
}
The tables are generated as expected. Existing data loads correctly into the model, but I can't save. Instead I get an index out of range exception. Presumably because Account.Details and Account.Logon are both trying to use the same db field for their reference (The Account.Id itself).
#2 Table includes extra fields, does not save properly
Using this mapping:
public class AutoOverride : IAutoMappingOverride<Account>
{
public void Override( AutoMap<Account> mapping )
{
mapping.LazyLoad();
mapping
.References( x => x.Details )
.Cascade.All();
mapping
.References( x => x.Logon )
.Cascade.All();
}
}
I get a table with a separate field for Details_id and Logon_id but they are null since the value of Details.Account.Id is null when the Account is persisted. So, attempting to Session.Get the account results in Details and Logon being null. If I save Account twice, the table is updated correctly and I can load it.
Help...
There must be a way of mapping this hierarchy and I'm missing something simple. Is there a way to help nhibernate pick the proper field (to solve #1) or to have it update dependent fields automatically after save (to solve#2)?
Thanks for any insight you folks can provide.
If I'm understanding your model and desired behavior, what you have is actually a one-to-one relationship between Account and AccountDetails and between Account and LogonDetails. References creates a many-to-one relationship, so that could be your problem; try HasOne instead.
That said, for this and other reasons, I avoid one-to-ones unless absolutely necessary. There may be more than what you're showing, but is it worth the headache and ugly model to avoid loading two DateTime fields?
Finally, and this is somewhat speculation since I have not tested this functionality, NHibernate 2.1's (which FNH has switched to as supported version) mapping XML schema defines a lazy attribute for property elements. The 1.0 release of FNH (should be in the next week or two) will support setting this attribute. As I said, I have not tested it but it would seem that this would allow you to lazy load individual properties which is exactly what you want.