Cannot insert duplicate key when directly assigning Foreign Key with EF - entity-framework

Model:
class TableA
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID {get;set;}
}
class TableB
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID {get;set;}
public int TableAID {get;set;}
[ForeignKey("TableAID")]
public virtual TableA TableA {get;set;}
}
Code:
var copyB = sourceDb.TableB.Single(B => B.ID == TableBID_Param); //EG. B.ID == 17
copyB.TableAID = TableAID_Param; //EG. B.TableAID = 5;
destDb.TableB.Add(copyB);
destDb.SaveChanges(); //Error occurs here
I have changed the Foreign Key ID, and the Primary Key should be regenerated

The problem here, is the the copyB object is:
still associated with sourceDb
likely lazy loading TableA (if TableAID has a value)
The TableA navigation property will take precedence over the TableAID foreign key property. DestDb will try to insert the TableA object, and this likely already exists (possibly from a previous successful execution of this flawed code).
You should quarantine your objects between contexts, in a similar way that you should use a separate ViewModel when sending data to a web client - you have control how it gets serialised. This also makes it clear to a future developer that a new Primary Key is expected (not implied)
Extend TableB:
class TableB
{
public string SomeData {get;set;} //Let's pretend there's something interesting to clone
public TableA ContextClone()
{
var clone = new TableA();
clone.SomeData = SomeData;
//Nothing else get's assigned - it's up to the caller to map ForeignKeys
}
}
New Code:
var sourceB= sourceDb.TableB.Single(B => B.ID == TableBID_Param); //EG. B.ID == 17
var clone = sourceB.ContextClone();
clone.TableAID = TableAID_Param; //EG. B.TableAID = 5;
destDb.TableB.Add(clone);
destDb.SaveChanges();

Related

Entity framework creating a new entry in referenced table instead of using the existing entry

I am using EF code first approach and i have three types
public class A
{
public int Id {get; set;}
public C C {get; set;}
}
public class B
{
public int Id {get; set;}
public C C {get; set;}
}
public class C
{
public int Id {get; set;}
}
When I create an entry in B a new row in C is created as expected. I want to refer to the same row created by B in A. Something like below
A a = new A();
a.C = GetC(id);
dbContext.A.Add(a);
public C GetC(int id)
{
return dbContext.C.FirstOrDefault(c => c.id == id);
}
When i do the above a new entry in C is created. How can I avoid the new entry I want to use the same old entry created by B?
I have tried doing the following but it results in failure citing another entity of the same type already has the same primary key value.
A a = new A();
a.C = dbContext.C.FirstOrDefault(c => c.id == id);
dbContext.A.Add(a);
If you work in ef, then there should be foreign keys (as in the database), and if you add the foreign key to a, this should be enough for make relationships.
But it is in your example that you can use the following:
A a = new A();
a.C = dbContext.C.AsNoTracking().FirstOrDefault(c => c.id == id);
dbContext.A.Add(a);
After AsNoTracking - dbcontect will not cache and track it.
You can read more about it: https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.dbextensions.asnotracking?view=entity-framework-5.0.0

EntityFramework: Model 1:0..1 relationship with fluent api + conventions

I have the following classes generated from an edmx model:
public partial class A
{
public int Id { get; set; }
public virtual B B { get; set; }
}
public partial class B
{
public int Id { get; set; }
public virtual A A { get; set; }
}
The existing db doesn't use the EF default which expects A.Id to be the primary key of table B:
CREATE TABLE [dbo].[B] (
[Id] INT IDENTITY (1, 1) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[A] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[BId] INT NULL,
CONSTRAINT [fk] FOREIGN KEY ([BId]) REFERENCES [dbo].[B] ([Id])
);
With an edmx model, I can explicitly configure the multiplicity of each end, but I haven't found how to get the equivalent model using the fluent-api. When I do something like the following and generate a new db, the foreign key gets placed in table A instead of table B.
modelBuilder.Entity<A>().HasOptional(a => a.B).WithRequired(b => b.A);
I'm guessing I need to use a convention, but so far I've been unable to get the desired output.
UPDATE:
The closest solution I've found so far is to use the following which generates the correct SQL in the db:
modelBuilder.Entity<A>()
.HasOptional(a => a.B)
.WithOptionalDependent(b => b.A)
.Map(c => c.MapKey("BId"));
However, it's conceptually modeled as a 0..1:0..1 relationship and I haven't found how to set a CASCADE delete rule that deletes B when A is deleted.
I wasn't able to find a direct solution, but using the following code seems to meet my requirements of preserving the existing schema and creating a conceptual model that has the same multiplicities & delete behaviors as my original edmx model.
I'd still be interested in any solutions that don't require updating the conceptual model during the post-processing IStoreModelConvention.
{
var overridesConvention = new OverrideAssociationsConvention();
modelBuilder.Conventions.Add(overridesConvention);
modelBuilder.Conventions.Add(new OverrideMultiplictyConvention(overridesConvention));
}
private class OverrideAssociationsConvention : IConceptualModelConvention<AssociationType>
{
...
public List<AssociationEndMember> MultiplicityOverrides { get; } = new List<AssociationEndMember>();
public void Apply(AssociationType item, DbModel model)
{
if (multiplicityOverrides.Contains(item.Name))
{
// Defer actually updating the multiplicity until the store model is generated
// so that foreign keys are placed in the desired tables.
MultiplicityOverrides.Add(item.AssociationEndMembers.Last());
}
if (cascadeOverrides.Contains(item.Name))
{
item.AssociationEndMembers.Last().DeleteBehavior = OperationAction.Cascade;
}
}
}
private class OverrideMultiplictyConvention : IStoreModelConvention<EdmModel>
{
private readonly OverrideAssociationsConvention overrides;
public OverrideMultiplictyConvention(OverrideAssociationsConvention overrides)
{
this.overrides = overrides;
}
public void Apply(EdmModel item, DbModel model)
{
overrides.MultiplicityOverrides.ForEach(o => o.RelationshipMultiplicity = RelationshipMultiplicity.One);
}
}

Inlining collections with EF Projection

I have the following classes:
public class Customer {
public int Id {get;set;}
public string Name {get;set;}
public List<Order> Orders {get;set;}
//other attributes
}
public class Order{
public int Id {get;set;}
public string Name {get;set;}
public decimal Value {get;set;}
}
Given a customerId I wish to only select the customer name and the order Id using projection in EF.
I am doing the following:
IQueryable<Customer> customer = DataContextFactory.GetDataContext().Set<Customer>();
var tempCustomer = customer.Where(x => x.Id == customerId).Select( c=>
new
{
Name = c.Name
}
)
This gives me the customer name. Which I can then pass back to the entity like so:
var customerToReturn = tempCustomer.ToList().Select(x => new Customer
{ Name = x.Name});
If I include the order on the query like this:
var tempCustomer = customer.Where(x => x.Id == customerId).Select( c=>
new
{
Name = c.Name,
Orders = new {
Id = c.Orders.Id
}
}
)
Then I get a duplicate customer per order line (as per the SQL generated by EF). Is there a way I can inline this generation into a single SQL call?
Currently I am getting around this by calling each child object separately.

How does Entity framework implement deleting parent and a child record on the ame SaveChanges()

I have the following two entity sets representing Dept & Emp:-
public partial class Dept
{
public Dept()
{
this.Emps = new HashSet<Emp>();
}
public int DeptID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Emp> Emps { get; set; }
}
public partial class Emp
{
public int EmpID { get; set; }
public string Fname { get; set; }
public string Lname { get; set; }
public int DeptID { get; set; }
public virtual Dept Dept { get; set; }
}
Now I wrote this test action method , which will try to delete a dept which have one Emp assigned to it, as follow:-
public ActionResult Test()
{
Dept d = db.Depts.SingleOrDefault(a=>a.id ==1);
Emp e = db.Emps.SingleOrDefault(a => a.id == 100);
db.Entry(d).State = EntityState.Deleted;
db.Emps.Remove(e);
db.SaveChanges();
return Content("done");
}
I thought that an exception will be raised when calling this action method, since the Dept with id=1 already has one emp with id=100. But what happened is that EF has removed the emp first, then the dept. As a final result the above action method, deleted both the dept with id=1 and emp with id =100.. so can you advice on this please? Bearing in mind that if I try to delete the Dept only as follows:
public ActionResult Test()
{
Dept d = db.Depts.SingleOrDefault(a=>a.id ==1);
//Emp e = db.Emps.SingleOrDefault(a => a.id == 100);
db.Entry(d).State = EntityState.Deleted;
//db.Emps.Remove(e);
db.SaveChanges();
return Content("done");
}
I will get the following exception:-
The DELETE statement conflicted with the REFERENCE constraint \"FK_Emp_ToTable\". The conflict occurred in database \"C:\USERS\...\DOCUMENTS\VISUAL STUDIO 2013\PROJECTS\WEBAPPLICATION19\WEBAPPLICATION19\APP_DATA\DATABASE1.MDF\", table \"dbo.Emp\", column 'DeptID'.\r\nThe statement has been terminated."}
So can anyone advice on how EF is implementing these scenarios?
EDIT
i check the sql profiler for my action method and i noted the following 2 delete sql statments:-
exec sp_executesql N'delete [dbo].[Emp]
where ([EmpID] = #0)',N'#0 int',#0=100
exec sp_executesql N'delete [dbo].[Dept]
where ([DeptID] = #0)',N'#0 int',#0=1
so it have deleted the emp first then the dept,, so how EF determine the order , you mentioned it is smart enough to know ,, but what govern this behavior ?
You tried to delete a Dept which has a collection of Emp assigned to it.
The following exception occurred
The DELETE statement conflicted with the REFERENCE constraint
That means that there's a constraint to the Dept - Emp relationship.
My guess is that it's an one - to many relationship, with one Dept being optional to Emp.
I can tell it's optional because the foreign key DeptID is a Nullable<int>.
When you try to delete a Dept, you get an exception because dept is referenced in Emp.
In the first action method you deleted by primary key
Dept d = db.Depts.SingleOrDefault(a=>a.id ==1);
Emp e = db.Emps.SingleOrDefault(a => a.id == 100);
And then used db.Emps.Remove(e); to mark the relationship between as deleted.
If the relationship was optional, Emp would be set to Null using only an SQL Update.
In your case i see two SQL Delete statements were called
The relationship is therefor identifying.
When a primary key of the principal entity is also part of the primary key of the dependent entity, the relationship is an identifying relationship. In an identifying relationship the dependent entity cannot exist without the principal entity. This constraint causes the following behaviors in an identifying relationship:
Deleting the principal object also deletes the dependent object. This is the same behavior as specifying in the model for the relationship.
Removing the relationship deletes the dependent object. Calling the Remove method on the EntityCollection marks both the relationship and the dependent object for deletion.

EF 4: Basic feature missing?

Imagine I have this class
public class Case
{
[Key]
[DataMember]
public int CaseId { get; set; }
[DataMember]
public string Title { get; set; }
[DataMember]
public string PublicStatusName { get; set; }
}
Basically, I want to take the result set of something like this query
select c.Id, c.Title, sp.Name
from Case c
inner join StatusGrouping sg on sg.InternalStatusId = c.StatusId
inner join StatusPublic sp on sp.PublicStatusId = sg.PublicStatusId
where c.Id = 42
and put it into the class above.
I know I can make navigation properties and express foreign key relationships etc. in EF. So one (ugly) possiblity would be to just have a StatusGrouping property on the Case class. And then have a StatusPublic property on the StatusGrouping class, and then have EF hook up the hierarchy when I read my data.
But the StatusGrouping table is just a relation table that I don't care about in this case. Also, I don't care about the PublicStatusId, all I care about is that I want the right StatusPublic.Name mapped into my PublicStatusName in the Case class whenever I fetch a Case from the db.
Is the only way to do this in EF to make a view on the db and map to that?
You can use a Defining Query in the .edmx file. By this way you can map the query directly to your model class.
Please refer http://msdn.microsoft.com/en-us/library/cc982038.aspx for more information on Defining Query.
Your query contains c.StatusId yet your class doesn't. So I added it.
var query = from c in Case
join sg in StatusGrouping on c.StatusId equals sg.InternalStatusId
join sp in StatusPublic on sg.PublicStatusId equals sp.PublicStatusId
where c.CaseId == 42
select new Case { CaseId = c.CaseId, Title = c.Title, PublicStatusName = sp.Name};