This is an easy question and I want to confirm with you experts!
I am changing some values in two different tables in same database. Is it okay to just call SaveChanges one time to update all? Seems to work but I want to know if its okay or need to call context.SaveChanges after every update?
bool success = false;
TestSuiteDB context = new TestSuiteDB();
var workstyle1 = context.WorkStyle.Where(d => d.WorkStyleId==21 && d.MemberId==1).ToList();
foreach (var ws1 in workstyle1)
{
ws1.ModuleId = flid1;
}
var workstyle2 = context.WorkStyle.Where(d => d.WorkStyleId == 22 && d.MemberId==1).ToList();
foreach (var ws2 in workstyle2)
{
ws2.ModuleId = flid2;
}
var workstylemodules1 = context.WorkStyleModules.Where(d => d.WorkStyleModuleId == 2 && d.MemberId == 1).ToList();
foreach (var ws1 in workstylemodules1)
{
ws1.ModuleId = flid1;
}
var workstylemodules2 = context.WorkStyleModules.Where(d => d.WorkStyleModuleId == 3 && d.MemberId == 1).ToList();
foreach (var ws2 in workstylemodules2)
{
ws2.ModuleId = flid2;
}
if (context.SaveChanges() > 0)
{
success = true;
}
You can call Context.SaveChanges(); once after all the updates and it will save all the changes done previously.
If you are expecting to change/insert a new object, and wants to modify the next objects based on the database's actual value, you may call Context.SaveChanges() so that the database gets updated.
Any changes/insertion to the entities are tracked in the context and once SaveChanges is called, it put the changes in the database as well.
Also keep this in mind:
From MSDN:
SaveChanges operates within a transaction. SaveChanges will roll back
that transaction and throw an exception if any of the dirty
ObjectStateEntry objects cannot be persisted.
I think it's better to call it once.you may also need a transactional code if you want to get a block excution.
Technically it's perfectly legal to do it either way.
BUT, as Habib.OSU already said, there may be more things to consider, like dependencies between the entities you are going to save. There would several strategies to handle this.
AND, if you call SaveChanges() for each loop iteration this might cause heavy performance issues.
Related
OK I can delete a single item in EF6 like this:
public void DeleteUserGroup(MY_GROUPS ug)
{
using (var context = new MYConn())
{
var entry = context.Entry(ug);
if (entry.State == EntityState.Detached)
{
context.MY_GROUPS.Attach(ug);
}
context.MY_GROUPS.Remove(ug);
context.SaveChanges();
}
}
If this method changed from passing a single instance of MY_GROUPS to a List<MY_GROUPS> how would I handle the delete?
Would there be a more efficient way then just doing a foreach and setting the state one at a time?
UPDATE:
I am already using a similar method as above utilizing the RemoveRange method.
However I am getting an error:
The object cannot be deleted because it was not found in the
ObjectStateManager.
I'm looking for the best way to attach a list of objects to the context so that I can delete them.
To be able to remove records, you need to make sure your ObjectContext is tracking them. Right now you have detached objects, and your context has no knowledge of them so it's impossible to delete them. One way to remove them is to do like you say, Attach all your objects to the context, then delete them. The other way is to fetch the records from the database so you can remove them:
//Find all groups in database with an Id that is in your group collection 'ug'
var groups = context.My_Groups.Where(g => ug.Any(u => u.Id == g.Id));
context.My_Groups.RemoveRange(groups);
context.SaveChanges();
However, note that even while using RemoveRange, a delete command will be send to the database per item you want to remove. The only difference between RemoveRange and Remove is that the first will only call DetectChanges once, which can really improve performance.
Iterate over your collection and set Deleted state for each
groups.ForEach(group => ctx.Entry(group).State = EntityState.Deleted);
ctx.SaveChanges();
You can use RemoveRange:
context.MY_GROUPS.RemoveRange(context.MY_GROUPS.Where(x => x.columnName== "Foo"));
You can also use ForEach like this:
context.MY_GROUPS.Where(x => x.columnName == "Foo").ToList().ForEach(context.DeleteObject);
context.SaveChanges();
You could also use ObjectContext.ExecuteStoreCommand Method as an another approach for this purpose.
I found this it worked for me. I did it in a loop before calling save changes. I wanted it to create just the delete sql command and it did.
http://www.entityframeworktutorial.net/entityframework6/delete-disconnected-entity-in-entity-framework.aspx
// disconnected entity to be deleted
var student = new Student(){ StudentId = 1 };
using (var context = new SchoolDBEntities())
{
context.Entry(student).State = System.Data.Entity.EntityState.Deleted;
context.SaveChanges();
}
EntityFramework 6+ has made this a bit easier with .RemoveRange().
public void DeleteUserGroup(List<MY_GROUPS> ug)
{
using (var context = new MYConn())
{
context.MY_GROUPS.RemoveRange(ug);
context.SaveChanges();
}
}
By using the solution provided by Alexander Deck where brands is an IEnumerable:
context.Brands.RemoveRange(context.Brands.Where(cb => brands.Any(b => b.Id == cb.Id)));
I got the following error:
Unable to create a constant value of type 'SomeCompany.SomeApp.DB.Entities.Brand'. Only primitive types or enumeration types are supported in this context.
And it was solved by converting the Brands DBSet to IEnumerable with the AsEnumerable() extension method:
context.Brands.RemoveRange(context.Brands.AsEnumerable().Where(cb => brands.Any(b => b.Id == cb.Id)));
That did the trick for me.
I have an entity called Hazaa containing two fields: Killed (type DateTime?) and Linky (type Guid). The business logic dictates that whenever we create a new instance of Hazaa, the old one is supposed to be killed by setting the time stamp.
So, doing it for a single element, I apply the method as follows.
public void Create(Hazaa newbie)
{
using (ModelContainer context = new ModelContainer())
{
Hazaa oldie = context.Hazaas
.Single(hazaa => hazaa.Linky == hazaa.Linky && !hazaa.Killed.HasValue);
oldie.Killed = DateTime.Now;
context.Hazaas.AddOrUpdate(oldie);
context.Hazaas.Add(newbie);
context.SaveChanges();
}
}
Now, I'd like to use a similar approach for bulk update. The signature of that method would be following.
public void Create(List<Hazaa> newbies)
{
...
}
My problems is that I'm not sure how to perform the selection of pre-existing hazaas given the list of new additions. One way is to apply foreach statement and execute them one by one (but that's a slow method). Another one would be to use Contains method (but that's a problem because the list might long).
Are there any other options? My colleague suggested the following. I feel that it might be refactored so that it doesn't access context so many times. Do I worry without reason, perhaps?
public void Create(List<Hazaa> newbies)
{
...
using (ModelContainer context = new ModelContainer())
{
List<Hazaa> oldies = newbies.Select(hazaa => context.Hazaas
.Single(oldie => oldie.Linky == hazaa.Linky && !oldie.Killed.HasValue))
.ToList();
...
}
...
}
Since you use Single I assume that all Linkys represent existing records. This means that you can update them without even fetching them from the database first:
using (ModelContainer context = new ModelContainer())
{
foreach(var hazaa in newbies)
{
context.Attach(hazaa);
hazaa.Killed = DateTime.Now; // Will mark hazaa as modified
context.Add(new Hazaa { Linky = hazaa.Linky };
context.Hazaas.Add(newbie);
}
context.SaveChanges();
}
I have a few methods that could be better optimized and it would be very helpfull if someone could explain the solutions aswell. I am using ASP.NET MVC 4 with Entity-framework.
First I have this method:
//gets all the items by id
var GetAllItems = re.GetAllWorldNewsByID();
// loops through all items
foreach (var newsitemz in GetAllItems)
{
if (newsitemz.Date <= DateTime.Now.AddDays(-2))
{
re.DeleteNews(newsitemz);
re.save();
}
}
How can I change this deletion loop to be a single delete from News where Date <= #twodaysold ? I guess it would give better performance if I did that.
This method is inside my repository file that is used alot in my project.
public void AddNews(News news)
{
var exists = db.News.Any(x => x.Title == news.Title);
if (exists == false)
{
db.News.AddObject(news);
}
else
{
db.News.DeleteObject(news);
}
}
What it does is that it checks if the news item title already exists in the database, if it does it deletes the item else it adds it. I know thats its badly made. To get it better optimized maby I should do a upset / merge. Any kind of help is appreciated.
Unfortunately EF doesn't support set based operations natively, although it is something they would like to add in at some stage, (feel free to add your two cents around this here http://entityframework.codeplex.com/discussions/376901)
There is however an extension which does add support for set based deletes, but im not too sure about the performance of this method, it would be worth your while benchmarking before and after trying this. https://github.com/loresoft/EntityFramework.Extended
The other key thing to note is that you could drastically improve performance by performing SaveChanges only once, this means that EF will push it all to the DB at once and only have to wait for one roundtrip to the database server. eg
foreach (var newsitemz in GetAllItems)
{
if (newsitemz.Date <= DateTime.Now.AddDays(-2))
{
re.DeleteNews(newsitemz);
}
re.save(); //assuming this is basically context.SaveChanges()
}
You can use the RemoveAll() method to delete all or selected items:
DeleteNews(x => x.Date <= DateTime.Now.AddDays(-2));
To do this you have to change your model a bit and have the DeleteNews() method accepts a predicate as a parameter. Then use this code in the method.
It should be like:
public void DeleteNews(Predicate<News> item)
{
//myList is list of News
myList.RemoveAll(item);
}
I am trying to implement an AuditLog using EF 4.1, by overriding the SaveChanges() method as discussed in the following places:
http://jmdority.wordpress.com/2011/07/20/using-entity-framework-4-1-dbcontext-change-tracking-for-audit-logging/
Entity Framework 4.1 DbContext Override SaveChanges to Audit Property Change
I am having problems with the "modified" entries though. Whenever I attempt to get at the OriginalValue of the property in question, it always has the same value as it does in the CurrentValue field.
I first use this code, and it successfully identifies the Entries that are modified:
public int SaveChanges(string userID)
{
// Have tried both with and without the following line, and received same results:
// ChangeTracker.DetectChanges();
foreach (
var ent in this.ChangeTracker
.Entries()
.Where( p => p.State == System.Data.EntityState.Added ||
p.State == System.Data.EntityState.Deleted ||
p.State == System.Data.EntityState.Modified ))
{
// For each change record, get the audit record entries and add them
foreach (AuditLog log in GetAuditRecordsForChange(ent, userID))
{
this.AuditLog.Add(log);
}
}
return base.SaveChanges();
}
The problem is in this (abbreviated code):
private List<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntry, string userID)
{
if (dbEntry.State == System.Data.EntityState.Modified)
{
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName),
dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
// It never makes it into this if block, even when
// the property has been updated.
}
// If I updated the property "Name" which was originally "OldName" to the value "NewName" and then break here and inspect the values by calling:
// ?dbEntry.OriginalValues.GetValue<object>("Name").ToString()
// the result will be "NewName" and not "OldName" as expected
}
}
}
The strange thing is that the call to dbEntry.Property(propertyName).IsModified(); will
return true in this case. It is just that the OriginalValue doesn't have the expected value inside. Would anyone be willing to help point me in the right direction? I cannot seem to get this to work correctly.
When EF retrieves an entity from the database it takes a snapshot of the original values for all properties of that entity. Later, as changes are made to the values of these properties the original values will remain the same while the current values change.
However, for this to happen EF needs to be tracking the entity throughout the process. In a web or other n-tier application, typically the values are sent to the client and the context used to query the entity is disposed. This means that the entity is now no longer being tracked by EF. This is fine and good practice.
Once the application posts back the entity is reconstructed using values from the client and then re-attached to the context and set into a Modified state. However, by default the only values that come back from the client are the current values. The original values are lost. Usually this doesn't matter unless you are doing optimistic concurrency or want to be very careful about only updating values that have really changed. In these cases the original values should also be sent to the client (usually as hidden fields in a web app) and then re-applied as the original values as a part of the attach process. This was not happening in the example above and this is why the original values were not showing as expected.
If you change
dbEntry.OriginalValues.GetValue<object>(propertyName);
to
dbEntry.GetDatabaseValues().GetValue<object>(propertyName);
then that works.
I got this error when i override SaveChanges in context As follows
public override int SaveChanges()
{
var changeInfo = ChangeTracker.Entries()
.Select(t => new {
Original = t.OriginalValues.PropertyNames.ToDictionary(pn => pn, pn => t.OriginalValues[pn]),
Current = t.CurrentValues.PropertyNames.ToDictionary(pn => pn, pn => t.CurrentValues[pn]),
}).ToList();
return base.SaveChanges();
}
and when I cleared it fixed!
ChangeTracker.Entries().ToList() in SaveChanges is wrong...
The problem is not in the code you show here. The issue is that how you track entities.
If you just create an entity object and calls Update on it EF framework just overwrite the existing value in db ( provided you supplied correct ID ). That is done for efficiency. So if you do:
var company = new Company{Id = mySuppliedId, Name = newname};
Context.Companies.Update(company);
Context.SaveChanges();
EF will go directly to DB in one shot and update all properties on the entity, without bringing anything back first. So it has no way of knowing the original values.
If you change the code in your logic to something like:
var company = Context.Companies.Where(c=>c.Id == mySuppliedId).FirstOrDefault();
company.Name = newName;
Context.SaveChanges()
Then your ChangeTracker code you showed above all of sudden starts working, as EF brought the data from DB first. It is however less efficient as you make and extra query.
I need the old/original value in post method. Finally this worked for me.
//Get Orignal value before save changes
Item entityBeforeChange = db.Items.Single(x => x.Id == item.Id);
db.Entry(entityBeforeChange).State = EntityState.Detached; // breaks up the connection to the Context
var locId = entityBeforeChange.LocationId;//Orignal value
//Saving the current Value
if (ModelState.IsValid)
{
db.Entry(item).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
You can get data that you haven't committed yet.
var Current = _dbContext.Entry(entity).GetDatabaseValues().ToObject();
I have a method like this:
public FbUser FindUserByGraphOrInsert(dynamic json, bool commit = false)
{
string graphId = json.id;
EntityDataModelContext context = DataContext.GetDataContext();
FbUser user = context.FbUsers.FirstOrDefault(u => u.FbGraphId == graphId);
if (user == null)
{
user = new FbUser();
user.FbGraphId = json.id;
user.FbUsername = StringExtensions.UnicodeDecode(json.name);
context.FbUsers.AddObject(user);
if (commit)
context.SaveChanges();
}
return user;
}
I call this method repeatedly in a loop (say upwards of 80 times), with commit = false
Thing is, I expected this method to let me know if the user is already in the context, but this doesn't seem to be the case.
The result is that when I finally save changes, I get a list of 80 users, where 27 are distinct.
I expect this method to return those 27, how could I change it to achieve this?
Do I really need to save changes every single time?
You cant 'simply' do that, the problem is that each query will always hit the database by default since EF has no way of knowing you either query the same data or that there have been no underlying changes in the database since you opened the connection.
You can however check the ChangeTracker/ObjectStateManager for existing changed objects and query that one as well prior to deciding to add a new object.
Sample:
var addedObjects = context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added);
var equalObjects = addedObjects.OfType<MyEntity>().Where(x => x.Name == newObject.Name);
Based on Polity's answer, I implemented the following extension method, which worked.
public static IEnumerable<T> IncludeUnsaved<T>(this ObjectSet<T> set) where T : class
{
var addedObjects = set.Context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added);
var equalObjects = addedObjects.Select(e => e.Entity).OfType<T>();
return equalObjects.Concat(set);
}