NHibernate/Transaction how commit only one object and not the entire session - c#

My problem is when I want to update only one object in the database, every object in my list are updated in the database. I load the list with the same session and I can't make an other session to make my update because I got an error : illegal attempt to associate a collection with two open sessions nhibernate.
There is my code that I use to make the update.
private ISession session = NHibernateConnexion.OpenSession();
using (var transaction = session.BeginTransaction())
{
session.Update(item);
transaction.Commit();
}
Item is the object that I want to update.
The code that I use to load the entire list:
public IList<Item> RetrieveAll()
{
var result = from i in session.Query<Item>()
orderby i.EstActif descending
select i;
IList<Item> listeTemp = result.ToList();
return listeTemp;
}
Thank you!

You have 2 options:
Save in new session
What you doing is right, but the only thing is, you need the evict the entity from session using which you fetched the data. Do something like this
sessionWhichFetchedTheData.evict(item)
private ISession session = NHibernateConnexion.OpenSession();
using (var transaction = session.BeginTransaction()
{
session.Update(item);
transaction.Commit();
}
Save in current session but clear the session before saving
session.clear()
sessionThatFetchedTheData.SaveOrUpdate(item)

Related

Store object in session variables

I have a dropdown menu that when you select an option value submit the form, and to avoid repetitive database calls I am storing my non-sensitive object in a session.
private List<Employee> stafflist
{
get { return Session["stafflist"] as List<Employee>; }
set { Session["stafflist"] = new Employee(); }
}
private void RemoveStaff()
{
Session.Remove("stafflist");
}
however in my
[HttpPost]
public ActionResult index (...)
{
//why can't I get the list of staff like this?
ViewBag.staff=stafflist.Where(..).toList();
//is the below still needed? i thought i
//have a session variable declare above,
//and to avoid 30x repetitive db calls?
//also note when i include the below the code runs fine,
//however, if i take it out it doesn't. i would like to avoid repetitive db calls
stafflist=db.Employee.toList();
}
First of all, you should not prevent to query the database. Proper caching is hard to get right, and a database is perfectly capable of performing queries and caching data.
If you're absolutely sure you want to circumvent the database, and query clientside (i.e. in the controller) then you need to pull the entire staff list from the database at least once per visitor.
You could do that in the first GET call to this controller, assuming the user will always visit that:
[HttpGet]
public ActionResult Index (...)
{
var cachedStaff = db.Employee.toList();
Session["stafflist"] = cachedStaff;
}
Then in the POST, where you actually want to do the database query (again, consider letting the database do what it's good at), you can query the list from the session:
[HttpPost]
public ActionResult Index (...)
{
var cachedStaff = Session["stafflist"] as List<Employee>();
// TODO: check cachedStaff for null, for when someone posts after
// their session expires or didn't visit the Index page first.
var selectedStaff = cachedStaff.Where(..).ToList();
// the rest of your code
}
Then the property you introduced can be used as syntactic sugar to clean up the code a bit:
private List<Employee> CachedStaff
{
get { return Session["stafflist"] as List<Employee>; }
set { Session["stafflist"] = value; }
}
[HttpGet]
public ActionResult Index (...)
{
CachedStaff = db.Employee.toList();
}
[HttpPost]
public ActionResult Index (...)
{
// TODO: this will throw an ArgumentNullException when
// the staff list is not cached, see above.
var selectedStaff = CachedStaff.Where(..).ToList();
// the rest of your code
}
A session is unique for the current user and the current session. That means that when the user closes the browser, the session information is lost. The session is also lost if the session cookie is removed. Read about state management.
If you want to have a global staff list that is available for all users you need to use something else. Caching is the most common case then.
you probably have it already figured it out, just in case I leave here what it worked for me.
First you create a new session variable based on an object created (in this case the object usr will be empty):
User usr = new User();
Session["CurrentUSR"]=usr;
where you want to use the new object, you will have to cast the session variable and point it to a new object created in that particular page:
User usr= new User(); //at this point the usr object is empty, now you are going to replace this new empty object with the session variable created before
usr=Session["CurrentUSR"] as User();
In case you have a list, the best course of action would be to create a List<> of that particular object.

Duplicate GUID being created - Fluent NHibernate

I have an application where I am using Fluent NHibernate to talk to a SQLite database and saving objects. When I run the code below, all of the new items enter into the loop with an empty Guid (which is expected), but then once the SaveOrUpdate function runs, all of the new items all recieve the same Guid. I added session.flush() to see if I could flush the session and force a unique Guid...but no dice.
Any help would be appreciated!
My Mapping File
Id(x => x.Id).GeneratedBy.GuidComb().Unique();
The Code
public void SaveItems()
{
using (ISession session = SessionProvider.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
foreach (Item item in this.Items)
{
session.SaveOrUpdate(item);
session.Flush();
}
transaction.Commit();
}
}
}
Originally this was a comment, but since it turned out to be correct it should be an answer instead:
Verify that the members of the Items collection are truly distinct objects, and not just multiple references to the same instance.

Entity Framework Pass Object from One Context to Another

I am new to Entity Framework so please bear with me.
I have a program that I want to select multiple records from a table and store it in a queue:
private Queue<RecordsToProcess> getRecordsToProcess()
{
Queue<RecordsToProcess> results = new Queue<RecordsToProcess>();
using (MyEntity context = new MyEntity())
{
var query = from v in context.RecordsToProcess
where v.Processed == false
select v;
foreach (RecordsToProcess record in query)
{
results.Enqueue(record);
}
}
}
Then I spin up multiple worker threads. Each worker thread takes one of the items in queue, processes it, and then saves it to the database.
private void processWorkerThread(object stateInfo)
{
while (workQueue.Count > 0)
{
RecordToProcess record = new RecordToProcess;
lock(workQueue)
{
if (workQueue.Count > 0)
RecordToProcess = workQueue.Dequeue();
else
break;
}
//Do the record processing here
//How do I save that record here???
}
}
My understanding is that to save changes back to the database you just call context.SaveChanges() but I can't do that in this situation can I?
Any help is appreciated.
Thanks!
Since you are disposing your MyEntity context in the first method (by wrapping it in a using statement), the entities that are enqueued will be in a "detached" state. That means, among other things, that changes done to the entity will not be tracked and you will not be able to lazy load navigation properties.
It is perfectly fine to dequeue these entities, "attaching" them to a different context, update them, and then call SaveChanges to persist the changes.
You can read about Attaching and Detaching Objects and Add/Attach and Entity States
It might be safer if you save off the primary key in the queue instead and retrieve the entities again. This way you are more likely avoid any data concurrency issues.

How to put an retrieve action in NHibernate in an transaction?

It appears that is a best practice to put all the database calls into a transaction. So, I wanted to put a select action in a transaction, but I can't find how to do this.
I have tried this code, but I get an error:
using (var session = GetSession().SessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
// var session = GetSession();
var result = session.Query<I>().Where(condition);
transaction.Commit();
return result;
}
Error:
Session is closed!
Object name: 'ISession'.
It's not a matter of Transaction itself, although I only use transactions for save/update calls, not selects, but that might be a matter of preference (or I simply don't know something important).
The thing is, you're not 'materializing' the collection before closing the session.
This should work:
var result = session.Query<I>.Where(condition).List();
return result;
The Where does not do anything by itself. Which means you're just deferring execution of the filter until you do something with it - e.g. iterate over it. If you're out of Session scope by then (and it seems you are), you'll get the exception, since you can't call the database when the session is closed.
Although you probably won't be able to access lazily loaded child items without eagerly Fetching them first - you can't call database through proxy when you're not inside an open session. :)
Disclaimer
By the way, same thing would happen in EF with LINQ:
IEnumerable<I> myObjects;
using(var context = new MyDbContext())
{
myObjects = context.Set<I>.Where(x => x.Name == "Test");
}
foreach(obj in myObjects)
{
var name = obj.Name; //BOOM! Context is disposed.
}

LINQ to SQL: To Attach or Not To Attach

So I'm have a really hard time figuring out when I should be attaching to an object and when I shouldn't be attaching to an object. First thing's first, here is a small diagram of my (very simplified) object model.
In my DAL I create a new DataContext every time I do a data-related operation. Say, for instance, I want to save a new user. In my business layer I create a new user.
var user = new User();
user.FirstName = "Bob";
user.LastName = "Smith";
user.Username = "bob.smith";
user.Password = StringUtilities.EncodePassword("MyPassword123");
user.Organization = someOrganization; // Assume that someOrganization was loaded and it's data context has been garbage collected.
Now I want to go save this user.
var userRepository = new RepositoryFactory.GetRepository<UserRepository>();
userRepository.Save(user);
Neato! Here is my save logic:
public void Save(User user)
{
if (!DataContext.Users.Contains(user))
{
user.Id = Guid.NewGuid();
user.CreatedDate = DateTime.Now;
user.Disabled = false;
//DataContext.Organizations.Attach(user.Organization);
DataContext.Users.InsertOnSubmit(user);
}
else
{
DataContext.Users.Attach(user);
}
DataContext.SubmitChanges();
// Finished here as well.
user.Detach();
}
So, here we are. You'll notice that I comment out the bit where the DataContext attachs to the organization. If I attach to the organization I get the following exception:
NotSupportedException: 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.
Hmm, that doesn't work. Let me try it without attaching (i.e. comment out that line about attaching to the organization).
DuplicateKeyException: Cannot add an entity with a key that is already
in use.
WHAAAAT? I can only assume this is trying to insert a new organization which is obviously false.
So, what's the deal guys? What should I do? What is the proper approach? It seems like L2S makes this quite a bit harder than it should be...
EDIT: I just noticed that if I try to look at the pending change set (dataContext.GetChangeSet()) I get the same NotSupportedException I described earlier!! What the hell, L2S?!
It may not work exactly like this under the hood, but here's the way I conceptualize it: When you summon an object from a DataContext, one of the things Linq does is track the changes to this object over time so it knows what to save back if you submit changes. If you lose this original data context, the Linq object summoned from it doesn't have the history of what has changed in it from the time it was summoned from the DB.
For example:
DbDataContext db = new DbDataContext();
User u = db.Users.Single( u => u.Id == HARD_CODED_GUID );
u.FirstName = "Foo";
db.SubmitChanges();
In this case since the User object was summoned from the data context, it was tracking all the changes to "u" and knows how to submit those changes to the DB.
In your example, you had a User object that had been persisted somewhere (or passed from elsewhere and do not have it's original DataContext it was summoned from). In this case, you must attach it to the new data context.
User u; // User object passed in from somewhere else
DbDataContext db = new DbDataContext();
u.FirstName = "Foo";
DbDataContext.Users.Attach( u );
db.SubmitChanges();
Since the relationship between user and organization is just a GUID (OrganizationId) in your data model, you only have to attach the user object.
I'm not sure about your scaffolding code, but maybe something like this:
private const Guid DEFAULT_ORG = new Guid("3cbb9255-1083-4fc4-8449-27975cb478a5");
public void Save(User user)
{
if (!DataContext.Users.Contains(user))
{
user.Id = Guid.NewGuid();
user.CreatedDate = DateTime.Now;
user.Disabled = false;
user.OrganizationId = DEFAULT_ORG; // make the foreign key connection just
// via a GUID, not by assigning an
// Organization object
DataContext.Users.InsertOnSubmit(user);
}
else
{
DataContext.Users.Attach(user);
}
DataContext.SubmitChanges();
}
So "attach" is used when you take an object that exists from the database, detach it (say by marshalling it over a webservice somewhere else) and want to put it back into the database. Instead of calling .Attach(), call .InsertOnSubmit() instead. You're almost there conceptually, you're just using the wrong method to do what you want.
I used an big table with 400+ columns. No way am I going to map and test all that!
Get the original object from database, and attach it with the amended object. Just make sure the object coming back in is fully populated other wise it will override it the DB with blanks!
Or you can copy the original GET into memory and work on a proper copy (not just reference) of the MOdel, then pass the original and the changed one in, instead of re getting like I do in the example. This is just an example of how it works.
public void Save(User user)
{
if (!DataContext.Users.Contains(user))
{
user.Id = Guid.NewGuid();
user.CreatedDate = DateTime.Now;
user.Disabled = false;
user.OrganizationId = DEFAULT_ORG; // make the foreign key connection just
// via a GUID, not by assigning an
// Organization object
DataContext.Users.InsertOnSubmit(user);
}
else
{
var UserDB = DataContext.Users.FirstOrDefault(db => db.id == user.id); //Costs an extra call but its worth it if oyu have 400 columns!
DataContext.Users.Attach(user, userDB); //Just maps all the changes on the flu
}
DataContext.SubmitChanges();
}

Categories