Confused about attaching/detaching entities in EF - c#

Using ASP.NET MVC and Entity Framework, I encounter some attach/detach errors when I need it to write changes to the database.
First here's how I get the records from my repository:
public PublishedApplication GetPublishedApplication(int id)
{
return dal.ExecuteFirstOrDefault(
context.PublishedApplication.Include("Customers")
.Where(w => w.Id == id));
}
Note that this is not being detached, and it has MergeOption = AppendOnly.
Another place, I call a method in my repo to attach a customer to the application:
public void AttachCustomer(int applicationId, int customerId)
{
var app = new PublishedApplication{ Id = applicationId};
var customer = new Customer { Id = customerId};
try
{
context.AttachTo("PublishedApplication", app);
context.AttachTo("Customer ", customer );
app.Customers.Add(customer);
context.SaveChanges();
}
catch
{
throw;
}
}
This, of course, gives me an error that the objects (app and customer) are already attached to an object context.
So I need to detach my objects in my repository. This is also more correct as I don't need to throw "query related information" around the web. But how can I do this? If I detach the entity in my GetPublishedApplication method, my graph of related data is lost, and I can't afford that. I need that graph elsewhere on my site. Can't use merge option NoTracking either.
So what options do I have?
Thanks in advance!

I think, there isn't easy solution for reattaching entities.
May be this post will help you:
Reattaching Entity Graphs with the Entity Framework on codeproject.com

Related

EF Core - can't add entity with the same related objects

I am porting app from EF 6 to EF Core 2.2. I have an object with some related objects inside, each one with database generated ID and GUID (db - postgresql).
I'm trying to create a generic method to add a whole object graph with all related objects the same way as in EF 6 - like this:
var res = context.Set<T>().Add(entity);
Before the insert, EF make temporary IDs, which will be replaced with real database IDs.
So, because inside different object I might have exactly the same objects (for better understanding, my subject area is medicine, I have several different analyzes that are performed from the same sample), in EF Core I can't add whole object graph like this - getting errors, for example:
Key (\"ID\")=(5) already exists
But in the EF 6 version, everything used to work - all objects are inserted including inner objects with correct IDs and GUIDs, without duplicates.
In both versions, temporary IDs in the same objects are also equal, but only in EF Core version, I'm getting this error.
I have tried add attributes
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
tried changing the DbContext
modelBuilder.Entity<Sample>().Property(e => e.ID).ValueGeneratedOnAdd();
but neither works for me - I think the problem is not here.
Also I found this article in Microsoft docs, which says
If the graph does contain duplicates, then it will be necessary to process the graph before sending it to EF to consolidate multiple instances into one.
but I'm not sure - is this about my case?
Am I doing this wrong or is it impossible in EF Core 2.2?
Magic sauce: Create an interface and implement it on object you don't want to save on the object graph, then simply do not set that object as modified. The paradigm that I was failing to understand was that I never really wanted to save a 'defining' object during a save when that object was being used to define the object being saved.
I save the defining objects with a separate process. Works perfectly.
public virtual T InsertOrUpdate(T oneObject)
{
T output = null;
if (oneObject.Id == Guid.Empty)
{
output = this.Insert(oneObject);
}
else
{
try
{
_dbContext.ChangeTracker.TrackGraph(oneObject, e =>
{
if (e.Entry.IsKeySet)
{
// See if the entry has interface with 'StaticObject'
List<Type> x = e.Entry.Entity.GetType().GetInterfaces().ToList();
if (x.Contains(typeof(IStaticObject)))
{
_logger.LogWarning($"Not tracking entry {e.Entry}");
}
else
{
e.Entry.State = EntityState.Modified;
}
}
else
{
e.Entry.State = EntityState.Added;
}
});
_dbContext.Entry(oneObject).State = EntityState.Modified;
_dbContext.SaveChanges();
output = oneObject;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Problem updating object {oneObject.Id}");
}
}
return output;
}
public virtual T Insert(T oneObject)
{
try
{
_dbContext.Attach(oneObject);
_dbContext.Entry(oneObject);
_dbContext.SaveChanges();
}
catch (Exception error)
{
_logger.LogError(error.Message, error);
}
return oneObject;
}

Entity Update Issue | C# Entity Framework

I have landed up in a situation here.
I am loading the User from a custom function here :
public User GetUserById(int Id)
{
var User = DBContext.Users.SingleOrDefault(x=>x.Id == Id);
return User;
}
The problem is in the controller where I call this!
var User = GetUserById(Id);
User.Language = UserModel.Language;
//UpdateModel(User); //I tried this but not working.
DBContext.SaveChanges();
The database is not being updated.
I tried loading the User in the controller directly with linq and things go fine.
But, Why is it not working the other way?
Is there any workaround to make it work with this function?
Isn't there any function like DBContext.Users.Update(User). I remember using something similar, but am not able to recollect!
As EBrown said, they need to be the same instance of DbContext. I'm betting your service uses one context and the controller another. So now when it gets to the controller it is disconnected and not tracked by EF.
The solution is to reconnect it to the context used by the controller (there are other ways to achieve this as well).
var User = GetUserById(Id); // returns a disconnected user
User.Language = UserModel.Language;
// Attach back to context and tell EF it is updated
DBContext.Users.Attach(User);
DBContext.Entity(User).State=EntityState.Modified;
DBContext.SaveChanges();
If this is your postback code, you could just as well write aUserUpdate function:
public void UpdateUser(UserModel userViewModel)
{
var userEntity = DBContext.Users.Find(userViewModel.Id); // Get your user from database
Mapper.Map(userViewModel, userEntity); // Use Automapper to move the changed fields into your entity
DbContext.SaveChanges();
}
Then your controller POST is simply:
if (ModelState.IsValid)
{
UpdateUser(UserModel);
// redirect to list or where ever...
}

Updating existing data in EF 6 throws exception - "...entity of the same type already has the same primary key value."

I am trying to update a record using Entity Framework 6, code-first, no fluent mapping or a tool like Automapper.
The entity(Employee) has other composite properties associated with it like Addreess(collection), Department
It is also inherited from a base called User
The save method is as follows, with _dbContext being the DbConext implementation
public bool UpdateEmployee(Employee employee)
{
var entity = _dbContext.Employees.Where(c => c.Id == employee.Id).AsQueryable().FirstOrDefault();
if (entity == null)
{
_dbContext.Employees.Add(employee);
}
else
{
_dbContext.Entry(employee).State = EntityState.Modified; // <- Exception raised here
_dbContext.Employees.Attach(employee);
}
return _dbContext.SaveChanges() > 0;
}
I keep getting the error:
Attaching an entity of type failed because another entity of the same
type already has the same primary key value. This can happen when
using the 'Attach' method or setting the state of an entity to
'Unchanged' or 'Modified' if any entities in the graph have
conflicting key values. This may be because some entities are new and
have not yet received database-generated key values. In this case use
the 'Add' method or the 'Added' entity state to track the graph and
then set the state of non-new entities to 'Unchanged' or 'Modified' as
appropriate.
I have tried the following:
Attaching before setting to EntityState.Modified
Adding AsNoTracking() on querying if the object exists(No exception but DB is not updated) - https://stackoverflow.com/a/23228001/919426
Saving using the base entity _dbContext.Users instead of the Employee entity - https://stackoverflow.com/a/25575634/919426
None of which is working for me now.
What could I have gotten wrong for some of those solutions not to work in my situation?
EF already includes a way to map properties without resorting to Automapper, assuming you do not have navigation properties to update:
public bool UpdateEmployee(Employee employee)
{
var entity = _dbContext.Employees.Where(c => c.Id == employee.Id).AsQueryable().FirstOrDefault();
if (entity == null)
{
_dbContext.Employees.Add(employee);
}
else
{
_dbContext.Entry(entity).CurrentValues.SetValues(employee);
}
return _dbContext.SaveChanges() > 0;
}
This usually generates a better SQL statement since it will only update the properties that have changed.
If you still want to use the original method, you'll get rid of entity from the context, either using AsNoTracking (not sure why it didn't update in your case, it should have no effect, so the problem might be something else) or as modifying your query to prevent it from materializing the entity in the first place, using something like bool exists = dbContext.Employees.Any(c => c.Id == employee.Id) for example.
This worked for myself
var aExists = _db.Model.Find(newOrOldOne.id);
if(aExists==null)
{
_db.Model.Add(newOrOldOne);
}
else
{
_db.Entry(aExists).State = EntityState.Detached;
_db.Entry(newOrOldOne).State = EntityState.Modified;
}
I've encountered the same thing when using a repository and unit of work pattern (as documented in the mvc4 with ef5 tutorial).
The GenericRepository contains an Update(TEntity) method that attempts to Attach then set the Entry.State = Modified. The up-voted 'answer' above doesn't resolve this if you are going to stick to the uow / repo pattern.
I did attempt to use the detach process prior to the attach, but it still failed for the same reason as indicated in the initial question.
The reason for this, it turns out, is that I was checking to see if a record existed, then using automapper to generate an entity object from my dto prior to calling update().
By checking for the existance of that record, i put the entity object in scope, and wasn't able to detach it (which is also the reason the initial questioner wasn't able to detach)... Tt tracked the record and didn't allow any changes after I automapper'ed the dto into an entity and then attempted to update.
Here's the generic repo's implementation of update:
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
This is my PUT method (i'm using WebApi with Angular)
[HttpPut]
public IHttpActionResult Put(int id, Product product)
{
IHttpActionResult ret;
try
{
// remove pre-check because it locks the record
// var e = unitOfWork.ProductRepository.GetByID(id);
// if (e != null) {
var toSave = _mapper.Map<ProductEntity>(product);
unitOfWork.ProductRepository.Update(toSave);
unitOfWork.Save();
var p = _mapper.Map<Product>(toSave);
ret = Ok(p);
// }
// else
// ret = NotFound();
}
catch (DbEntityValidationException ex)
{
ret = BadRequest(ValidationErrorsToMessages(ex));
}
catch (Exception ex)
{
ret = InternalServerError(ex);
}
return ret;
}
As you can see, i've commented out my check to see if the record exists. I guess i'll see how it works if I attempt to update a record that no longer exists, as i no longer have a NotFound() return opportunity.
So to answer the initial question, i'd say don't look for entity==null before making the attempt, or come up with another methodology. maybe in my case, i could dispose of my UnitOfWork after discovery of the object and then do my update.
You need to detach to avoid duplicate primary key exception whist invoking SaveChanges
db.Entry(entity).State = EntityState.Detached;

Entity Framework 6 context not retrieving navigation properties

I have found many other posts but they are nt facing exactly the same problem. And they are using a slightly different code. SO I think it is worth reviewing this.
I´m using EF6 code first, and I created a Client Entity that has some navigation properties.
I´ll post just the relevant code, consider there are a few more properties and foreign keys as well, but not relevant to the problem. Model is generating ok.
public class Client
{
public Client()
{
JobsExperiences = new Collection<JobsExperience>();
CapacitationCourses = new Collection<CapacitationCourse>();
ScholarLevelDetails = new Collection<ScholarLevelDetail>();
Relatives = new Collection<Relative>();
}
public long ClientID { get; set; }
public virtual ICollection<ScholarLevelDetail> ScholarLevelDetails { get; set; }
public virtual ICollection<JobsExperience> JobsExperiences { get; set; }
}
Now I created a ClientServices class where I put all methods that get or send data from and to the data base., ther I have this code, which is working randomly, I´ll try to explain clearly.
internal Client GetClient(string userId, bool lazyLoadingEnabled = true)
{
using (var context = new ApplicationDbContext())
{
context.Configuration.LazyLoadingEnabled=lazyLoadingEnabled;
var client = (from _client in context.Client
where _client.ApplicationUserId == userId
select _client).FirstOrDefault();
return client;
}
}
My objective some cases is to retrieve just the client attributes, and sometimes all attributes including navigation properties.
In my controller I have a line like this
var client = uuc.GetClient(user.Id, false);
or this
var client = uuc.GetClient(user.Id);
When I run the first sentence, the navigation properties are initialized but all has Count=0, even when my DB has records associated. I think, if lazy loading is disabled, it means eager loading is enabled, but it seems not. However, there is no Load() Method in the navigation properties to force load.
When I run the second sentence, the navigation properties throws an exception 'client.ScholarLevelDetails' threw an exception of type 'System.ObjectDisposedException'. This is thrown one line after the sentence, loking at the navigation properties in the watch. However, and this is the weirdest part, if I step back to the sentence and debug stepping into the method, All navigation properties are loaded correctly.
Why the code behaves differently if running at once than running stepping into the method?
I presume the using statement scope finishes before that the navigation properties load, but why disabling lay loading doe snot retrieve them either?
How can I code this to have a consistent behaviour?
I change the query code Ihad in Linq with this one.
internal Client GetClient(string userId, bool lazyLoadingEnabled = true)
{
using (var context = new ApplicationDbContext())
{
context.Configuration.LazyLoadingEnabled = lazyLoadingEnabled;
var client = context
.Client
.Include(s => s.ScholarLevelDetails)
.Include(s => s.JobsExperiences)
.Include(s => s.CapacitationCourses)
.Include(s => s.Relatives)
.FirstOrDefault(s => s.ApplicationUserId == userId);
return client;
}
}
And now it works. however I still have some questions I´d lve to discuss with you readers and colleagues.
Why plain Linq doesn´t work?
Why it doesn matter if lazyloading is enabled or not, this code works the same everytime?
The problem is that your context fell out of scope before the navigational properties could be loaded.
To do what you want, you would need to change how you are working with your context, or just eager load the entities you are going to need via table join(s) using the query syntax that you are using, or via .Include() lambda expressions if you use the lambda query syntax.
using (var context = new ApplicationDbContext())
{
context.Configuration.LazyLoadingEnabled=lazyLoadingEnabled;
var client = (from _client in context.Client
where _client.ApplicationUserId == userId
select _client).FirstOrDefault();
return client; //at this point, your context is gone, and no
//navigational properties will be loaded onto your client object,
//even if you try to navigate them. You may even get exceptions if
//attempting to navigate to some properties.
}
Here is a join example:
var client = (from _client in context.Client
join t in context.Table on _client.Val equals t.val //this will eager load Table property on client.
where _client.ApplicationUserId == userId
select _client).FirstOrDefault();
You should use Include method for properties.
_context.Client.Include(c=>c.JobsExperiences) ... and all props like this
but for you is better not to use lazy loading.
Cause context become inactive after you return from method.
Using EF 6, I had to use (lamba's and Client keyword is unavailable):
using (var context = new SyntheticData.EF.DBContext())
{
var item = (from t in context.TEMPLATEs
.Include("DATASET")
.Include("COLUMNs")
.Include("SORTs")
.Include("FILTERs")
where t.USERID == identityname && t.ID == id select t).FirstOrDefault();
return item;
}
This filled in relationship classes with syntax like:
item.DATASET.xxx
item.COLUMNs[0].xxx
The "using (var context" construct is good practice because it insures your connection to the database will be released back to the pool. If you don't, you can end up running out of conenctions for busy systems.
Just in case this helps someone I was not getting any navigation properties and my problem seemed to be that EF did not properly hookup the properties because I was using an interface as the navigational property type and I needed to use actual type. Unless anyone knows how to use annotations or something to tell EF the actual type that the property is mapped to.

Linq2Sql: Manage DataContext

In the following code doesn't work as
public void Foo()
{
CompanyDataContext db = new CompanyDataContext();
Client client = (select c from db.Clients ....).Single();
Bar(client);
}
public void Bar(Client client)
{
CompanyDataContext db = new CompanyDataContext();
db.Client.Attach(client);
client.SomeValue = "foo";
db.SubmitChanges();
}
This doens't work, I get error msg. "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported."
How do you work with DataContexts throughout an application so you don't need to pass around a reference?
What
They really mean it with 'This is not supported.'. Attaching to an object fetched from another data context is not implemented.
There are a number of workarounds to the problem, the recommended way is by serializing objects, however this is not easy nor a clean approach.
The most simple approach I found is to use a readonly DataContext for fetching objects like this:
MyDataContext dataContext = new MyDataContext()
{
DeferredLoadingEnabled = false,
ObjectTrackingEnabled = false
};
The objects obtained from this context can be attached to another context but only applies to some scenarios.
The PLINQO framework generates detach for all entities making it easy to detach and reattach objects without receiving that error.
public void Foo()
{
CompanyDataContext db = new CompanyDataContext();
Client client = (select c from db.Clients ....).Single();
// makes it possible to call detach here
client.Detach();
Bar(client);
}
public void Bar(Client client)
{
CompanyDataContext db = new CompanyDataContext();
db.Client.Attach(client);
client.SomeValue = "foo";
db.SubmitChanges();
}
Here is the article that describing how the detach was implemented.
http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx
Yep. That's how it works.
You have tagged this asp.net so I guess it's a web app. Maybe you want one datacontext per request?
http://blogs.vertigo.com/personal/keithc/Blog/archive/2007/06/28/linq-to-sql-and-the-quote-request-scoped-datacontext-quote-pattern.aspx
(P.S. It's a lot harder in WinForms!)
I've created data access classes that encapsulate all the communication with Linq2Sql.
These classes have their own datacontext that they use on their objects.
public class ClientDataLogic
{
private DataContext _db = new DataContext();
public Client GetClient(int id)
{
return _db.Clients.SingleOrDefault(c => c.Id == id);
}
public void SaveClient(Client c)
{
if (ChangeSetOnlyIncludesClient(c))
_db.SubmitChanges();
}
}
Ofcourse you will need to keep this object instantiated as long as you need the objects.
Checking if only the rigth object has been changed is altso somewhat bothersom, you could make methods like
void ChangeClientValue(int clientId, int value);
but that can become a lot of code.
Attaching and detaching is a somewhat missing feature from Linq2Sql, if you need to use that a lot, you sould probably use Linq2Entities.
I took a look at this and found that it appears to work fine as long as the original DataContext has been disposed.
Try wrapping the DataContext with using() and make sure your changes occur after you've attached to the second DataContext? It worked for me..
public static void CreateEntity()
{
User user = null;
using (DataClassesDataContext dc = new DataClassesDataContext())
{
user = (from u in dc.Users
select u).FirstOrDefault();
}
UpdateObject(user);
}
public static void UpdateObject(User user)
{
using (DataClassesDataContext dc = new DataClassesDataContext())
{
dc.Users.Attach(user);
user.LastName = "Test B";
dc.SubmitChanges();
}
}
You need to handle object versioning.
An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.
So, if there's no timestamp member or other 'versioning' mechanism provided there's no way for LINQ to determine whether that data has changed - hence the error you are seeing.
I resolved this issue by adding a timestamp column to my tables but there are other ways around it. Rick Strahl has written some decent articles about exactly this issue.
Also, see this and this for a bit more info.

Categories

Resources