I have such entity:
public class Entity1
{
public short Id { get; set; }
public int Entity2Id { get; set; }
public virtual Entity2 Entity2 { get; set; }
}
And have such one to Many relationship:
this.HasRequired(m => m.Entity2)
.WithMany()
.HasForeignKey(m => m.Entity2Id)
.WillCascadeOnDelete(false);
And here is the thinkg which I cannot understand:
For example, evertyhting works fine if I have changed the entity state to Added firstly:
context.Entry(entity1).State = System.Data.EntityState.Added;
entity1.Entity2.Id = 541;
// Since this line `entity1.Entity2Id` value is 0.
context.Entry(entity1.Entity2).State = System.Data.EntityState.Unchanged;
// But now everything is fine, because `entity1.Entity2Id` value is 541 also.
context.Entry(entity1).State = System.Data.EntityState.Unchanged;
But, I don't want to change state to Added, when I am removing the first line, this exception occured:
A referential integrity constraint violation occurred: The property
values that define the referential constraints are not consistent
between principal and dependent objects in the relationship.
Beacuse, entit1.Entity2Id != entity1.Entity2.Id.
And, I don't want manually to change the value.
How, I can make it work without changing the state to Added?
Update:
I have investigated this problem more. From this So question:
This is called property fixup, and used to be done automatically by the generated proxies. However, with DbContext this is no longer the case. According to this Connect issue, this is by design.
Hello, The DbContext template actually doesn't generate classes that
will be used as change tracking proxies - just lazy loading proxies
(which don't do fix-up). We made this decision because change tracking
proxies are complex and have a lot of nuances that can be very
confusing to developers. If you want fix-up to occur before
SaveChanges you can call myContext.ChangeTracker.DetectChanges. ~EF
Team
An alternative is to call DbContext.Entry(entity), which will sync up the entity. This is described in this article: Relationships and Navigation Properties under "Synchronizing the changes between the FKs and Navigation properties"
Related
Using Microsoft.EntityFrameworkCore version 5.0.7 and Npgsql.EntityFrameworkCore.PostgreSQL version 5.0.7, I'm currently stuck trying to remove a relationship and have that change stored. Assume two models:
public class Banana {
public int Id { get; set; }
public Consumer? Consumer { get; set; }
}
public class Consumer {
public int Id { get; set; }
}
I'm able to assign a consumer just fine using
myBanana.Consumer = dbContext.Consumers.First(row => row.Id == 1);
dbContext.Update(myBanana);
dbContext.SaveChanges();
and that works just fine - the database is updated accordingly. However, once that is stored, trying to remove the reference again using
myBanana.Consumer = null;
dbContext.Update(myBanana);
dbContext.SaveChanges();
fails. After saving, the old value is still in the database, not null as I would expect. Interestingly, other changes to the banana are saved just fine.
I'm not sure if I'm hitting a weird issue with Nullables, or if I'm just missing something, so I'd appreciate some hints.
If you want to continue using auto-generated foreign key properties, you have to make sure that the navigations are loaded. If they are lazy-loaded by default and you don't manually load them, the property will already be null before you try to assign null, so EF can't observe a change.
Loading the navigation with .Include(banana => banana.Consumer) works, and so does loading it via dbContext.Entry(myBanana).Reference(banana => banana.Consumer).Load(). After the relevant navigation items are loaded, myBanana.Consumer = null from the example in the question works as expected.
If you have a non-tracking entity to work with (for example because it was generated by Model Binding), you can either get a tracking entity, or change the value of the auto-generated foreign key shadow property directly:
dbContext.Entry(myBanana).Property("ConsumerId").CurrentValue = null;
which also works. This may be a little bit less polished since you depend on a string as the field name to be correct, but it can be a valid option depending on the circumstances.
While saving changes in my database, an exception with the following message is returned:
The property 'OrderId' on entity type 'Order.CustomerDeliveryDetails#CustomerDetails' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
The database is implemented with entity framework core with a 'code first' approach. Order.CustomerDeliveryDetails is an owned type (of the type CustomerDetails) of the entity Order. CustomerDetails has no property called OrderId. As I understand OrderId is a implicit key, generated by entity framework core as a shadow property.
The classes are structured as follows:
public class Order
{
public int Id { get; set; }
public CustomerDetails CustomerDeliveryDetails { get; set; }
}
[Owned]
public class CustomerDetails
{
public string Street { get; set; }
}
The object is updated as follows:
var order = await orderContext.Orders
.Where(o => o.Id== updateOrder.Id)
.FirstOrDefaultAsync();
order.CustomerDeliveryDetails.Street = updateOrder.CustomerDeliveryDetails.Street;
await orderContext.SaveChangesAsync();
What I fail to understand is how OrderId can be modified, when it can't be accessed directly in the code.
The only thing I can think of which might cause this error, is the fact that this update is being run on a timed webjob in Azure. This is hunch is supported by the fact that the update passes the related unit tests. Could this have to do with a race condition?
Update:
I'm fairly certain the error comes from some sort of race condition. The timed webjob loads a list of orders that need to be updated every 2 minutes. The update works fine as long as the list contains less then +-100 orders, but starts to fail once this list gets longer.
The webjob is probably inable to finish updating all the orders within 2 minutes if the list gets to long.
The context is added through dependency injection as follows:
serviceProvider.AddDbContext<OrdersContext>(options => options.UseSqlServer(ctx.Configuration["ConnectionString"], sqlOptions => sqlOptions.EnableRetryOnFailure()));
My best geuss is that the context is being shared between multiple calls of the webjob, which is causing the errors.
This boils down to your database relationships.Are you using database first or code first approach? How are the models defined? Whats the relationship between Order, CustomerDetails and the CustomerDeliveryDetails tables?
Please provide the code and I will be able to help you with the solution.
I have the following entity:
public class Employee
{
public Guid Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
public string State { get; set; }
}
And using fluent API I have configured the owned entity as following:
private void ConfigureEmployee(EntityTypeBuilder<Employee> builder)
{
builder.OwnsOne(x => x.Address, w =>
{
w.Property(x => x.City).HasMaxLength(100);
w.Property(x => x.State).HasMaxLength(100);
});
}
And when I try to delete Employee entity :
var employee = dbContext.Employees.AsNoTracking().FirstOrDefault();
dbContext.Entry(employee).State = EntityState.Deleted;
dbContext.SaveChanges();
I got the following exception:
The entity of type 'Employee' is sharing the table 'Employees' with entities of type 'Employee.Address#Address', but there is no entity of this type with the same key value '{Id: 1ad382d7-4064-49a3-87ee-633578175247}' that has been marked as 'Deleted'.
I have tried the workaround specified Here but it didn't work.
I am using EntityFrameworkCore v2.2.4
The problem you are facing is due to the way you load the entity. When you do:
var employee = dbContext.Employees.AsNoTracking().FirstOrDefault();
You're basically saying to EF: Load this Employee for me but forget about it. Treat it as if you never had loaded it in the first place.
Next, you want to delete it. You know that it's not being tracked by DbContext so you do:
dbContext.Entry(employee).State = EntityState.Deleted;
This is the "key" to the issue. This line tells the DbContext: Hey, Please start tracking this entity and mark it as to be deleted.
The issue: The Employee entity owns an address but the DbContext is not aware that you also want to delete it.
The error message you get offers a lot of insight on the actual error, but it might not be that clear at first sight:
The entity of type 'Employee' is sharing the table 'Employees' with entities of type 'Employee.Address#Address', but there is no entity of this type with the same key value '{Id: 1ad382d7-4064-49a3-87ee-633578175247}' that has been marked as 'Deleted'.
This is saying: The entity Employee with id 4 is marked as Deleted but it also has an entity of type Address which was not added to be deleted. Although you don't have Address declared as a DbSet in your context, it is still an actual Entity as far as EF is concerned.
To fix it while keeping the same code as you have you need to also add the Address to be deleted:
context.Entry(employee.Address).State = EntityState.Deleted;
But:
I'm not sure if this is just a sample or if it's your real code. But, I personally (and I see many also against it) try to avoid as much as possible manipulating the entity states manually. This can get nasty pretty quickly and produce, as you already experienced it not so obvious results. Instead, if you have a DbContext which you load the entity you want to delete, you can avoid messing with states and problems by just changing your code to this:
var employee = dbContext.Employees.First();
// .Remove will also mark the related entities to be deleted
// If Employee is not tracked, it will also start tracking it
// So, it's not necessary to do .Attach()
dbContext.Remove(employee);
dbContext.SaveChanges();
This will work and the entity will be deleted as expected. Of course, if your code is not that and you are, in fact working with entities in a disconnected scenario, then you need to manually set it to be deleted as I showed above.
Edit:
As pointed out in the comments by #IvanStoev, the Remove method is what actually can fix the behavior you are facing. The Remove method will mark the entity itself plus the related ones as Deleted and if not previously tracked, will also start tracking them. From the docs: (emphasis from me)
If the entity is already tracked in the Added state then the context will stop tracking the entity (rather than marking it as Deleted) since the entity was previously added to the context and does not exist in the database.
Any other reachable entities that are not already being tracked will be tracked in the same way that they would be if Attach(Object) was called before calling this method. This allows any cascading actions to be applied when SaveChanges() is called.
DbContext.Remove Method
You have to use cascade delete like below:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasOptional<Standard>(s => s.Address)
.WithMany()
.WillCascadeOnDelete(false);
}
I have created an asp.NET MVC web app with two models using EF Code First which have a 1-to-0..1 relationship.
public class ClassA
{
public int Id {get;set;}
//other properties
public virtual ClassB ClassB {get;set;}
}
public class ClassB
{
public int Id {get;set;}
//other properties
}
In my database, this successfully creates the two tables with ClassA having a Nullable FK for ClassB. This works great, except for the scenario in which a ClassA record is deleted. In that case, any associated ClassB record is left in the database. I know that I can manually remove them in the Delete POST method:
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
ClassA classA = context.ClassA.Include(c => c.ClassB).First(c => c.Id == id);
context.ClassB.Remove(classA.ClassB);
context.ClassA.Remove(classA);
context.SaveChanges();
}
I don't really like this approach because it relies on me not making a mistake and assumes that this method is the only way that a record might be deleted (there might also be direct DELETE SQL statements run against the database at some point).The example I've created here is simple, but my actual application involves a lot of models and associations and it has gotten quite complicated. While I like to think that I am infallible, I have learned from experience that I am not and would rather not rely on myself to ensure that records don't become orphaned =)
How can I force the database to enforce referential integrity so that when a ClassA is deleted, any ClassB's that have foreign keys in that ClassA are also deleted?
SOLVED (kinda)
As suggested by Gert, I used Fluent API to ensure that the right entity was set as the Principle and to set all of the relationships to cascade on delete. I did run into a few issues though, mostly due to there already being data in my database. Fortunately, I am at a point in development where I can simply delete all of the data; otherwise, I'm not sure how I would have fixed this.
First I tried to just add the Fluent API and update-database. I got an error that read in part: "Either the parameter #objname is ambiguous or the claimed #objtype (COLUMN) is wrong" which seemed to be due to EF trying to change the name of an existing FK column. That being the case, I resolved to use 2 migrations: One to remove existing relationships and another to add the newly reconfigured relationships. I had to do this in a rather specific series of events.
Commented out all references to the affected relationships in my controller to avoid errors while updating.
Commented out the relationship in the Model.
public class ClassA
{
public int Id {get;set;}
//other properties
//public virtual ClassB ClassB {get;set;}
}
Add-Migration and Update-Database to remove the existing relationships.
Undid all changes to the model and controller by uncommenting everything that I commented out in step 1 and 2.
Configured the new relationship in OnModelCreating as suggested by Gert to save the new relationships.
modelBuilder.Entity<ClassA>()
.HasOptional(b => b.ClassB)
.WithRequired()
.Map(m => m.MapKey("ClassB_Id"))
.WillCascadeOnDelete();
Add-Migration and Update-Database to create the new relationships. This step failed when there was existing data in my database. If I didn't have the option to simple clear the database of all data, I'm not sure how I would have accomplished this.
In one-to-one associations you always have to think about which entity is the principle entity and which is the dependent one. The dependent one, as the word implies, can't exist without the other.
In your design the principle and dependent are the wrong way round: ClassA is the dependent, ClassB can live on its own. This is how EF happens to interpret the class model. If you want it to do it otherwise, you'll have to add some mapping instructions. Keeping the class model unaltered, this can only be done by the fluent API, for instance in the context's OnModelCreating override:
modelBuilder.Entity<ClassA>().HasOptional(a => a.ClassB)
.WithRequired().Map(m => m.MapKey("ClassAId"))
.WillCascadeOnDelete();
Now there will be a foreign key, ClassAId, in the ClassB table. The cascaded delete makes sure that when a ClassA is deleted, its dependent ClassB is deleted automatically.
Change the foreign key constraint in the sql server database to "ON DELETE CASCADE"
https://stackoverflow.com/a/6260736/1056639
ALTER TABLE dbo.T2
DROP CONSTRAINT FK_T1_T2 -- or whatever it's called
ALTER TABLE dbo.T2
ADD CONSTRAINT FK_T1_T2_Cascade
FOREIGN KEY (EmployeeID) REFERENCES dbo.T1(EmployeeID) ON DELETE CASCADE
I have two entities and there are their POCO:
public class DocumentColumn
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual long? DocumentTypeId { get; set; }
}
public class DocumentType {
public virtual long Id { get; set; }
public virtual string Name { get; set; }
}
There is a relation between those two entities. In the db the relation called:FK_T_DOCUMENT_COLUMN_T_DOCUMENT_TYPE.
When I do:
DocumentColumns.Where(x => x.DocumentTypeId == documentTypeId).ToList();
I get the exception:
{"Metadata information for the relationship 'MyModel.FK_T_DOCUMENT_COLUMN_T_DOCUMENT_TYPE' could not be retrieved. If mapping attributes are used, make sure that the EdmRelationshipAttribute for the relationship has been defined in the assembly. When using convention-based mapping, metadata information for relationships between detached entities cannot be determined.\r\nParameter name: relationshipName"}
I tryed to remove the relationship and the DocumentColumn table and reload them but the code still throws the exception.
Whet does this exception means and how can I solve it?
EDIT:
The exception happens also If I do DocumentColumns.ToList();
(Presuming you are talking about Code First ....)
There is no information in either class to let CF know that there is a relationship between them. It doesn't matter that the database has the info. Entity Framework needs to have a clue about the relationship. You provide only a property with an integer. CF cannot infer a relationship. You must have something in one class or another that provides type or another. This is not a database. It's a data model. Very different things.
But that's not all. I'm guessing that this is a one to many relationship. You could either put a List property into the Document class or a Document property in the DocumentColumn class. If you only do the latter, CF and EF will NOT know about the 1:. It will presume a 1:1 (that is if you leave DocumentId integer in there, otherwise it will presume a 1:0..1). However, I think you could get away with this and then just configure the multiplicity (1:) in fluent API.
UPDATE...reading your question again, I think you are using an EDMX and designer not code first. What are you using to create your POCO classes? Are you doing code gen from the EDMX or just writing the classes. I still think the lack of a navigation property in at least ONE of the types might be the cause of the problem. The ERROR message does not suggest that...I'm only coming to this conclusion by looking at the classes and inferring my understanding of how EF works with the metadata. I could be barking up the wrong tree. FWIW, I have asked the team if they are familiar with this exception and can provide some idea of what pattern would create it. It's pretty bizarre. :)
It seems odd to me that you are using EF with a defined relationship and you are not using the related property. Can you not do:
DocumentColumns.Where(x=>x.DocumentType.Id == documentTypeId).ToList();
This is what I would expect to see in this instance.