Updating entity framework with related objects from detached copy - entity-framework

I am not sure I am even explaining this right since I don't know the correct terms. I am also not sure if I am using EF 4.0 or 4.1.
I have an Entity model generated from my DB. My DB has a one-to-many relationship between a Survey and an Answer. My data layer gets a Survey object from the model (with an Answers sub-collection) and sends it over a Web Service to the front-end, where it is updated (both the survey data and the associated answers may get updated). I gather this is called a 'detached' object from the Entity model?
Anyway, after the editing, it is sent back to the data layer to get saved, and here is the problem. I tired to do the following 3 options, based on answers to other question here at SO:
public bool updateSurvey(Survey surv)
{
Survey target = entity.Surveys.FirstOrDefault(p => p.id == surv.id);
if (target == null)
return false;
//first try - exception at commented line
target.ownerID = surv.ownerID;
target.question = surv.question;
target.title = surv.title;
//target.Answers = surv.Answers;
//second try - replace commented line above with this - different exception
target.Answers.Clear();
foreach (var item in surv.Answers)
{
target.Answers.Add(item);
}
//third try - replace whole above block with this line
entity.Surveys.ApplyCurrentValues(surv);
try
{
entity.SaveChanges();lse;
}
catch (Exception)
{
return false;
}
return true;
}
My third try above finally didn't throw an exception, but the updated answers were not saved (I don't know about the other properties of surv, since in this case I didn't change them).
So, bottom line - how do I save this Survey and all attached Answers? Do I have to manually loop through the answers and save each one? What about atomicity in this case?

Related

Entity Framework DbContext Update Fails if No Change in Field Values

When we pass our DbContext an object whose values have not changed, and try to perform an Update we get a 500 internal server error.
A user may open a dialog box to edit a record, change a value, change it back and then send the record to the database. Also we provide a Backup and Restore function and when the records are restored, some of them will not have changed since the backup was performed.
I was under the impression that a PUT would delete and re-create the record so I didn't feel there would be a problem.
For example, having checked that the Activity exists my ActivityController is as follows:
var activityEntityFromRepo = _activityRepository.GetActivity(id);
// Map(source object (Dto), destination object (Entity))
_mapper.Map(activityForUpdateDto, activityEntityFromRepo);
_activityRepository.UpdateActivity(activityEntityFromRepo);
// Save the updated Activity entity, added to the DbContext, to the SQL database.
if (await _activityRepository.SaveChangesAsync())
{
var activityFromRepo = _activityRepository.GetActivity(id);
if (activityFromRepo == null)
{
return NotFound("Updated Activity could not be found");
}
var activity = _mapper.Map<ActivityDto>(activityFromRepo);
return Ok(activity);
}
else
{
// The save failed.
var message = $"Could not update Activity {id} in the database.";
_logger.LogWarning(message);
throw new Exception(message);
};
My ActivityRepository is as follows:
public void UpdateActivity(Activity activity)
{
_context.Activities.Update(activity);
}
If any of the fields have changed then we don't get the error. Do I have to check every record for equality before the PUT? It seems unnecessary.
Perhaps I have missed something obvious. Any suggestions very welcome.
There is a lot of code missing here.
In your code you call your SaveChangesAsync (not the EF SaveChangesAsync).
Probably (but there is not the code to be sure) your SaveChangesAsync is something that returns false if there is an exception (and is not a good pattern because you "loose" the exception info) or if DbSet.SaveChangesAsync returns 0.
I think (but there is a lot of missing code) that this is your case. If you don't make any changes, SaveChangesAsync returns 0.
EDIT
The System.Exception is raised by your code (last line). EF never throws System.Exception.

EF Core 2.0: How to discover the exact object, in object graph, causing error in a insert operation?

I have a complex and big object graph that I want to insert in database by using a DbContext and SaveChanges method.
This object is a result of parsing a text file with 40k lines (around 3MB of data). Some collections inside this object have thousands of items.
I am able to parse the file correctly and add it to the context so that it can start tracking the object. But when I try to SaveChanges, it says:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: String or binary data would be truncated.
I would like to know if there is a smart and efficient way of discovering which object is causing the issue. It seems that a varchar field is too little to store the data. But it's a lot of tables and fields to check manually.
I would like to get a more specific error somehow. I already configured an ILoggerProvider and added the EnableSensitiveDataLogging option in my dbContext to be able to see which sql queries are being generated. I even added MiniProfiler to be able to see the parameter values, because they are not present in the log generated by the dbContext.
Reading somewhere in the web, I found out that in EF6 there is some validation that happens before the sql is passed to the database to be executed. But it seems that in EF Core this is not available anymore. So how can I solve this?
After some research, the only approach I've found to solve this, is implementing some validation by overriding dbContext's SaveChanges method. I've made a merge of these two approaches to build mine:
Implementing Missing Features in Entity Framework Core - Part 3
Validation in EF Core
The result is...
ApplicationDbContext.cs
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
ValidateEntities();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken())
{
ValidateEntities();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void ValidateEntities()
{
var serviceProvider = this.GetService<IServiceProvider>();
var items = new Dictionary<object, object>();
var entities = from entry in ChangeTracker.Entries()
where entry.State == EntityState.Added || entry.State == EntityState.Modified
select entry.Entity;
foreach (var entity in entities)
{
var context = new ValidationContext(entity, serviceProvider, items);
var results = new List<ValidationResult>();
if (Validator.TryValidateObject(entity, context, results, true)) continue;
foreach (var result in results)
{
if (result == ValidationResult.Success) continue;
var errorMessage = $"{entity.GetType().Name}: {result.ErrorMessage}";
throw new ValidationException(errorMessage);
}
}
}
Note that it's not necessary to override the other SaveChanges overloads, because they call these two.
The Error tells you that youre writing more characters to a field than it can hold.
This error for example would be thrown when you create a given field as NVARCHAR(4) or CHAR(4) and write 'hello' to it.
So you could simply check the length of the values you read in to find the one which is causing your problem. There is at least on which is too long for a field.

Confusing articles and documentation about the differences (if any) between System.Data.EntityState.Add & DbSet.Add

I am working on a C# ASP.NET MVC 5 web application with EF 5. Mapping of my database tables using EF generates a DbContext class and an .edmx file. Today, I was reading a great article about creating generic DAL classes, but I stopped on the following sentence:
Note that using the Entry method to change the state of an entity will
only affect the actual entity that you pass in to the method. It won’t
cascade through a graph and set the state of all related objects,
unlike the DbSet.Add method.
That contradicts what is mentioned in these questions:
http://forums.asp.net/p/2015170/5803192.aspx
http://forums.asp.net/p/2060606/5943259.aspx
Difference between DbSet.Add(entity) and entity.State = EntityState.Added
What is the difference between IDbSet.Add and DbEntityEntry.State = EntityState.Added?
In all the above questions’ answers, all users mentioned that using System.Data.EntityState.Added is exactly the same as using DbSet.Add. But the article I mentioned first states that using System.Data.EntityState.Added will not cascade through the graph.
Based on my test, I conclude that using System.Data.EntityState.Added will cascade through the graph same as in the DBset.Add case. Is the article wrong, or is it my test and the Q&A?
Those methods are the same which you can verify by regular testing, or, if you want to be completely sure - by some exploration of EF 6 code.
DbSet.Add method (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/DbSet.cs)
public virtual TEntity Add(TEntity entity)
{
Check.NotNull<TEntity>(entity, "entity");
this.GetInternalSetWithCheck("Add").Add((object) entity);
return entity;
}
This calls InternalSet<T>.Add(object) method.
DbEntityEntry<T>.State property (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Infrastructure/DbEntityEntry.cs)
public EntityState State
{
get { return _internalEntityEntry.State; }
set { _internalEntityEntry.State = value; }
}
Where _internalEntityEntry is of InternalEntityEntry type.
InternalEntityEntry.State property (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Internal/EntityEntries/InternalEntityEntry.cs)
public virtual EntityState State
{
get { return IsDetached ? EntityState.Detached : _stateEntry.State; }
set
{
if (!IsDetached)
{
if (_stateEntry.State == EntityState.Modified
&& value == EntityState.Unchanged)
{
// Special case modified to unchanged to be "reject changes" even
// ChangeState will do "accept changes". This keeps the behavior consistent with
// setting modified to false at the property level (once that is supported).
CurrentValues.SetValues(OriginalValues);
}
_stateEntry.ChangeState(value);
}
else
{
switch (value)
{
case EntityState.Added:
_internalContext.Set(_entityType).InternalSet.Add(_entity);
break;
case EntityState.Unchanged:
_internalContext.Set(_entityType).InternalSet.Attach(_entity);
break;
case EntityState.Modified:
case EntityState.Deleted:
_internalContext.Set(_entityType).InternalSet.Attach(_entity);
_stateEntry = _internalContext.GetStateEntry(_entity);
Debug.Assert(_stateEntry != null, "_stateEntry should not be null after Attach.");
_stateEntry.ChangeState(value);
break;
}
}
}
}
You see that if entity is detached (your case) and state is Added - the same InternalSet<T>.Add(object) is called.
As for verification by testing:
using (var ctx = new TestDBEntities()) {
// just some entity, details does not matter
var code = new Code();
// another entity
var error = new Error();
// Code has a collection of Errors
code.Errors.Add(error);
var codeEntry = ctx.Entry(code);
// modify code entry and mark as added
codeEntry.State = EntityState.Added;
// note we did not do anything with Error
var errorEntry = ctx.Entry(error);
// but it is marked as Added too, because when marking Code as Added -
// navigation properties were also explored and attached, just like when
// you do DbSet.Add
Debug.Assert(errorEntry.State == EntityState.Added);
}
I don't know the writer of that blog. I do know the writers of the book DbContext though (albeit not in person). They know EF inside-out. So when on page 80 they write
Calling DbSet.Add and setting the State to Added both achieve exactly the same thing.
I know what I'm up to. They do exactly the same thing, which is:
If the entity is not tracked by the context, it will start being tracked by the context in
the Added state. Both DbSet.Add and setting the State to Added are graph operations—
meaning that any other entities that are not being tracked by the context and are reachable
from the root entity will also be marked as Added.
I also know by experience that it works that way. But to remove any doubt, in EF's source code, both DbSet.Add and DbEntityEntry.State (when set to Added) arrive at the same point in ObjectContext that does the actual work:
public virtual void AddObject(string entitySetName, object entity)
It's a feature that continues to delude developers that start working with EF, as is evident from the large number of questions at StackOverflow asking something along the lines of "how come my entities are duplicated?". Julie Lerman wrote an entire blog explaining why this may happen.
This continued delusion made the EF team decide to change this behavior in EF7.
Maybe the writer of the blog you refer to was one of those deluded developers.

How to delete an object by id with entity framework

It seems to me that I have to retrieve an object before I delete it with entity framework like below
var customer = context.Customers.First(c => c.Id == 1);
context.DeleteObject(customer);
context.Savechanges();
So I need to hit database twice. Is there a easier way?
In Entity Framework 6 the delete action is Remove. Here is an example
Customer customer = new Customer () { Id = id };
context.Customers.Attach(customer);
context.Customers.Remove(customer);
context.SaveChanges();
The same as #Nix with a small change to be strongly typed:
If you don't want to query for it just create an entity, and then delete it.
Customer customer = new Customer () { Id = id };
context.Customers.Attach(customer);
context.Customers.DeleteObject(customer);
context.SaveChanges();
Similar question here.
With Entity Framework there is EntityFramework-Plus (extensions library).
Available on NuGet. Then you can write something like:
// DELETE all users which has been inactive for 2 years
ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2))
.Delete();
It is also useful for bulk deletes.
If you dont want to query for it just create an entity, and then delete it.
Customer customer = new Customer() { Id = 1 } ;
context.AttachTo("Customers", customer);
context.DeleteObject(customer);
context.Savechanges();
I am using the following code in one of my projects:
using (var _context = new DBContext(new DbContextOptions<DBContext>()))
{
try
{
_context.MyItems.Remove(new MyItem() { MyItemId = id });
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
if (!_context.MyItems.Any(i => i.MyItemId == id))
{
return NotFound();
}
else
{
throw ex;
}
}
}
This way, it will query the database twice only if an exception occurs when trying to remove the item with the specified ID. Then if the item is not found, it returns a meaningful message; otherwise, it just throws the exception back (you can handle this in a way more fit to your case using different catch blocks for different exception types, add more custom checks using if blocks etc.).
[I am using this code in a MVC .Net Core/.Net Core project with Entity Framework Core.]
This answer is actually taken from Scott Allen's course titled ASP.NET MVC 5 Fundamentals. I thought I'd share because I think it is slightly simpler and more intuitive than any of the answers here already. Also note according to Scott Allen and other trainings I've done, find method is an optimized way to retrieve a resource from database that can use caching if it already has been retrieved. In this code, collection refers to a DBSet of objects. Object can be any generic object type.
var object = context.collection.Find(id);
context.collection.Remove(object);
context.SaveChanges();
dwkd's answer mostly worked for me in Entity Framework core, except when I saw this exception:
InvalidOperationException: The instance of entity type 'Customer' cannot
be tracked because another instance with the same key value for {'Id'}
is already being tracked. When attaching existing entities, ensure
that only one entity instance with a given key value is attached.
Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to
see the conflicting key values.
To avoid the exception, I updated the code:
Customer customer = context.Customers.Local.First(c => c.Id == id);
if (customer == null) {
customer = new Customer () { Id = id };
context.Customers.Attach(customer);
}
context.Customers.Remove(customer);
context.SaveChanges();
A smaller version (when compared to previous ones):
var customer = context.Find(id);
context.Delete(customer);
context.SaveChanges();
In EF Core, if you don't care if the object exists or not, and you just care that it will not be in the DB, the simplest would be:
context.Remove(new Customer(Id: id)); // adds the object in "Deleted" state
context.SaveChanges(); // commits the removal
You don't really need Attach() - it adds the object to the change tracker in the Unchanged state and Remove() adds the object to the tracker in the Deleted state. The most important thing, however, is that you do only one roundtrip to the backend.
Raw sql query is fastest way I suppose
public void DeleteCustomer(int id)
{
using (var context = new Context())
{
const string query = "DELETE FROM [dbo].[Customers] WHERE [id]={0}";
var rows = context.Database.ExecuteSqlCommand(query,id);
// rows >= 1 - count of deleted rows,
// rows = 0 - nothing to delete.
}
}
From official documentation (and the most efficient one I have found so far):
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
await _context.SaveChangesAsync();
Easier and more understandable version.
var customer = context.Find<Customer>(id);
context.Remove(customer);
context.SaveChanges();
Since Entity Framework Core 7 you can use this:
await context.Customers.Where(c => c.Id == 1).ExecuteDeleteAsync();

How to get rid off "An entity object cannot be referenced by multiple instances of IEntityChangeTracker"?

I have a model in Ado.Net EF.
I have a one to many relation and when I want to Add the entities I get the error
"An entity object cannot be referenceed by multiple instances of IEntityChangeTracker"
Any clue?
Something similar to
Template template = new Template();
...
...
while (from < to)
{
Course course = new Course();
.....
template.Course.Add(course);
.....
}
courseEntities.AddToTemplate(template); // Problem line
courseEntities.SaveChanges();
I was getting this message until i started to store the data context in the HttpContext.Items Property. This means you can use the same data context for the current web request. That way you don't end up with 2 data contexts referencing the same entities.
Here is a good post on DataContext Life Management.
I hope it helps.
Dave
"template", or something that it references, has already been added to courseEntities or another context. I don't see anything in the code you show it would do that, but it is certainly happening. Perhaps it's happening in some of the code that you've trimmed. Look at the EntityState property of "template" in the debugger, and look at the EntityState of the properties of "template" as well. This should help you find out which entity instance is already in a context.
I already realize the problem. I have another relation and I get the other entity from another context.
Let me relate my experience with this nasty error and point out the terrain chasing it will take you over leading to a tremendously simple solution.
CompanyGroup is pretty simple. It has a name and it has a Company object.
I started with this:
1 public static void Add(CompanyGroup item)
2 {
3 try
4 {
5 using (Entities scope = new Entities())
6 {
7 scope.AddToCompanyGroup(item);
8 scope.SaveChanges();
9 }
10 }
11 catch (Exception ex)
12 {
13 LogException(ex, item);
14 throw;
15 }
16 }
And got this error:
{"An entity object cannot be
referenced by multiple instances of
IEntityChangeTracker."}
So, I added this between lines 6 and 7:
(IEntityWithChangeTracker)item).SetChangeTracker(null);
That rewarded me with:
{"The object cannot be added to the
ObjectStateManager because it already
has an EntityKey. Use
ObjectContext.Attach to attach an
object that has an existing key."}
So I changed
scope.AddToCompanyGroup(item);
to
scope.Attach(item);
Now it complained about:
{"An object with a temporary EntityKey
value cannot be attached to an object
context."}
(beginning to sound like some of the girls I dated in my youth -- never content -- but I digress)
So I made the entity key null (didn't work) and used the method to create new (didn't work, either)
Along the way, I got this error, too:
{"The source query for this
EntityCollection or EntityReference
cannot be returned when the related
object is in either an added state or
a detached state and was not
originally retrieved using the
NoTracking merge option."}
The Solution?
Replace the core, lines 7 and 8, with:
CompanyGroup newcg = new CompanyGroup();
newcg.GroupName = item.GroupName;
newcg.Company = scope.Company.Where(c => c.CompanyID == item.Company.CompanyID).First();
scope.AddToCompanyGroup(newcg);
scope.SaveChanges();
Essentially, I took the data passed via 'item', and moved it to newly created object of the same type that introduces the same
scope as the one used in the Add.
I hope this is the simplest and correct solution. You need one db context per httprequest.
EF4 Code First template Global.asax.cs
http://gist.github.com/574505
void MvcApplication_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Items[SessionKey] = new Db();
}
void MvcApplication_EndRequest(object sender, EventArgs e)
{
var disposable = HttpContext.Current.Items[SessionKey] as IDisposable;
if (disposable != null)
disposable.Dispose();
}
Please initialize your Entities only one time.
Like as
If You more than one time initialize your Entities.
You will get the error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
ex:
public class Test
{
private Entities db=new Entities();
public static void Add(CompanyGroup item)
{
try
{
db.CompanyGroup.Add(item);
db.SaveChanges();
}
catch (Exception ex)
{
}
}
}
This problem I was solved by removing from the object that I update, extra relationships with other entities (Virtual). Left only their ID.
This is wrong code
dataFileEntity.IterParameterValue = parameterValueEntity.ParameterValue;
dataFileEntity.IterParameterValueId = parameterValueEntity.ParameterValue.Id;
dataFileEntity.ResultParameter = parameterValueEntity.ResultParameter;
dataFileEntity.ResultParameterId = parameterValueEntity.ResultParameter.Id;
dataFileEntity.RawDataResult = result.Value;
This is right
dataFileEntity.IterParameterValueId = parameterValueEntity.ParameterValue.Id;
dataFileEntity.ResultParameterId = parameterValueEntity.ResultParameter.Id;
dataFileEntity.RawDataResult = result.Value;
RequestTestRawDataFileRepository.AddOrUpdate(dataFileEntity);
Я эту проблему решила, убрав из объекта, который я апдейтила лишние связи с другими сущностями (Virtual). Оставила только их id.