I have the following code:
model = new Option();
model.val1 = newVal1;
model.val2 = newVal2;
model.val3 = newVal3;
//this saves new record just fine
if (recordCount < 1)
{
context.Options.AddObject(model);
context.SaveChanges();
}
else
{
var tempID = from s in context.Options where (s.val1 == newVal1 && s.val2 == newVal2) select s.ID;
var resultsID = tempID.First();
model = context.Options.Single(m => m.ID == resultsID);
if (TryUpdateModel(model, new[] { "val3" }))
{
//this isn't updating the record
context.SaveChanges();
}
}
The database adds a new entry just fine, but isn't updating it. What am I missing? Thanks.
Looking at this code, you first make a new model and set some properties on it:
model = new Option(); // <- A
model.val1 = newVal1;
model.val2 = newVal2;
model.val3 = newVal3;
then, assuming you're heading down the "else" path you do this:
var tempID = from s in context.Options where (s.val1 == newVal1 && s.val2 == newVal2) select s.ID;
var resultsID = tempID.First();
model = context.Options.Single(m => m.ID == resultsID); // <- B
if (TryUpdateModel(model, new[] { "val3" }))
{
//this isn't updating the record
context.SaveChanges();
}
which goes out and finds the entry in context.Options that has the matching ID.
So, now that model, which you created via the new() call (which I've marked with the comment "A") is now cast adrift and you've got a different one - the one you retrieved via the call to context.Options.Single(), which I've marked with the comment "B". It has properties based on what's in the context, not what was in that object you made. That A object is gone now. You've got a new object, B, retrieved from the DB.
So now, you're calling TryUpdateModel on this retrieved object, telling it that val3 is updated, but the value hasn't changed, right? It's whatever you pulled from the context.
So, it's not going to update anything because the model object isn't the one you think it is... the one you updated is waiting to be garbage collected. The one you retrieved hasn't been updated because it still has whatever value it's got for the property val3.
Assuming I follow what you're trying to do here, that's why you're not seeing any updated values in the context.
If you want to change the value of the val3 property on that model object you've retrieved, you need to set it after you retrieve it, otherwise it is overwritten.
If you are using a global context, you will have to update the context itself because it is not soft-link to the database.
context.SaveChanges();
DbContext context = new DbContext();
Check if Configuration.AutoDetectChangesEnabled = true;
Related
In below code , I want to add myEntity object more than once to database using EF Core. but each time with different value on property id but all other properties are the same. How can I do this? because it's only adding 1 row in the database.
I want to do this because I don't want to repeat calling GetCurrentLocalDateTime() for each iteration and also in the else statement.
var myEntity = _mapper.Map<AEntity >(entityDto);
myEntity.updatedAt = _helper.GetCurrentLocalDateTime();
myEntity.CreatedAt = _helper.GetCurrentLocalDateTime();
if (entityDto.ids != null || entityDto.ids.Count > 0)
{
foreach (var id in entityDto.ids)
{
myEntity.id = id;
await _dbContext.myEntities.AddAsync(myEntity);
}
}
else
{
await _dbContext.myEntities.AddAsync(myEntity);
}
await _dbContext.SaveChangesAsync();
You can't add a single instance of a class, change one of its properties, and add it again expecting a new instance to be added to your database. All that will happen is that you change the property of the single instance you have added.
Instead, you will need to map the DTO multiple times, so that you add multiple instance of your entity class to the DbSet.
You also need to use && instead of || in your if condition. Use OR (||) will result in a NullReferenceException if the entityDto.ids collection is null.
var now = _helper.GetCurrentLocalDateTime();
if (entityDto.ids != null && entityDto.ids.Count > 0)
{
foreach (var id in entityDto.ids)
{
var myEntity = _mapper.Map<AEntity>(entityDto);
myEntity.updatedAt = now;
myEntity.CreatedAt = now;
myEntity.id = id;
await _dbContext.myEntities.AddAsync(myEntity);
}
}
else
{
var myEntity = _mapper.Map<AEntity>(entityDto);
myEntity.updatedAt = now;
myEntity.CreatedAt = now;
await _dbContext.myEntities.AddAsync(myEntity);
}
await _dbContext.SaveChangesAsync();
It uses it to add multiple data.Look at these
.AddRangeAsync()
.AddRange()
you can try to keep your data in list and save it all at once with "addrange()"
Yes, it is possible, though I would use caution to ensure that this is only to insert a number of copies:
foreach (var id in entityDto.ids)
{
myEntity.id = id;
await _dbContext.myEntities.AddAsync(myEntity);
_dbContext.Entry(myEntity).State = EntityState.Detached;
}
By detaching the entity, the DbContext will no longer be tracking it, so updating it and adding it again will be treated as a request to add a new entity.
Normally this kind of code results in a bug where developers are trying to reuse a single entity instance to update multiple data rows. This is a rather odd requirement to insert several copies of the same data, so I would ensure it is documented well so future developers don't try repurposing it. :)
I have a very basic example C# program which uses EF6 with DbContext and which contains two classes:
MyTable1 1...n MyTable2
I have the following code:
// Creation of instances and saving them to the database.
using (var context = new EFTestEntities()) {
var myTable1 = new MyTable1() { ID = 1 };
var myTable2 = new MyTable2() { ID = 2 };
myTable1.MyTable2.Add(myTable2);
context.MyTable1.Add(myTable1);
context.SaveChanges();
}
// This part here is what my question is about.
using (var context = new EFTestEntities()) {
var myTable2 = context.MyTable2.Where(e => e.ID == 2).FirstOrDefault(); // First line
myTable2.MyTable1 = null; // Second line
var myTable1AssignedToMyTable2 = myTable2.MyTable1.ID; // Value '1'; third line
}
The second part of the code is where my question is about.
myTable1AssignedToMyTable2 will have the value '1'. I would expect a NullReferenceException.
When starting SQL Server Profiler to see all database accesses I realized that the MyTable1 navigation property of myTable2 is due to lazy loading automatically loaded when it is first accessed. This happens in the third line or when hovering the word MyTable1 in the second line in a debug session.
After the lazy loading took place it can be set to null.
But: If I replace the null assignment above with:
myTable2.MyTable1 = new MyTable1() { ID = 11 };
Then myTable1AssignedToMyTable2 will have the expected value '11'. And no further database accesses take place.
Further Observation:
If I first assign a new value and assign then the old value again it is still ignored. Only when I call DetectChanges() in between the behavior is as expected. Example:
using (var context = new EFTestEntities()) {
var myTable2 = context.MyTable2.Where(e => e.ID == 2).FirstOrDefault();
myTable2.MyTable1 = new MyTable1() { ID = 11 };
// context.ChangeTracker.DetectChanges();
myTable2.MyTable1 = null;
var myTable1AssignedToMyTable2 = myTable2.MyTable1.ID; // Value is still '1'
}
Conclusions:
Assigning the current value again seems to be ignored by EF. Further tests showed that it doesn't matter whether it is null or not.
Sometimes a DetectChanges() is needed for EF to realise that something has changed, sometimes not. Replacing the null assignment by an assignment with a new object (see above, at the middle of this post) doesn't need a DetectChanges() call. But doing exactly this again and setting it back to null needs a DetectChanges() call (see example right above).
Is this correct? It is not very intuitive in my opinion. Assigning null is ignored, assigning a not null value is not ignored.
I am making a POST call to Add a flower to the database, but I want to update a record in another table if the if statement is true. If the statement is not true, it adds the flower without any problems but when I am trying to update PinkRoseId with a new id, it creates a new record instead of updating the old one.
Is there another way to update the database?
public void AddFlower(Flower flowerToAdd, int someId)
{
_flowerContext = _contextUtility.GetFlowerContext(flowerToAdd.FlowerName);
var pinkRoseId = _flowerService.GetFlowerIdByName(flowerToAdd.FlowerName, "PinkRose", "Rose");
if (flowerToAdd.SomeFlowerId.HasValue)
{
var flowerToUpdate = _updateService.UpdateFlowerBySomething(flowerToAdd.FlowerName, flowerToAdd.SomeFlowerId.Value, flowerToAdd.PetalAk);
flowerToUpdate.PinkRoseId = pinkRoseId;
_flowerContext.SaveChanges();
}
var firstId = _petalService.GetFlowerIdByName(flowerToAdd.FlowerName, "rose", "petal");
var secondId = _sunService.GetSunIdByTypeOf(flowerToAdd.FlowerName, flowerToAdd.SomeName, "sun");
flowerToAdd.FlowerId = secondId;
flowerToAdd.SomeOtherId = firstId;
flowerToAdd.CreatedDate = DateTime.UtcNow;
flowerToAdd.CreatedByUserID = someId;
_flowerContext.Flowers.Add(flowerToAdd);
_flowerContext.SaveChanges();
}
On reading more about Entity Framework, there is method AddOrUpdate which adds a new record or updates any existing records. Side note: I am using EF6 (not sure if AddOrUpdate is available in any older versions or not)
Solution 1
if (flowerToAdd.SomeFlowerId.HasValue)
{
var flowerToUpdate = _updateService.UpdateFlowerBySomething(flowerToAdd.FlowerName, flowerToAdd.SomeFlowerId.Value, flowerToAdd.PetalAk);
}
_flowerContext.Flower.AddOrUpdate(flowerToUpdate); -- this did the trick and updated the existing record instead of adding a new one
Using this method is highly discouraged as per my understanding as it can add new records. Yet to find another solution.
Solution 2
I figured out another way to get this done without using AddOrUpdate
var changeRequest = (from x in _flowerContext.FlowerUpdateSomething
where x.FlowerPrimaryId == flowerToUpdate.FlowerPrimaryId
select x).FirstOrDefault();
changeRequest.PinkRoseId = pinkRoseId;
_flowerContext.SaveChanges();
}
Update on Solution 2:
I copied the solution to another service (eg SunFlowerService) like the following:
public SomeDataBaseEntity UpdateFlower(int id)
{
var changeRequest = (from x in _flowerContext.FlowerUpdateSomething
where x.FlowerPrimaryId == id
select x).FirstOrDefault();
}
But now when I call this method like the following:
if (flowerToAdd.SomeFlowerId.HasValue)
{
var flowerToUpdate = _updateService.UpdateFlowerBySomething(flowerToAdd.FlowerName, flowerToAdd.SomeFlowerId.Value, flowerToAdd.PetalAk);
var update = _someOtherService.UpdateFlower(flowerToUpdate.Id);
update.PinkFlowerId = pinkFlowerId;
_flowerContext.SaveChanges();
}
It does not update the record in the database. I am not sure how to solve this problem, or why it would have such behavior. But on keeping the original LINQ query like the following:
var changeRequest = (from x in _flowerContext.FlowerUpdateSomething
where x.FlowerPrimaryId == flowerToUpdate.FlowerPrimaryId
select x).FirstOrDefault();
changeRequest.PinkRoseId = pinkRoseId;
_flowerContext.SaveChanges();
I am able to change the records in the database. Not sure why making another method doesn't work.
I have tried multiple solution found in stack and the internet i have lost a full day of work behind this please have a look to my code
public async Task<ActionResult> Edit(JobeezUserInfoViewModel jobeezUserInfo, HttpPostedFileBase UploadedImage)
{
var user = UserManager.FindById(User.Identity.GetUserId());
//list of languages
var lgs = jobeezUserInfo.Languages.Select(l => l.LanguageId);
//Languages to be deleted
var lngTodel = db.Languages.AsNoTracking()
.Where(ut => ut.JobeezUserInfoId == jobeezUserInfo.ApplicationUserId)
.Where(ut => !lgs.Contains(ut.LanguageId));
//language ids as Ilist for better performace
var ids = lgs as IList<int> ?? lgs.ToList();
//Languages to be added
var lngToAdd = ids
.Where(
lid =>
user.JobeezUserInfo.Languages
.Select(ut => ut.LanguageId) //for each userlanguages create a list if languageids _
.Contains(lid) == false //_check if it does not contain the posted languageids and return the language ids if it is the case(tid is the posted languageid)
)
.Select(tid =>
new Language()
{
JobeezUserInfoId = user.Id,
LanguageId = tid,
Name = Enum.GetName(typeof(Enums.LanguageEnum), tid)
});
//languages to be updated
var lngToUpdate = user.JobeezUserInfo.Languages.Where(l=>ids.Contains(l.LanguageId));
Mapper.CreateMap<JobeezUserInfoViewModel, JobeezUserInfo>(MemberList.Destination);
JobeezUserInfo info = Mapper.Map<JobeezUserInfo>(jobeezUserInfo) as JobeezUserInfo;
user.FirstName = jobeezUserInfo.FirstName;
user.LastName = jobeezUserInfo.LastName;
user.PostCode = jobeezUserInfo.PostCode;
user.PhoneNumber = jobeezUserInfo.Telephone;
if (ModelState.IsValid)
{
//mark modified for the userinfo
db.JobeezUserInfo.Attach(info); // Entity is in Unchanged state
db.Entry(info).State = EntityState.Modified;
And the next line is this - My question is why I cant attach the language object 'l'
lngToUpdate.ForEach(l =>
{
db.Languages.Attach(l);
db.Entry(l).State = EntityState.Modified;
});
I have the error
"An entity object cannot be referenced by multiple instances of
IEntityChangeTracker."
I have opened the the quick view After the following line (one part of the image shows also the input parameters of my controler action (viewmodel object):
//mark modified for the userinfo
db.JobeezUserInfo.Attach(info); // Entity is in Unchanged state
Precision : the languageobject in the db.changetracket.Entities() is the same object that is posted to my server (in my viewModel) why the enitity framework cannot understand that the new object has to be tracked or attached in place of the new language object (My code is partial for the sake of clarity i can post full code if needed)
My question is : I really dont know what is the best method to update the child entities correctly. What am I doing wrong here and how to get this work ?
thanks in advance
I solved my parent child problem by using AsNoTracking().
Here BOND is my Parent table and GOVBONDINTERESTSCHEDULE is its Child. Also FI is another child table of BOND. A simple update Threw an error because of EF tracking. So I simply updated GOVBONDINTERESTSCHEDULE using AsNoTracking().
Below is my working code.
GOVBONDINTERESTSCHEDULE editmodel = new GOVBONDINTERESTSCHEDULE();
editmodel = new Entities(Session["Connection"] as EntityConnection).
GOVBONDINTERESTSCHEDULEs.Include("BOND").AsNoTracking().Where(t => t.REFERENCE == Ref).
SingleOrDefault();
editmodel.STATUS = ConstantVariable.STATUS_APPROVED;
editmodel.LASTUPDATED = DateTime.Now;
editmodel.LASTUPDATEDBY = Session["UserId"].ToString();
using (Entities db = new Entities(Session["Connection"] as EntityConnection)) {
db.Entry(editmodel).State = EntityState.Modified;
db.SaveChanges();
}
I understood that I have two different errors actually
1.
I had this error because lngToUpdate was a wrongly populated one - speaking about the following line (my bad! ;) ).
var lngToUpdate = user.JobeezUserInfo.Languages.Where(l=>ids.Contains(l.LanguageId));
It is the same collection as in the database which was obviously already been tracked.
so I created a new collection with the modified property values for each languages who's properties were to be updated.
2.
Now For the other two lists lngToAdd and lngToUpdate , with the ObjectStateManager gotten buy the following code
var manager = ((IObjectContextAdapter)db).ObjectContext.ObjectStateManager;
I used the manager to change the objectState of the peviously tracked languages as EntityState.unchanged, attched the new ones to the context and marked EntityState.Added EntityState.Deleted respectively (as per my lists).
and it worked! hope this helps someone!
I am implementing an import routine, where a user pastes a specific formatted string into an input field, which in turn gets tranformated into an entity and then put into a database.
The algorithm checks if the entity already exists and either tries to update it or insert it into the database. Inserting works fine - updating fails.
//considered existing if Name and owning user match.
if (db.Captains.Any(cpt => cpt.Name == captain.Name && cpt.User.Id == UserId))
{
var captainToUpdate = db.Captains.Where(cpt => cpt.Name == captain.Name && cpt.User.Id == UserId).SingleOrDefault();
db.Entry(captainToUpdate).CurrentValues.SetValues(captain);
db.Entry(captainToUpdate).State = EntityState.Modified;
await db.SaveChangesAsync();
}
The problem at hand is, that written like this, it tries to update the primary key as well, (captain Id is 0, whereas captainToUpdate Id is already set) which results in an exception The property 'Id' is part of the object's key information and cannot be modified.
What do I need to change, so the enttiy gets updated properly. If it can be avoided I don't want to update every property by hand, because the table Captain contains 30ish columns.
What you can do is first set the Id of captain to be the same as the Id of captainToUpdate:
captain.Id = captainToUpdate.Id;
db.Entry(captainToUpdate).CurrentValues.SetValues(captain);
await db.SaveChangesAsync();
I would not use the entity Captain to transfer the data to the UI, but a DTO object that has all properties you want to copy and no more. You can copy values from any object. All matching properties will be copied, all other properties in captainToUpdate will not be affected.
Try something like this ?
var captainToUpdate = db.Captains.FirstOrDefault(cpt => cpt.Name == captain.Name && cpt.User.Id == UserId);
if(captainToUpdate != null){//Update captain Here
captainToUpdate.Update(captain);
}else{//Create captain here
db.Captains.Add(captain);
}
db.Savechanges();
I had the same issue and solved it by extension method and reflection, of course it will be better to create standalone class with some cachning for relfection, but performance wasn't critical in my task.
public static class EnitityFrameworkHelper
{
public static void SetValuesByReflection(this DbPropertyValues propertyValues, object o, IEnumerable<string> properties = null)
{
var reflProperties = o.GetType().GetProperties();
var prop = properties ?? propertyValues.PropertyNames;
foreach (var p in prop)
{
var refp = reflProperties.First(x => x.Name == p);
var v= refp.GetValue(o);
propertyValues[p] = v;
}
}
}
and here is example how to use it
var entry = ctx.Entry(accSet);
entry.CurrentValues.SetValuesByReflection(eParameters, entry.CurrentValues.PropertyNames.Except(new [] { "ID"}));
Also be careful with foreign keys in object which you want to update, probably you want to exclude them too.