How to revert a specific property to its saved value (EF4)? - asp.net-mvc-3

Well, I'm using MVC3 and EF4 and everything is great... Except when i have to update an object but not all of its properties.
Here's how its implemented so far:
I have a class, it's properties, and a DbSet for it on my DbContext. My page is sending back a JSon with all values of that object (including the ones I want to update and the ones i want to be left unchanged). On the controller, i'm receiving the object and just changing it's State to EntityState.Modified and calling the SaveChanges() of the DbContext.
As I said, everything works great, but i do want that some of the values received by that JSon to be discarded, leaving their values untouched as they are on the database.
How can I achieve that?
Thanks in advance for any help...

Not sure of your code but when you only want to update certain properties on an update call to EF you should do it like so....
db.Person.Attach(person);
db.Entry(person).Property(x => x.Name).IsModified = true;
db.SaveChanges();
so you will modify whichever properties you want and leave alone which ever ones you don't.

You may also try this to..
using (DBContext newCtx = new DBContext())
{
newCtx.Employee.Attach(emp);
newCtx.ObjectStateManager.ChangeObjectState(emp, System.Data.EntityState.Modified);
newCtx.SaveChanges();
}
Reference : Update Entity in Entity Framework

Related

How do I do cross-entity server-side validation

In my application, I have cross-entity validation logic that requires me to look at the entire change set and I'm doing this using the BeforeSaveEntities override.
I can construct the right logic by examining the saveMap parameter, but what am I supposed to do if I find something invalid?
If I throw an exception, like I would for single entity validation in the BeforeSaveEntity override, the whole save is aborted and the error is reported to the client. But some of the entities might be valid so I would want to save those and only abort the invalid parts.
Because BeforeSaveEntities returns a saveMap, I think I should be able to remove the invalid entities from the change set and continue to save the valid entities, but then how do I report the invalid parts to the client?
Is it possible to do a partial save of only the valid entities and at the same time, report a sensible error to the client to describe the parts of the save that failed?
Jay told you the way it is.
I wouldn't hold my breath waiting for Breeze to change because I think yours is a rare scenario and it isn't one we would want to encourage anyway.
But I'm weird and I can't stop thinking what I'd do if were you and I absolutely HAD to do it. I might try something like this.
Warning: this is pseudo-code and I'm making this up. I do not recommend or warrant this
Create a custom MyCustomEFContextProvider that derives from EFContextProvider.
Give it an ErrorEntities property to hold the error object
Override (shadow) the SaveChanges method with another that delegates to the base
public new CustomSaveResult SaveChanges(JObject saveBundle,
TransactionSettings transactionSettings = null) {
var result = base.SaveChanges(saveBundle, transactionSettings);
// learn about CustomSaveResult below
return new CustomSaveResult(this.ErrorEntities, result);
}
Catch an invalid entity inside BeforeSaveEntities
Pass it with error message to your custom ErrorEntities property
You get to that property via the EntityInfo instance as in
((MyCustomEFContextProvider) info.ContextProvider).ErrorEntities.Add(new ErrorEntity(info, message));
Remove the invalid entity from the SaveMap so it won't be included in the actual save
Let the save continue
The second line of your override SaveChanges method creates a new instance of your CustomSaveResult from the standard one and returns that to the caller.
public class CustomSaveResult : SaveResult {
public List ErrorEntities;
public CustomSaveResult(List errorEntities, SaveResult result){
// copy over everything
this.Entities = result.Entities;
this.KeyMappings = result.KeyMappings;
this.Errors = this.Errors;
// and now your error stuff
this.ErrorEntities = errorEntities;
}
}
Let's assume the caller is your Web API controller's SaveChanges method. Well you don't have to change a thing but you might make it clear by explicitly returning your custom SaveResult:
readonly MyCustomEFContextProvider _contextProvider = new MyCustomEFContextProvider();
...
[HttpPost]
public CustomSaveResult SaveChanges(JObject saveBundle) {
return _contextProvider.SaveChanges(saveBundle);
}
JSON.Net will happily serialize the usual material + your custom ErrorEntities property (be sure to make it serializable!) and send it to the Breeze client.
On the Breeze client you write your own variation on the stock Breeze Web API data service adapter. Yours does almost exactly the same thing as the Breeze version. But, when processing the save payload from the server, it also extracts this extra "error entities" material in the response and does whatever you want to do with it.
I don't know what that will be but now you have it.
See how easy that was? LOL.
Breeze does not currently support a save mechanism that both saves and returns an error at the same time. While possible this seems a bit baroque.
As you pointed out, you can
1) Throw an exception inside of the BeforeSaveEntities and fail the save. You can even specify which specific entity or entities caused the failure and why. In this case the entire save is aborted.
or
2) Remove 'bad' items from the saveMap within the BeforeSaveEntities and save only a subset of what was passed in. In this case you are performing a partial save.
But we don't support a hybrid of these two. Please add this to the Breeze User Voice if you feel strongly and we can see if other members of the community feel that this would be useful.

Refresh LINQ db-connection object

My collegue helped me with starting programming in c# although I had no experience but I like it. All went well until I came across some problems we both can't fix. He uses SQL himself but started me up with LINQ.
To do a LINQ-query I use this object : _oDBConnection (in clsApplication.cs)
So when opening the programm this object is built. But it creates some problems:
When saving a new object (putting data into table), I cannot load those values with a query. I need to restart the programm.
When running 2 instances of the programm, one is not getting the latest values when changed in the other (but it is showing the new ones but not the changed ones!)
According to these problems I can only conclude that when I call clsApplication._oDBConnection.tblTAble a second time it is not relinking again to the db but is giving me the old db-states back.
This is the code he built:
public static DBReservationDataContext _oDBConnection;
private static frmMain _fMain;
public clsApplication()
{
Thread.CurrentThread.Name = "main";
clsErrorLog.ErrorLocation = "C:\\Software\\ErrorLog";
clsErrorLog.setPassword("*****");
clsErrorLog.LockApplication += new clsErrorLog.dLockApplication(lockApplication);
_oDBConnection = new DBReservationDataContext();
_fMain = new frmMain();
_fMain.Show();
}
What can I do to fix this problem?
Example:
although present in the database, it crashes on this query because the entity with id == iID is not found. But the iID is correct and it does exist in the database. The query will work after closing and restarting the programm. Then the clsApplication is called again.
public clsReservationDetail(int iID)
:this()
{
_oReservationDetail = (from oReservationDetailQuery in clsApplication._oDBConnection.tblReservationDetails
where oReservationDetailQuery.ID == iID
select oReservationDetailQuery).First();
}
thx in advance
Your data context will have a Refresh method which will clear any cached results, and should allow your query to complete with no problems
The static keyword makes it so that you have one reference per AppDomain. That is the wrong way to use DataContext instances.
Each instance of DataContext tracks the objects it has seen. This is for consistency. If you get a Customer instance with CustomerID = 4 from one query, you should get the same Customer instance from another query that returns the CustomerID = 4 record.
If you want to see the changes in the database, you must
1) Tell the datacontext to stop tracking changes. This must be done before the first query and makes the datacontext instance unable to SubmitChanges (since it can't track them anymore).
OR
2) Tell the datacontext to Refresh each instance you suspect has changed. If you do that, you should specify how resolve the conflict between your local changes and the remote changes - the simplest way of resolving this conflict is to have no local changes.
OR
3) (The right way) Make a new DataContext instance and load the record with that!
Also note: Since DataContext implements IDisposable, you are required to call Dispose when you are done with each instance even when exceptions occur. The using block is a good way to get that to happen.

Spring JSON merge

I'm updating a record from a form over AJAX. I have a JSON object that maps to my entity, and my controller method is:
#RequestMapping(value = "/vendors", method = RequestMethod.POST)
public ResponseEntity<String> saveVendorsJson(#RequestParam String vendor) {
Vendor v = Vendor.fromJsonToVendor(vendor);
if (v.merge() == null) {
v.persist();
}
return new ResponseEntity<String>(HttpStatus.OK);
}
I expected from the documentation that v.merge() will return null if it didn't find an existing record by the object's 'id' field to merge with, and in that case I want to persist it as a new Vendor object.
What's happening is, despite my JSON having an 'id' field value that matches an existing record, I'm ALWAYS inserting a new record with my updated goods from the browser.
I'm aware I'm having the POST method pull double-duty here, which isn't strictly RESTful. In theory, this is simpler for me (though of course that's turning out not to be the case).
I believe this is a Hibernate thing. Hibernate will not "merge" if it doesn't know it already exists. I think what I have done in the past is to do a lookup, then a persist. I think if you try to just merge something coming in off the wire you would get a Primary Key Collision, or something similar. I believe Hibernate has some sort of "dirty" flag internal to indicate if you are creating or editing an existing object.
There also used to be a way in raw-Hibernate to do a soft-lookup, basically tell Hibernate "look, I have this object, I don't want to do a SELECT blah-blah-blah, I just want to update some fields". It would load the object into Cache and allow you to do the update without doing the SELECT first. There is also an updateOrSave() in Spring, but that actually does the SELECT first.

How to delete and re-insert entities in linq?

I am having troubles inserting entities in linq after deleting them (without submitting the changes).
List<AlergiesPerPersonBE> AlergiesPerPerson = AlergiesPerPersonToInsert;
RepositoryFactory.GetAlergiesPerPersonRepository().DeleteWhere(x => x.PersonId == id);
RepositoryFactory.GetAlergiesPerPersonRepository().Insert(AlergiesPerPerson);
DataContextFactory.SubmitChanges();
Both (delete and insert) donĀ“t submit any changes. They just InsertAllOnSubmit and DeleteAllOnSumbit.
The code works fine the first time. All the details are inserted properly. The second time that I run the same code, all rows of the db are deleted. The third time, all works fine. It works, then it doesn't, and so on.
Try
...DeleteWhere(x => x.PersonId == id);
DataContextFactory.SubmitChanges();
...Insert(AlergiesPerPerson);
DataContextFactory.SubmitChanges();
There's no reason that you have to submit changes only once per unit of work. Note that this will still work with any transactions that you have.
I think you will have to recreate the details again.
The context is currently tracking this item as being 'deleted' even if save changes hasn't yet been called.
There is dirty work around. Since you want to get rid of DataContext observer you will need to:
serialize object or graph to memory
stream.
Then deserialize it back as new instance
change ID to 0
Attach new object to DataContext and submit
changes.
Delete previous
You can move this to CloneHelper static class so you can use it for any type

Updating LINQ to SQL object causing System.NotSupportedException

I get System.NotSupportedException: An attempt has been made to Attach or Add an entity that is not new perhaps having been loaded from another DataContext when I want to update an object's with child entities.
The scenario is like this:
I have a SubscriberProvider that allows me to create subscribers.
var provider = new SubscriberProvider(); // Creates a new repository with own datacontext
var newSubscriber = new Subscriber
{
EmailAddress = emailAddress,
};
newSubscriber.Interests.Add(new Interest{
Id=1,
Name="cars"
});
provider.Subscribe(newSubscriber);
On a normal subscribe page, this works fine.
Now I have a linq2sql Member class(retrievable by a MemberRepository) and I want to extend it to have a helper subscribe method like so:
var repository = new MembershipRepository(); // Holds its own datacontext
var member = repository.Get("member1");
member.Subscribe(); // transfer member's info and interests to subscriber's table
The exception occurs when SubscriberProvider tries to add interests of the member.
Commenting out
newSubscriber.Interests.Add(new Interest{
Id=1,
Name="cars"
});
will make member.Subscribe() work.
member.Subscribe() is simply:
public void Subscribe(bool emailIsVerified, bool receiveEmails, bool sendDoubleOptIn)
{
var provider = new MailingListProvider();
provider.Subscribe(EmailAddress, emailIsVerified, receiveEmails, CountryId, sendDoubleOptIn, ConvertInterests(MemberInterests.ToList()));
}
So what's causing the child entities(Interests) to lose their datacontext when I do member.Subscribe() and how do I go about fixing this?
It seems there's some code missing here, but I'll take a stab anyway because I think I have an idea what's going on.
If you have a different DataContext created for your MembershipRepository and your SubscriberRepository you're going to have issues related to entities "having been loaded from another DataContext." (as the Exception you posted points out). You can't just take an object out of one DataContext and save it into another.
It seems that you might have an architectural issue here. Should these 2 repositories actually be separate? If so, should they have completely different DataContexts? I would probably recommend using Dependency Injection to inject your DataContexts into your Repositories. Then you can decide how to cache your DataContexts.
That line of code you commented out is being flagged by the DataContext as a new record, even though it's likely that the record already exists, due to the error message.
Change the line to:
newSubscriber.Interests.Add(DataContext.Interests.Where(a => a.Id == 1).Single());
Now, the DataContext will know that record is one that already exists, and won't try to add it as an Insert to the ChangeSet.
Found the solution to this myself. Turns out it was the ConvertInterests() method causing it. The converted interest object had an invalid declaration which compiled ok.
Thinking the code was simple enough, I didn't create a test for it. I should have known better!

Resources