I have code first entity framework.
when I'm trying to delete row from table that have foreign key to other table i get a exception. after handling the exception when I try load my data again the Navigation Key of selected item that i try to delete is become null
why this happen and how can i fix it.
the delete process is so simple :
public class Person
{
public int ID {get;set;}
public string Name {get;set;}
public Something Something {get;set;}
public ICollection<Profile> Profile {get;set;}
}
try
{
var p = en.Person.First(t => t.ID == 14);
//p.Something here is not null;
en.Remove(p)
en.SaveChanges(); // I'm get an exception here
}
catch { /*error handling */}
finally
{
var p = en.Person.First(t => t.ID == 14);
//but p.Something here is null;
}
but in en.SaveChange() i get an exception.
The DELETE statement conflicted with the FOREIGN KEY constraint
"FK_person_profile
this is a sample of my class.
that SomeThing Property is a Navigation Key.
in first select the Something have value.
but in second select inside finally block its become null.
after fixing the delete problem now I got the same problem after adding object to database.
and that reload function doesn't help.
any suggestion?
I think I understand your problem - you're deleting an entity, it's causing an error and you want to recover. However, in your finally block when you get your entity again from your current context again it's not correct.
The issue is that you're expecting your context to reload from the database, but that's not quite how it works. What you're actually doing is going back to your context, which is a unit of work and retrieving the entity in memory, because the entity your retrieving is flagged for deletion so it's properties are not being loaded. If you want to refresh from the database, you'll need to reload the entity fully using the Entry, using the DbEntityEntry.Reload.
Using your code an an example:
var p = en.Person.First(t => t.ID == 14);
en.Entry(p).Reload();
Related
Using ASP.NET Core 2.2 with EF Core, I have followed various guides in trying to implement the automatic creation of date/time values when creating either a new record or editing/updating an existing one.
The current result is when i initially create a new record, the CreatedDate & UpdatedDate column will be populated with the current date/time.
However first time I edit this same record, the UpdatedDate column is then given a new date/time value (as expected) BUT for some reason, EF Core is wiping out the value of the original CreatedDate which results in SQL assigning a default value.
Required result I need as follows:
Step 1: New row created, both CreatedDate & UpdatedDate column is given a date/time value (this already works)
Step 2: When editing and saving an existing row, I want EF Core to update the UpdatedDate column with the updated date/time only, BUT leave the other CreatedDate column unmodified with the original creation date.
I'm using EF Core code first, and do no want to go down the fluent API route.
One of the guides i was partially following is https://www.entityframeworktutorial.net/faq/set-created-and-modified-date-in-efcore.aspx but neither this or other solutions I've tried is giving the result I am after.
Baseclass:
public class BaseEntity
{
public DateTime? CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
DbContext Class:
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
var entries = ChangeTracker.Entries().Where(E => E.State == EntityState.Added || E.State == EntityState.Modified).ToList();
foreach (var entityEntry in entries)
{
if (entityEntry.State == EntityState.Modified)
{
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
else if (entityEntry.State == EntityState.Added)
{
entityEntry.Property("CreatedDate").CurrentValue = DateTime.Now;
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
UPDATE FOLLOWING ADVICE FROM STEVE IN COMMENTS BELOW
I spent a bit more time debugging today, turns out the methods I posted above are appear to be functioning as expected i.e. when editing an existing row and saving it, only the entityEntry.State == EntityState.Modified IF statement is being called. So what I'm finding is that after saving the entity, the CreatedDate column is being overwitten with a Null value, I can see this by watching the SQL explorer after a refresh. I believe the issue is along the lines of what Steve mentions below "If it is #null then this might also explain the behavior in that it is not being loaded with the entity for whatever reason."
But i'm a little lost in tracing where this CreatedDate value is being dropped somewhere through edit/save process.
Image below shows the result at the point just before the entity is saved following an update. In the debugger I'm not quite sure where to find the entry of the CreatedDate to see what value is held at this step, but it appears to be missing from the debugger list so wandering whether somehow it doesn't know about the existence of this field when saving.
Below is the method I have in my form 'Edit' Razor page model class:
public class EditModel : PageModel
{
private readonly MyProject.Data.ApplicationDbContext _context;
public EditModel(MyProject.Data.ApplicationDbContext context)
{
_context = context;
}
[BindProperty]
public RuleParameters RuleParameters { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
RuleParameters = await _context.RuleParameters
.Include(r => r.SystemMapping).FirstOrDefaultAsync(m => m.ID == id);
if (RuleParameters == null)
{
return NotFound();
}
ViewData["SystemMappingID"] = new SelectList(_context.SystemMapping, "ID", "MappingName");
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(RuleParameters).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!RuleParametersExists(RuleParameters.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool RuleParametersExists(int id)
{
return _context.RuleParameters.Any(e => e.ID == id);
}
}
Possibly one of the reasons for this issue is the fact that I have not included the CreatedDate field in my Edit Razor Page form, so when I update the entity which in turn will run the PostAsync method server side, there is no value stored for the CreatedDate field and therefore nothing in the bag by the tine the savechangesasync method is called in my DbContext Class. But I also didn't think this was necessary? otherwise I'd struggle to see what value there is in the this process of using an inherited BaseEntity class i.e. not having to manually add the CreatedDate & UpdatedDate attribute to every model class where I want to use it...
It may be easier to just give your BaseEntity a constructor:
public BaseEntity()
{
UpdatedDate = DateTime.Now;
CreatedDate = CreatedDate ?? UpdatedDate;
}
Then you can have your DbContext override SaveChangesAsync like:
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken token = default)
{
foreach (var entity in ChangeTracker
.Entries()
.Where(x => x.Entity is BaseEntity && x.State == EntityState.Modified)
.Select(x => x.Entity)
.Cast<BaseEntity>())
{
entity.UpdatedDate = DateTime.Now;
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, token);
}
Possibly one of the reasons for this issue is the fact that I have not included the CreatedDate field in my Edit Razor Page form, so when I update the entity which in turn will run the PostAsync method server side, there is no value stored for the CreatedDate field and therefore nothing in the bag by the tine the savechangesasync method is called in my DbContext Class.
That's true.Your post data does not contains the original CreatedDate,so when save to database, it is null and could not know what the exact value unless you assign it before saving.It is necessary.
You could just add below code in your razor form.
<input type="hidden" asp-for="CreatedDate" />
Update:
To overcome it in server-side,you could assign data manually:
public async Task<IActionResult> OnPostAsync()
{
RuleParameters originalData = await _context.RuleParameters.FirstOrDefaultAsync(m => m.ID == RuleParameters.ID);
RuleParameters.CreatedDate = originalData.CreatedDate;
_context.Attach(RuleParameters).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
I don't suspect EF is doing this, but rather your database, or you're inadvertently inserting records instead of updating them.
A simple test: Put break-points in your SaveChangesAsnc method within both the Modified and Added handlers and then run a unit test that loads an entity, edits it, and saves. Which breakpoint is hit? If the behavior seems to be normal with a simple unit test, repeat again with your code.
If the Modified breakpoint is hit, and only the Modified handler is hit then check the state of the CreatedDate value in the entity modified. Does it still reflect the original CreatedDate? If yes, then it would appear that something in your schema will be overwriting it on save. If no then you have a bug in your code that has caused it to update. If it is #null then this might also explain the behaviour in that it is not being loaded with the entity for whatever reason. Check that the property has not been configured as something like a Computed property.
If the Added breakpoint is hit at all, then this would point at a scenario where you're dealing with a detached entity, such as an entity that was read from a different DB Context and being associated to another entity in the current DB Context and saved as a byproduct. When a DbContext encounters an entity that was loaded and disassociated with a different DbContext, it will treat that entity as a completely new entity and insert a new record. The biggest single culprit for this is invariably MVC code where people pass entities to/from views. Entity references are loaded in one request, serialized to the view, and then passed back on another request. Devs assume they are receiving an entity that they can just associate to a new entity and save, but the Context of this request doesn't know about that entity, and that "entity" isn't actually an entity, it is now a POCO shell of data that the serializer created. It's no different to you newing up a new class and populating fields. EF won't know the difference. The result of this is you will trip the Added condition for your entity, and after completion you will have a duplicate record. (with different PK if EF is configured to treat PKs as Identity)
So an example is an Order screen: When presenting a screen to create a new order I may have loaded the Customer and passed that to the view to display customer information and will want to associate to the new order:
var customer = context.Customers.Single(x => x.CustomerId == 15);
var newOrder = new Order { Customer = customer };
return View(newOrder);
This looks innocent enough. When we go to save the new order after setting their details:
public ActionResult Save(Order newOrder)
{
context.Orders.Add(newOrder);
newOrder.Customer.Orders.Add(newOrder);
context.SaveChanges();
// ...
}
newOrder had a reference to Customer #14, so all looks good. We're even associating the new order to the customer's order collection. We might even want to have updated fields on the customer record to reflect a change to the Modified date. However, newOrder in this case, and all associated data including .Customer are plain 'ol C# objects at this point. We've added the new order to the Context, but as far as the context is concerned, the Customer referenced is also a new record. It will ignore the Customer ID if that is set as an Identity column and it will save a brand new Customer record (ID #15 for example) with all of the same details as Customer ID 14 and associate that to the new order. It can be subtle and easy to miss until you start querying Customers and spotting duplicate looking rows.
If you are passing entities to/from views, I'd be very wary of this gotcha. Attaching and setting modified state is one option, but that involves trusting that the data has not been tampered with. As a general rule, calls to update entities should never pass entities & attach them, but rather re-load those entities, validate row version, validate the data coming in, and only copy across fields you expect should ever be modified before saving the entity associated to the DbContext.
Hopefully that gives you a few ideas on things to check to get to the bottom of the issue.
I need to load records from an existing database into a new database using a LINQ query using LINQPad. If the record exists in the database, then update the name. Otherwise, insert the record. Currently, the new DB is empty. This script will run periodically, so I have to check for updates. I have code that loads the existing and new records into two List for comparison. I loop through the list:
foreach (Coaster oldCoaster in listOfOldCoasters) {
var coaster = listOfNewCoasters.Where(c => c.coasterId == oldCoaster.coasterId).FirstOrDefault();
if (coaster != null) {
coaster.Name = oldCoaster.Name;
} else {
coaster = new Models.Coaster();
coaster.CoasterId = oldCoaster.coasterId;
coaster.Name = oldCoaster.Name;
//newCoasterDbContext.Coasters.Attach(coaster);
newCoasterDbContext.Coasters.Add(coaster);
}
}
newCoasterDbContext.SaveChanges();
When I run the code using the "Add" method, I receive the exception "OriginalValues cannot be used for entities in the Added state." Digging deeper, I see this message:
Cannot insert the value NULL into column 'CoasterId', table 'Coaster'; column does not allow nulls.
I am setting the primary key in question, so I must be missing something about EF as to why this fails. If I uncomment the "Attach" statement and try that instead of "Add", then the script runs, but nothing gets inserted into the database.
My Coaster class:
public class Coaster
{
public System.Guid CoasterId { get; set; }
public string Name { get; set; }
public Coaster()
{
CoasterId = System.Guid.NewGuid();
}
}
Based on other posts, I have tried adding attributes to the PK:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
And I have tried modifying the OnModelCreating method:
HasKey(x => x.CoasterId).Property(x => x.CoasterId).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
These haven't worked for me. What have I missed that is causing Entity Framework to ignore the GUID that I have attached to the primary key field?
You can't fix stupid, but you can fix stupid mistakes. So as I mentioned, in the OnModelCreating method, I had this line:
HasKey(x => x.CoasterId).Property(x => x.CoasterId).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
I thought it didn't work, but that was only because a bit later in the method, there was a line that overwrote change:
Property(x => x.FpoDistrictId).HasColumnName(#"FPODistrictId").HasColumnType("uniqueidentifier").IsRequired().HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
At least I figured it out...
I just changed our database, adding a new column to a table and setting it as a foreign key. We're using EF4 Database first, so I updated the model from the DB, added my new field to the DTOs and everything looked great until I tried to save data to it.
The new column is called DiaryEventId, and because it's a FK there's also a virtual property on the object called DiaryEvent. Here's what the code looks like:
public void SaveDocument(Guid CaseId, string diaryText, string ActivityType)
{
Guid eventTypeId = RepositoryHelper.GetDiaryEventFromCache("Document Uploaded", _commonQueryContext);
//this wanders off and created a diary event object, adds it to
//the context and returns its ID
Guid diaryId = RepositoryHelper.AuditEvent(CaseId, diaryText, commonUpdateContext);
Entities.DocumentMetadata docData = new Entities.DocumentMetadata()
{
Id = Guid.NewGuid();,
ActivityType = activityType,
DiaryEventId = diaryId
};
_commonUpdateContext.DocumentMetadatas.Add(docData);
_commonUpdateContext.SaveChanges();
}
This compiles and runs fine, and when you step through it it appears to function as expected - diaryId is generated and set on the object. But in the DB, it always appears as null.
I've tried calling SaveChanges after adding the diary event (to make sure the key has something to connect to) and I've tried adding the actual DiaryEvent object to the virtual property rather than just the ID - both have the same effect.
When I watch what's going on in SQL Profiler, I can see that the INSERT statement simply ignores my new column completely - it's not listed in the insert columns at all.
Really got no idea what's going on. Any ideas?
Try modifying the object after adding it to the context, just as a test.
_commonUpdateContext.DocumentMetadatas.Add(docData);
Entities.DocumentMetadata docData = new Entities.DocumentMetadata()
{
Id = Guid.NewGuid();,
ActivityType = activityType,
DiaryEventId = diaryId
};
_commonUpdateContext.SaveChanges();
Or is this perhaps a typo?
Entities.DocumentMetadata docData = new Entities.DocumentMetadata()
{
Id = Guid.NewGuid();, <-------------typo?
Beyond that, it will require a bit of info on how you have your entities mapped out. Is there a navigation property between DiaryEvent and DocumentMetadata? Is the key defined as nullable (I'm assuming it is)? Is ActivityType getting persisted correctly?
I currently have an object like this (simplified):
public class Image {
public int Id { get; set; }
public int ExternalId { get; set; }
}
Now let's say I have this method (mostly pseudo-code):
public void GetImage(int externalId) {
var existingImage = db.Images.FirstOrDefault(i => i.ExternalId == externalId);
if (existingImage != null) {
return existingImage;
}
var newImage = new Image() { ExternalId = externalId };
db.Images.Attach(newImage);
db.SaveChanges();
return newImage;
}
Because ExternalId isn't a key, the change tracker won't care if I have "duplicate" images in the tracker.
So now, let's say this method gets called twice, at the same time via AJAX and Web API (my current scenario). It's async, so there are two threads calling this method now.
If the time between calls is short enough (in my case it is), two rows will be added to the database with the same external ID because neither existing check will return a row. I've greatly simplified this example, since in my real one, there's a timing issue as I fetch the "image" from a service.
How can I prevent this? I need the image to be returned regardless if it's new or updated. I've added a Unique Constraint in the database, so I get an exception, but then on the client, the call fails whereas it should use the existing image instead of throwing an exception.
If I understand EF correctly, I could handle this by making ExternalId a primary key and then use concurrency to handle this, right? Is there any way to avoid changing my current model or is this the only option?
If you already have property defining uniqueness of your entity (ExternalId) you should use it as a key instead of creating another dummy key which does not specify a real uniqueness of your entity. If you don't use ExternalId as a key you must put unique constraint on that column in the database and handle exception in your code to load existing Image from the database.
I'm having the same problem that a few of you have had - when trying to insert a new object, EF inserts null values for some of their properties, and the insert fails.
First let me describe the structure of our DB. Its an event management system, in which each event needs to be associated with a practice group, stored in a cache table but ultimately fetched from Active Directory. I manually created the join table - is that a problem? Anyway, so Event has a foreign key pointing to EventPracticeGroup, which has a foreign key pointing to PracticeGroupCache. PracticeGroupCache also has a RegionId pointing to the Regions table.
The problem comes when trying to insert a new EventPracticeGroup object. Below is the code I'm currently using:
var eventPracticeGroup = new EventPracticeGroup();
if (TryUpdateModel<EventPracticeGroup>(eventPracticeGroup))
{
/*
var eventId = EventScheduleRepository.GetById(Convert.ToInt32(Request.QueryString["EventScheduleId"])).EventId;
eventPracticeGroup.Event = EventRepository.GetById(eventId);
eventPracticeGroup.PracticeGroupCache = PracticeGroupCacheRepository.GetById(eventPracticeGroup.PracticeGroupCacheId);
eventPracticeGroup.PracticeGroupCache.Region = RegionRepository.GetById(eventPracticeGroup.PracticeGroupCache.RegionId);
EventPracticeGroupRepository.Add(eventPracticeGroup);
*/
var eventId = EventScheduleRepository.GetById(Convert.ToInt32(Request.QueryString["EventScheduleId"])).EventId;
var theEvent = new Event() { Id = eventId };
EventRepository.Repository.UnitOfWork.Context.AttachTo("Events",theEvent);
var practiceGroupCache = new PracticeGroupCache() { Id = eventPracticeGroup.PracticeGroupCacheId };
practiceGroupCache.Region = new Region() { Id = eventPracticeGroup.PracticeGroupCache.RegionId };
eventPracticeGroup.PracticeGroupCache = practiceGroupCache;
EventPracticeGroupRepository.Add(eventPracticeGroup);
EventPracticeGroupRepository.Save();
return RedirectToAction("Index");
}
Anyway... as you can see, I've just tried using stub objects (no help), and I've also tried actually fetching and setting the objects. The error I get is:
Cannot insert the value NULL into column 'Name', table 'XXXX.dbo.Regions'; column does not allow nulls. INSERT fails. The statement has been terminated.
Obviously name is not a key field. I have checked the EDMX XML - only the Id (primary key columns) have StoreGeneratedPattern set to Identity, as they should (they are int32 identity columns). Not a single foreign key has StoreGeneratedPattern set to identity.
if I set Regions.Name to allow nulls, PracticeGroupCaches.Description throws the same error. It seems that every linked object gets set to null. I did have a look with the debugger, when I used the now commented out code, nothing was null and everything had a value. I even got the RegionRepository to return all of the regions, just to see if one of them somewhere had a null name. None did. There are only 2 in my test DB. Our object context is shared per HTTP request.
Please can anyone help. At this point I would settle for using the dirtiest workaround as long as it worked.
Regards,
Jonathan.
Look what happens when you call this line:
EventPracticeGroupRepository.Add(eventPracticeGroup);
You are adding a new eventPracticeGroup to the context. But eventPracticeGroup has the other related objects:
eventPracticeGroup -> PracticeGroupCache -> Region
And you create new objects for those:
var practiceGroupCache = new PracticeGroupCache() {
Id = eventPracticeGroup.PracticeGroupCacheId };
practiceGroupCache.Region = new Region() {
Id = eventPracticeGroup.PracticeGroupCache.RegionId };
eventPracticeGroup.PracticeGroupCache = practiceGroupCache;
When you add the eventPracticeGroup to the context this whole object graph gets added which means that EF considers all three objects as new which have to be added to the DB. Since you only fill the Id properties other string properties (like Name or Description) are null. Because they are not nullable in the database the INSERT command fails.
But I guess that you don't want to insert the related entities into the DB anyway but only the eventPracticeGroup. So you need to attach them to the context before you add the new object, something like:
var practiceGroupCache = new PracticeGroupCache() {
Id = eventPracticeGroup.PracticeGroupCacheId };
EventRepository.Repository.UnitOfWork.Context.AttachTo(
"PracticeGroupCaches",practiceGroupCache);
practiceGroupCache.Region = new Region() {
Id = eventPracticeGroup.PracticeGroupCache.RegionId };
EventRepository.Repository.UnitOfWork.Context.AttachTo(
"Regions",practiceGroupCache.Region);
eventPracticeGroup.PracticeGroupCache = practiceGroupCache;
EventPracticeGroupRepository.Add(eventPracticeGroup);
BTW as a side note: About this EventRepository.Repository.UnitOfWork.Context.XXX take a look at Ladislav Mrnka's answer here: EF 4.0 IsAttachedTo extension method and error An object with the same key already exists
Try to Add: [DatabaseGenerated(DatabaseGeneratedOption.None)]
On your Id field, Like:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
It seems like you already found the problem and the solution. In your DB schema it seems like the columns don't allow NULL values. So either change all these columns to allow NULL or don't insert null (this is what you currently are trying to do)