Discard Changes with Entity Framework 4 POCO - entity-framework-4

I am using Entity Framework 4.0 POCO with WPF.
I have a form displaying to the user a an object with a many to many relationship like the following:
public class BPartner : BaseEntity
{
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = bpartnerValidatorLight.ValidateName(value);
OnPropertyChanged("Name",true);
}
}
}
public virtual ObservableCollection<BPAddress> BPAddresses { get; set; }
}
public class BPAddress : BaseEntity
{
public string Line1
{
get
{
return line1;
}
set
{
if (line1 != value)
{
line1 = bpAddressValidatorLight.ValidateLine1(value);
OnPropertyChanged("Line1",true);
}
}
}
public virtual City City
{
get { return city; }
set
{
if (city != value)
{
city = value;
OnPropertyChanged("City");
}
}
}
}
The user may add and delete addresses in the BPAddresses collection and change the "Name" of the BPartner. When the user is done modifying the BPArtner object he/she may click "Save" or "Cancel". The problem is that when the user clicks "Cancel" I need to tell the Entity Framework to revert all the changes.
Any approach for handling this is very welcome including reloading.
Here is what I have tried:
1. Discard the objectContext, create a new object context and just query the database to reload everything again. The problem here is that Entity Framework caches things and old objects linger attached to the old context and I get exceptions if the user clicks cancel then edits again and clicks save.
2.
repoBPartner.Refresh(bp);
IQueryable<BPAddress> query = from e in addressRepo.AsQueryable()
where e.BPartnerId == bp.Id
select e;
ObjectQuery<BPAddress> objectQuery = (ObjectQuery<BPAddress>)query;
objectQuery.Include("City");
objectQuery.Include("Country");
ObjectResult<BPAddress> result = (ObjectResult<BPAddress>)objectQuery.Execute(MergeOption.OverwriteChanges);
bp.BPAddresses = new System.Collections.ObjectModel.ObservableCollection<BPAddress>(result.ToList<BPAddress>());
The problem here is that "City" property does not get refreshed.
3. Tried: objectContext.LoadProperty(bp, "BPAddresses", MergeOption.OverwriteChanges);
All the above worked partially. What is the best practice for achieving this?
Any help will be appreciated.
Thanks,
K. Mitev

EF doesn't offer discarding changes. EF also doesn't have second level cache so once you dispose a context with changes and create a new context to load data from database you will get unchanged data.
The problem here is that Entity
Framework caches things and old
objects linger attached to the old
context and I get exceptions if the
user clicks cancel then edits again
and clicks save.
When user clicks cancel you must throw away all objects used in editation together with their context. These objects and context are dead. Once user clicks edit again you must load everything again from database.
Refreshing navigation properties doesn't work very well. For example once you add something to navigation collection it will never be removed by refreshing from database.

Related

How to share same data one action method to other action method in ASP.NET MVC

When i call my admin controller- Index Action method will get all the user details
when i want select particular user again i dont want to hit the DB.
both action method same controller and i'm using model popup for display details.
My Question
I dont want to use entity framework.
- when admin form load i will get all the user details this is Index Action Method
-based on user id i need to display particular user so again i dont want hit to the DB already i'm having all the user details. that details how to get another action method?
i can remember asp.net i used session to share the data globally. like that asp.net mvc is possible? please help me.
Thanks
It looks you're looking for a cache mechanism. For simple scenarios, I use a simple static variable, but I keep it in a separated class. Let's suppose you have a User class like this:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
You could create a class like this:
public static class UserCacheService
{
private static IEnumerable<User> _users;
private static readonly object lockObj = new object();
public static IEnumerable<User> GetUsers()
{
lock (lockObj)
{
if (_users == null)
{
using (var db = new MyNiceDbContext())
{
_users = db.Users.ToList();
}
}
return _users;
}
}
public static void InvalidateCache()
{
lock (lockObj)
{
_users = null;
}
}
}
Then you can get your shared users in any action, of any controller like this:
public class AdminController : Controller
{
public ActionResult Index()
{
// the first time, it'll need to get users from DB (e.g with Entity Framework)
var users = UserCacheService.GetUsers();
return View();
}
}
The first time, the _users in your UserCacheService will be null, and as expected, it'll need to load users from database. However, the next time it won't, no matter if you are using another controller:
public class AnotherController : Controller
{
public ActionResult Index(string userId)
{
// now, it won't load from DB anymore, because _users is already populated...
var users = UserCacheService.GetUsers();
var currentUser = users.Where(u => u.Id == userId).FirstOrDefault();
if (currentUser != null)
{
// do something with the user...
}
return View();
}
}
There are times when unfortunately your _users will become null again, for example when you restart your ApplicationPool in IIS, but UserCacheService is already prepared for fetching database once if that's the case.
Be careful about three things:
Whenever you keep data in memory (like _users), you are consuming
your server's memory, which might be limited. Don't start trying to
keep everything in memory, only data you know you'll need everytime.
Whenever you update something in your users, like a name, an address or something else, since the _users will not get from database everytime, you need to call the UserCacheService.InvalidateCache() method, in order to force the next call to load again from database, thus making sure you have _users up to date.
This only works for simplistic scenarios. If you have your application distributed in two or more servers, this won't work, as each server has it's own memory and they can't share it out of the box. That's when you would look forward for something like Redis. Though, I don't think it's your case here.

Entity Framework MVC and "static" objects from database

I have a table in my database, where I put states of USA (just ID and state name).
I want to get these values in my controler - shall I do standard operations: add context in my EF, later get objects? Or there is another way to get it?
I don't want to make any changes in this table, so db.XYZ.Add, db.XYZ.SaveChanges() etc. methods is not important for me.
What is the best practise to get objects like "states" or for example base of "zip codes"?
Later, second question: what means "virtual" in code like this:
[Table("Users")]
public class User
{
(..)
public virtual string UserType { get; set; }
Regards!
Entity framework allows getting objects from the database without adding them to the change tracker:
using (var db = new MyContext())
{
var states = db.States.AsNoTracking();
...
}
However, when you use a State object in a reference, e.g. like:
var address = new Address() { State = state1 };
db.Addresses.Add(address);
db.SaveChanges();
where state1 is a State object, state1 will be attached to the context after all and have EntityState.Added!
So you should still be careful how to use these "untracked" objects. In this example you'd better set a primitive StateId property:
var address = new Address() { StateId = state1.Id };
(does not attach state1).
What you'd really want in this case is preventing EF from ever changing the EntityState of a State object. There is no way I can think of to enforce this. Would be an interesting feature.
what means "virtual" in code like this
The virtual modifier is commonly used to enable lazy loading of navigation properties. Address could have
public virtual State State { get; set; }
Now when you grab an address from the database and afterwards access its State property, the state is loaded from the database at that moment. This is because under the hood EF creates an Address object that is a derivative of your Address class and that has overridden the State property with code that enables lazy loading.
However, in the code you show virtual has no bearing on lazy loading because it is a string property. So it can't be a navigation property and it does not allow lazy loading.
In code first I use an enums to save items with an int id and description.
To do it this way I add the following:
In the user add:
...
public int StateId { get; set; }
public State State
{
get { return (State)StateId ; }
}
...
Create an enum:
public enum State
{
State1,
State2
}
The enum Id is saved back as a StateId to the User each time I save a user.
On this link you can find more about "virtual" in your code
To get all states you can easy do this:
var getStates = from i in db.States select i;
and of course you can add some conditions if you want.

EntityFramework and ont-to-many CRUD operations

I'm really trying hard to put everything on my project to work with the EF, but it's really getting difficult and sometimes it makes me wonder if it's really the smart move (to rely on EF against coding all the ins and outs of the database).
Well, my problem is still related to 1-N creating/editing/deleting functionality (something that should be simple, right?).
Ok, I'm pasting here some simple equivalent of my code.
For the Entities, I got the main class as:
[Table("OLIM_LOTE")]
public class Lote
{
[Key]
[Column("LOTE_ID_LOTE")]
public int? IDLote { get; set; }
[Column("LOTE_TX_OBS")]
public string Obs {get;set;}
[Column("LOTE_TX_DOCUMENTO_EXTRA")]
public string DocumentoExtra { get; set; }
[NotMapped]
public List<DocumentoLote> Documentos { get; set; }
public void LoadLists()
{
OlimpiqueDBContext myDbContext = new OlimpiqueDBContext();
var docs = (from doc in myDbContext.DocumentosLote
where doc.IDLote == this.IDLote
select doc);
this.Documentos = docs.ToList<DocumentoLote>();
}
}
[Notice that i used the nullable int? for Key - otherwise it throws me validation exception asking for a value on creation]
For the child class, i got this:
[Table("OLIM_DOCUMENTO_LOTE")]
public class DocumentoLote
{
[Key]
[Column("DOLO_ID_DOCUMENTO_LOTE")]
public int? IDDocumentoLote { get; set; }
[Column("DOCU_ID_DOCUMENTO")]
[ForeignKey("Documento")]
public int IDDocumento { get; set; }
public virtual Documento Documento { get; set; }
[Column("LOTE_ID_LOTE")]
[ForeignKey("Lote")]
public int IDLote { get; set; }
public virtual Lote Lote { get; set; }
}
[Notice that the child class has a reference back to the owner class, which are the "IDLote" and "Lote" attributes, and the owner class has a list of child class instances - so I got i bi-directional refernce - I assume that this is somehow related to the problems]
I got a Controller and View generated automatically by VS2012 with Read/Write functionality related to the class Lote.
What I did in the View can be described as: I used a Jquery DataTable to manage the child class data (the user can add "N" instances on the DataTable). I substituted the Post Button with a call to a JS method that simply gets all the data from the Form and from the DataTable and wrap it in a JSon object and send it to the controller via Ajax.
The controller method that receives it can be simplified as below:
[HttpPost]
public JsonResult Edit(Lote lote)
{
try
{
if (ModelState.IsValid) //<< HAVING PROBLEMS HERE... DETAILS BELOW
{
if (lote.IDLote.HasValue)
{
//Separete updates/inserts from deletes
List<int?> dbDocs = db.DocumentosLote
.Where(dt => dt.IDLote == lote.IDLote)
.Select(dt => dt.IDDocumentoLote)
.ToList();
List<int?> postedDocs = lote.Documentos
.Select(pt => pt.IDDocumentoLote)
.ToList();
List<int?> deletedDocs = dbDocs
.Except(postedDocs).ToList();
//Perform deletes
foreach (var delDocId in deletedDocs)
{
if (delDocId.HasValue)
{
DocumentoLote delDoc = db.DocumentosLote
.Where(dt => dt.IDLote == lote.IDLote && dt.IDDocumentoLote == delDocId)
.Single();
db.Entry(delDoc).State = EntityState.Deleted;
}
}
//Perform insert and updates
foreach (var doc in lote.Documentos)
{
if (doc.IDDocumentoLote.HasValue)
{
db.Entry(doc).State = EntityState.Modified;
}
else
{
db.Entry(doc).State = EntityState.Added;
doc.IDLote = (int)lote.IDLote;
}
}
}
else
{
db.Lotes.Add(lote);
}
db.SaveChanges();
// If Sucess== 1 then Save/Update Successfull else there it has Exception
return Json(new { Success = 1, ex = "" });
}
else
{
return Json(new { Success = 0, ex = "Falha ao tentar salvar os dados" });
}
}
catch (Exception ex)
{
// If Sucess== 0 then Unable to perform Save/Update Operation and send Exception to View as JSON
return Json(new { Success = 0, ex = ex.Message.ToString() });
}
}
Problems: Well I really passed through a lot to got to this point and now, I got only 2 problems. The first being that the creation is throwing a Validation Exception sayin that it needs an IDLote (for the child classes - but anyway, how would i have it if the owner class itself still doesn't have an Id at that point in creation?)
Second problem: Deletion dont work at all! Doesn't matter how i code it, it throws the exception "objects cannot be defined because they are attached to different ObjectContext objects". I really feel that this has something to do with the bidirectional reference between owner-children classes, but still, don't have a clue on exactly whats happening and how to solve it
I'm starting to feel really lost here. Any ideas on this would be very appreciated. Thanks
As there are a lot of views on this old question and now I do have some answer, I'm posting them for reference:
Q - Regarding the int? type for the key attributes:
A - It doesn't have to be a nullable int at all. The entity can be declared with a simple int attribute as key and when posting the JSon object from the View, back to some controller method, this attribute (the key) can be filled with the value "0". EF will generate the correct value as soon as it persists the object.
Q - Regarding the navigational attributes and how to implement the relation between the two classes when neither of them have already got a value (non-zero) on theis keys:
A - The JSon object to be sent back can implement the exact navigational relationaship between them. Wehn the controller binds the data posted to the model it should be receiving, it will "understand" their relationship and as soon as the values for the keys are generated, they will correctly reference one another.
Q - Regarding the error described on the delete method attempts:
A - When objects should interact with other objects, and those interactions should be persisted or "understood" by EF in any way, they must have been obtained, generated or attached to a same DBContext. EF rely on the DB context to create a tree of this interactions, thus, rendering impossible to build this tree when objets are not present on the same DB Context.

ASP.NET MVC: Keeping last page state

Here's the situation: i have a SearchPage where an user can make a complex search. Nothing really unusual. After the results are displayed, the user can select one of them and move to another Page (Like a Master/Detail).
I have a breacrumb which holds the places where the user has been and it can have more than 4 levels (Like Main -> 2Page -> 3Page -> 4Page -> NPage). What i want is to maintain the state of each control on my complex search page, if the user uses the breacrumb to navigate backwards, since i don't want them to manually set all those search filters again.
So far, i've been using javascript:history.back(), but since i can have multiple levels on my breadcrumb, this hasn't been very useful. I was thinking about using OutputCache to do it, but i don't know how i would proceed.
UPDATE
I've just talked to a co-worker and he told me that some of our combobox (dropdownlist) are dynamically generated. So if the user select one item on the first combobox, the second will be filled with data related to the first selection.
OutputCache would cache the results for every user. Why don't you try to store the information in a cookie with page url and filter information. Each time an action is executed, read the cookie and populate the model (custom model for search) with those values found (if they match the page url, action in this situation). Pass the model to the view and let it repopulate the search criteria text boxes and check boxes.
UPDATE:
When a user fills in the search filter text boxes, you are passing that information back to a controller somehow. Probably as some kind of a strongly typed object.
Let's say your users get to enter the following information:
- Criteria
- StartDate
- EndDate
There is a model called SearchCriteria defined as:
public class SearchCriteria
{
public string Criteria { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
Your action could look something like this:
[HttpGet]
public ViewResult Search()
{
SearchCriteria criteria = new SearchCriteria();
if (Request.Cookies["SearchCriteria"] != null)
{
HttpCookie cookie = Request.Cookies["SearchCriteria"];
criteria.Criteria = cookie.Values["Criteria"];
criteria.StartDate = cookie.Values["StartDate"] ?? null;
criteria.EndDate = cookie.Values["EndDate"] ?? null;
}
return View(criteria);
}
[HttpPost]
public ActionResult Search(SearchCriteria criteria)
{
// At this point save the data into cookie
HttpCookie cookie;
if (Request.Cookies["SearchCriteria"] != null)
{
cookie = Request.Cookies["SearchCriteria"];
cookie.Values.Clear();
}
else
{
cookie = new HttpCookie("SearchCriteria");
}
cookie.Values.Add("Criteria", criteria.Criteria);
if (criteria.StartDate.HasValue)
{
cookie.Values.Add("StartDate", criteria.StartDate.Value.ToString("yyyy-mm-dd"));
}
if (criteria.EndDate.HasValue)
{
cookie.Values.Add("EndDate", criteria.EndDate.Value.ToString("yyyy-mm-dd"));
}
// Do something with the criteria that user posted
return View();
}
This is some kind of a solution. Please understand that I did not test this and I wrote it from top of my head. It is meant to give you an idea just how you might solve this problem. You should probably also add Action to SearchCriteria so that you can check whether this is an appropriate action where you would read the cookie. Also, reading and writing a cookie should be moved into a separate method so that you can read it from other actions.
Hope this helps,
Huske

ASP.net Dynamic Data - OnPropertyChanging Not Updating other columns

Here is my code:
partial void OnisApprovedChanging(bool value)
{
this.dateApproved = DateTime.Now;
}
'dateApproved' is updated in the business logic, but this change is not being applied to the database table. I've seen some examples where DateUpdated columns are updated whenever any edit to a table is made, but I'm only interested in updating the time-stamp when this field is updated and I'm not sure of the best way to access the DataContext from this scope.
Do I need to instantiate the Data-context and update it manually?
EDIT Did some more research, and found that some blogs suggested adding business logic on update like this:
public partial class DataContext : System.Data.Linq.DataContext
{
partial void Updateaccount(account instance)
{
//business logic here
}
}
However, I cannot determine any way to find out if particular fields have changed. Any ideas?
Found this is the way to get the original entity and do comparisons.
partial void Updateaccount(account instance)
{
account acctPriorToUpdate = accounts.GetOriginalEntityState(instance);
if (instance.isApproved != acctPriorToUpdate.isApproved)
{
//Do Stuff
}
this.ExecuteDynamicUpdate(instance);
}

Resources