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.
Related
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 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"
I have a File entity and a User entity. The File entity has a 1:1 relationship with the User entity through a property called LastChangeUser (this records the user who last changed the file). There's also a field within the File entity named LastChangeUserId, which is the actual FK relationship. The relationship is one-way: the User entity has no navigation property leading back to the File entity.
class File
{
public int Id { get; set; }
public int? LastChangeUserId { get; set; }
public virtual User LastChangeUser { get; set; }
}
class User
{
public int Id { get; set; }
}
When a File is changed, I need to set the LastChangeUser for the File. I only have the ID of the user to hand, not the complete User object. So, I'm doing this:
file.LastChangeUser = null;
file.LastChangeUserId = userId;
This seems to work on creating the file, when the File object is newly-created (a POCO which is then added to the entity collection).
However, it does not work when updating the file, when the File object is an existing object retrieved (as a proxy) from the DB.
In the latter case, I end up with a NULL in the DB for the LastChangeUserId field. (After a call to SaveChanges, the object has null in both the LastChangeUser and LastChangeUserId fields).
Maybe I'm just doing the wrong thing here? What's the right way? Do I really need to go get the User object in order to set the LastChangeUser property?
The reason #kevin_fitz solution works here is due to the way changetracking and validation in EF work. The default behavior for change tracking in EF is a method called snapshot tracking which essentially clones an initial state of every entity when its first loaded. When you go to save changes in EF the original snapshot for each entity is compared to the current state object (the one which you are modifying) and any differences are persisted to the database.
Along side this EF also performs pre-submit validation on entities (which can FYI be disabled).
In your case you have made two changes to the model which will be detected by the snapshot tracker on save (and they actually conflict). The tracker however will try and process both of these through the validation rules which will pickup that this is a required relationship and cant be set to null. This is why you are seeing this error and why removing the null update fixes your problem.
On a sidenote, you actually only need to update either the object or the key on a navigation property to trigger that database relationship to be updated. For more details on how navigation properties work in EF codefirst checkout my article here: http://blog.staticvoid.co.nz/2012/07/entity-framework-navigation-property.html
I've got some code like this:
Activity[] GetAllActivities() {
using (ScheduleContext ctx = new ScheduleContext())
return ctx.Activities.AsNoTracking().ToArray();
}
The aim to have a very simple in-memory cache of some data: Activities is mapped to a db view which summarizes everything I need.
If I omit AsNoTracking the returned objects are non-deterministically corrupted: properties on the returned objects aren't set correctly, and frequently one object's property value is duplicated in other objects' properties. There's no warning or exception; neither on EF4.3.1 nor EF5rc2. Both CLR 4 and the 4.5 release candidate exhibit the same behavior.
The Activity objects are very simple; consisting solely of non-virtual properties of basic type (int, string, etc.) and have no key nor a relationship with any other entity.
Is this expected behavior? Where can I find documentation about this?
I understand that obviously change tracking cannot work once the relevant DbContext is gone, but I'm surprised the materialized properties are corrupted without warning. I'm mostly worried that I'll forget AsNoTracking somewhere in a more complex scenario and get somewhat plausible but wrong results.
Edit: The entity looks as follows. Thanks Jonathan & Kristof; there is indeed a column that is inferred as the ID!
public class Activity
{
public string ActivityHostKey { get; set; }
public int ActivityDuration { get; set; }
public int ActivityLastedChanged { get; set; }
public string ActivityId { get; set; }//!!!
public string ModuleHostKey { get; set; }
public string ModuleName { get; set; }
...
I think "frequently one object's property value is duplicated in other objects' properties" and that the Activity objects "and have no key" are the key pieces of information here (no pun intended).
When importing a View (which obviously doesn't have a primary key), EF guesses at what the primary key is. If tracking is then enabled, it uses that primary key to make sure only a single copy of each entity is created in memory. This means if you load two rows with the same values for the field EF guessed was the PK, the values for the second row will overwrite the first.
As for the data being "non-deterministically corrupted", that's probably because the database doesn't guarantee the order the rows are returned in, and it's a "last-in-wins" process in EF, so if the order of the records changes from the DB, the record that gets to keep it's values changes too.
Try marking more columns as part of the primary key, or modifying the view (or the DefiningQuery in the EDMX) to contain a column based on the ROW_NUMBER function so you can use it as the primary key.
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.