NHibernate: If two calls are made to CreateCriteria, which list is Get<T> going to retrieve the object from? - nhibernate

Under one UnitOfWork (session) I may call CreateCriteria twice. My first call is to populate a grid for data editing. Let's say the data has been edited and flushed (saved) to the database and the grid is still open. After the data is edited, I may call CreateCriteria a second time to retrieve a list of objects that are validated and found in error.
Lets say ObjectA was retrieved by both calls to session.CreateCriteria. It was edited in the grid but found in error within the second list.
The first question would be: Considering first level cache, is ObjectA--that was retrieved from the second call to CreateCriteri--represent the one retrieved from the first call? or, better yet, did NHibernate "detect and reuse" ObjectA from the first call assuming the keys did not change?
To my final point in question: I want to edit ObjectA which was found in error, and let's say it was brought up in a ListBox. Therefore, I want to highlight that object, call session.Get()(key) in order to retrieve it from cache, then bring up a change form to change ObjectA's properties. Which object am I changing? The one from the first call to CreateCriteria or the second call? Are they the same?
Thank you in advance.

Second level cache
Take a look at http://ayende.com/Blog/archive/2006/07/24/DeepDivingIntoNHibernateTheSecondLevelCache.aspx and http://www.javalobby.org/java/forums/t48846.html
From the former:
The second level cache does not hold
entities, but collections of values
So, with caching setup properly, NHibernate will be able to recreate your object without having to get the actual values from the database. In other words, the object will be created the same as when it wasn't in cache, except that since the values are cached, NHibernate won't actually query the database since it already knows what's in there.
I'm not quite sure what you mean by "validation" and "found in error". Are you validating before insert? Typically my entities are validated before the insert/update and won't actually be inserted/updated if invalid.
Validation aside, what I think you're asking is that if you:
save something
do a flush
retrieve an item (from a new session) with the same key as the one saved in step 1
will you be retrieving the same reference to the object you saved in step 1(?). And the answer is no since NHibernate does not cache the OBJECT but rather the values so it can create a new entity populated with the cached values (instead of actually performing a DB query).
However, does that really matter? If you overload Equals such that equality of 2 entities are based on their ID, then finding the same (not reference equal, but same) item in a grid (or a hash of any kind) should be a snap.
First level cache
I didn't realize you were talking about 1st level cache. 1st level cache works as an identity map and does cache the instance of the object. Therefore, if you do 2 selects from the db based on the same ID, you will retrieve the same instance of the object.

Related

Remove object from Active Record Relation without deleting it

I'm working in Ruby on Rails 4 with Postgresql, and I've hit a bit of a snag. We have an Active Record model called AttendanceRecord which belongs to an AttendanceDay, AttendanceSwipe, Course, and CourseTimeSlot. Attendance Records were supposed to be unique on these fields, but something went wrong and duplicates snuck in. So, I wrote a method to find all of the Attendance Records which were duplicated and only keep one of them.
In the course of that method, I built an Active Record Relation of objects that shared the same attributes, like so:
records = AttendanceRecord.where(course_id: attributes[0], course_time_slot_id: attributes[1], attendance_swipe_id: attributes[2], attendance_day_id: attributes[3])
Nice relation, right?
Next, I found the object that I wanted to keep and named it to_keep. Then, I tried to remove just that object from the relation, like this:
records.delete(to_keep)
Unfortunately, I discovered that the delete method works a little differently on a Relation than it does on an Array. Instead of simply removing the object from the list, it actually does delete it from the database (without the callbacks).
So: I'm wondering if there is a method that I'm missing that will remove my to_keep object from the Relation without actually touching the object itself. Then, I'll be able to safely call records.destroy_all and happily go about my business. :)
If you want to exclude an object from a relation you can do so by id. For example:
records.where('id <> ?', to_keep.id).destroy_all
or, thanks to #trushkevich, in rails 4 you can do:
records.where.not(id: to_keep.id).destroy_all
This means that destroy_all will be called on the records you've identified already but excluding the to_keep record.

Many-to-many relationships with NSFetchedResultsController

I have a model that looks like this:
and want to display it in a UITableView using an NSFetchedResultsController. I want to list all of the Objects for a given Owner, grouped by Group. For example, if we were to view the groups for some Owner Owner A the table view might look like this:
Group A
Object A
Object B
Object C
Group B
Object A
Object D
Group C
Group D
Object C
Object E
It's also important that the Groups be ordered by their name attribute (not shown in the schema above) and that NSFetchedResultsController delegate methods get called whenever Objects are modified or added/removed from a Group.
Given those requirements, I have set up the NSFetchedResultsController to fetch a bunch of Objects with a predicate like [NSPredicate predicateWithFormat:#"ANY groups.owner = %#", someOwner], which throws an NSInvalidArgumentException exception: "to-many key not allowed here". I have tried a few other predicates, but am stuck.
Any suggestions? Is this not something I should be using an NSFetchedResultsController for? Or is there a better way to model my data?
Thanks!
Edit: I actually got the predicate working with the above code, my mistake was in my sectionNameKeyPath argument. I was trying to pass in groups.name, which was what was producing the error. I can see why that way wouldn't work, but am struggling to find a different way to achieve the desired results. Perhaps a join object?
Edit 2: This works somewhat well with a join object like this:
There are two downsides that I see now. The first is that I have to enforce uniqueness myself using code. That's easy enough, but a bit of a nuisance. The second downside, that I don't yet see a way around, is that the NSFetchedResultsController will not call its delegate for updates to Objects anymore. I can live with that downside for now, but am happy to hear better suggestions.
I think the problem you're going to see is that if you have a fetched results controller searching for Objects then it's going to find each Object only exactly once. So each object will appear exactly once in your table. Whereas what you sort of want to do is invert things and find all the Groups, then displaying all the relevant contained Objects. In terms of the fetched results controller, rather than finding rows and thereby being able to divide into sections you want to find sections and thereby figure out what to supply as rows.
The easiest thing, I think, would be to create a fetched results controller on Group and to add an intermediary object of your own that remaps those to sections and supplies group.objects (with a suitably deterministic sorting applied) as the rows per section.
If you want the rows to be dynamic then I guess the easiest thing is to create a fetched results controller per section based on the feedback of the Group controller.
If that's all getting a bit painful and you want to just write your own collection logic then you'll probably want to catch NSManagedObjectContextDidSaveNotification and rerun your logic whenever that occurs. For memory efficiency reasons you probably want to keep hold only of the objectIDs and to get the appropriate objects only when the table view requests them via the existingObjectWithID:error: method on the context. Core Data has a built-in caching mechanism that responds appropriately to memory warnings so that's all written for you.
You shouldn't need a join object to get at data in Many to Many relationships
See this answer: How to deal with many to many relationships with NSFetchedResultsController?
And example project: https://github.com/dmathewwws/Many-to-Many-CoreData

Optimal way to add / update EF entities if added items may or may not already exist

I need some guidance on adding / updating SQL records using EF. Lets say I am writing an application that stores info about files on a hard disk, into an EF4 database. When you press a button, it will scan all the files in a specified path (maybe the whole drive), and store information in the database like the file size, change date etc. Sometimes the file will already be recorded from a previous run, so its properties should be updated; sometimes a batch of files will be detected for the first time and will need to be added.
I am using EF4, and I am seeking the most efficient way of adding new file information and updating existing records. As I understand it, when I press the search button and files are detected, I will have to check for the presence of a file entity, retrieve its ID field, and use that to add or update related information; but if it does not exist already, I will need to create a tree that represents it and its related objects (eg. its folder path), and add that. I will also have to handle the merging of the folder path object as well.
It occurs to me that if there are many millions of files, as there might be on a server, loading the whole database into the context is not ideal or practical. So for every file, I might conceivably have to make a round trip to the database on disk to detect if the entry exists already, retrieve its ID if it exists, then another trip to update. Is there a more efficient way I can insert/update multiple file object trees in one trip to the DB? If there was an Entity context method like 'Insert If It Doesnt Exist And Update If It Does' for example, then I could wrap up multiple in a transaction?
I imagine this would be a fairly common requirement, how is it best done in EF? Any thoughts would be appreciated.(oh my DB is SQLITE if that makes a difference)
You can check if the record already exists in the DB. If not, create and add the record. You can then set the fields of the record which will be common to insert and update like the sample code below.
var strategy_property_in_db = _dbContext.ParameterValues().Where(r => r.Name == strategy_property.Name).FirstOrDefault();
if (strategy_property_in_db == null)
{
strategy_property_in_db = new ParameterValue() { Name = strategy_property.Name };
_dbContext.AddObject("ParameterValues", strategy_property_in_db);
}
strategy_property_in_db.Value = strategy_property.Value;

WCF data services - Limiting related objects returned based on critera

I have an object graph consisting of a base employee object, and a set of related message objects.
I am able to return the employee objects based on search criteria on the employee properties (eg team) etc. However, if I expand on the messages, I get the full collection of messages back. I would like to be able to either take the top n messages (i.e. restrict to 10 most recent) or ideally use a date range on the message objects to limit how many are brought back.
So far I have not been able to figure out a way of doing this:
I get an error if I attempt to filter on properties on the message (&$filter=employee/message/StartDate gives an error ">No property 'StartDate' exists in type 'System.Data.Objects.DataClasses.EntityCollection`1).
Attempting to use Top on the message related object doesn't work either.
I have also tried using a WebGet extension that takes a string list of employee IDs. That works until the list gets too long, and then fails due to the URL getting too long (it might be possible to setup a paging mechanism on this approach)...
Unfortunately the UI control I am using requires the data to be in a fairly specific hierarchical shape, so I can't easily come at this from starting on the message side and working backwards.
Outside of making multiple calls does anyone know of a method to accomplish this with wcf data services?
Thanks!
M.
Looks like the only real way of doing this is in fact to reverse the direction of the query.
So instead of starting from the Employee, I go from the message side. You can filter back on the employee properties, and restrict on the Messages collection. Its not ideal, as it means iterating the collection on return to re-center it on the employee for what I am attempting to do, but it will work. The async nature of silverlight and rich client at least means while an extra iteration is required, it still appears to be reasonably fast.
Another interesting thing to note: the current version of odata/wcf data services does not support querying on properties of inherited classes, so I had to move the start/end date properties up to the base class in order to be able to restrict my search on them.
http://Site/Service.svc/Messages()?&$filter=Employee/OfficeName eq 'Toronto' and (year(StartDate) eq 2010 and month(StartDate) ge 9 )

How do I get NHibernate to save an entity if I assign it an ID, but generate one otherwise?

According to the REST philosophy, a PUT request should update the resource at a URL if it exists, and create it if it doesn't exist. So in other words, if I use the following URL:
PUT http://server/item/5
If an Item exists with an ID of 5, it will be updated. If an Item doesn't exist with an ID of 5, a new Item will be created with an ID of 5.
However, I'm using NHibernate for persistence, and I mapped my IDs as Identity. This means that no matter what value I assign the ID, NHibernate will replace it with its own when I save a new Item.
How do I get NHibernate to save an Item with the ID that I assign it, without changing the ID mapping to Assigned?
If you use Identity, the DB won't allow you to enter a value.
That said, if your DB has some special syntax to allow inserting with explicit values in Identity fields, you can implement your own generator, which I guarantee will be error prone, hard to debug, and not very useful. But it's possible.
Study everything in https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/nhibernate/src/NHibernate/Id and start creating your Frankenstein :-)