I have the following code which uses the repository pattern, backed by a fluent nhibernate automapping to a MySQL DB.
The following snippet adds a user, and a person to the model, and then persists to the database.
It works, however I have to remember to put user.Person.User = user; otherwise when the object is persisted, Person has the FK userId set to null.
Is there a way to automatically cause the Person.User to be populated, or do I need to create a method to do this?
Is there a way to cause all objects associated with that user to be persisted (e.g. only call repository.Save(obj) on one object and cause the others to also be persisted.)
using (MySQLRepositoryBase repository = new MySQLRepositoryBase())
{
try
{
repository.BeginTransaction();
User user = new User() { Password = "12345", Username = "jbloggs", Person = new Person() };
user.Person.Firstname = "Joe";
user.Person.Lastname = "Bloggs";
user.Person.User = user;
repository.Save(user);
repository.Save(user.Person);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
repository.RollbackTransaction();
}
}
NHibernate can't set this automatically, you have to do it yourself.
Most common is a AddSomething() method in the parent entity which
adds the child to the collection and sets the parent reference in
the child. But if you don't need the parent reference in your code
you could just remove it (and map only the collection).
Use the Cascade options that are available on collections and references.
Related
I'm new to NHibernate and was assigned to a task where I have to change a value of an entity property and then compare if this new value (cached) is different from the actual value stored on the DB. However, every attempt to retrieve this value from the DB resulted in the cached value. As I said, I'm new to NHibernate, maybe this is something easy to do and obviously could be done with plain ADO.NET, but the client demands that we use NHibernate for every access to the DB. In order to make things clearer, those were my "successful" attempts (ie, no errors):
1
DetachedCriteria criteria = DetachedCriteria.For<User>()
.SetProjection(Projections.Distinct(Projections.Property(UserField.JobLoad)))
.Add(Expression.Eq(UserField.Id, userid));
return GetByDetachedCriteria(criteria)[0].Id; //this is the value I want
2
var JobLoadId = DetachedCriteria.For<User>()
.SetProjection(Projections.Distinct(Projections.Property(UserField.JobLoad)))
.Add(Expression.Eq(UserField.Id, userid));
ICriteria criteria = JobLoadId.GetExecutableCriteria(NHibernateSession);
var ids = criteria.List();
return ((JobLoad)ids[0]).Id;
Hope I made myself clear, sometimes is hard to explain a problem when even you don't quite understand the underlying framework.
Edit: Of course, this is a method body.
Edit 2: I found out that it doesn't work properly for the method call is inside a transaction context. If I remove the transaction, it works fine, but I need it to be in this context.
I do that opening a new stateless session for geting the actual object in the database:
User databaseuser;
using (IStatelessSession session = SessionFactory.OpenStatelessSession())
{
databaseuser = db.get<User>("id");
}
//do your checks
Within a session, NHibernate will return the same object from its Level-1 Cache (aka Identity Map). If you need to see the current value in the database, you can open a new session and load the object in that session.
I would do it like this:
public class MyObject : Entity
{
private readonly string myField;
public string MyProperty
{
get { return myField; }
set
{
if (value != myField)
{
myField = value;
DoWhateverYouNeedToDoWhenItIsChanged();
}
}
}
}
googles nhforge
http://nhibernate.info/doc/howto/various/finding-dirty-properties-in-nhibernate.html
This may be able to help you.
The entities and mappings I'm talking about in this question can be found here :)
Here is the context:
I have a parent view-model which helps to manage some entities, and which has its own session.
From this VM, I open another view-model (with its own session too), do some changements to the entity (add and/or remove children), and when I validate the changements, I commit the session and warns the first view-model to refresh the display:
public void Validate()
{
using (var tx = Session.BeginTransaction())
{
try
{
SelectedTeam.ClearRoster();
foreach (var teamPlayer in TeamPlayers)
SelectedTeam.AddPlayer(teamPlayer);
teamsRepository.SaveOrUpdate(SelectedTeam);
tx.Commit();
}
catch (Exception ex)
{
tx.Rollback();
}
finally
{
if (tx.WasCommitted)
ServiceLocator.Current.GetInstance<Mediator>().NotifyColleagues(MediatorMessages.DisplayEntityInfos, SelectedTeam.Id);
}
}
}
Here is the faulted method of the parent VM:
public void RefreshEntitiesListAndDisplayEntityInfos(int selectedEntityId)
{
TEntity entity = entitiesRepository.Load(selectedEntityId);
Session.Refresh(entity);
//...
}
The exception is thrown at the Refresh line:
NHibernate.UnresolvableObjectException
And the message is:
No row with the given identifier exists[Emidee.CommonEntities.PlayerInTeam#3
I can open and change the entity multiple times, but it seems that the exception is thrown when I delete a children, then add another one, and finally delete another one.
After some readings on the web, it seems that's because when I refresh the entity, and because I changed the HasMany relationship (because I have deleted a player for example), NH tries to reload the deleted row.
I've tried to add a NotFound.Ignore statement on the HasMany in my mappings, I've tried to force a new query to the DB instead of a Load, but I still get this exception.
Does someone know how I could fix that?
Thanks in advance
Mike
This is a known behavior when refreshing objects with modified collections.
To force reload, change your method to do session.Evict with the entity as a parameter. This is the code we use in our base model class:
public T ReGet<T>(T entity) where T : IEntity
{
var id = entity.Id;
Session.Evict(entity);
return Session.Get<T>(id);
}
Well, I've just spotted the problem.
To update the players list of the team, I used to clear the list, and add the new players, before updating the entity.
Now, I update the list by removing and adding only the players who have been moved by the user, and I don't have any problems at all now.
That's weird. I don't know what was wrong before, but as least that works now.
I have an object where I maintain a relationship to another object:
public class Foo {
public Bar Bar { get; set; }
}
In the mapping I reference Bar to that I can maintain the relationship - I don't want the parent object to update properties in the child, just the existence of the relationship:
References(x => x.Bar, "BarId")
.Cascade.None();
In the UI layer I create the relationship using a property which is not the underlying primary key:
item.Bar = new Bar { Code = "123" };
In the repository layer I hydrate the object if it doesn't have the primary key populated:
if(item.Bar.Id == null)
{
item.Bar = barRepository.RetrieveByCode(item.Bar.Code);
}
When I the RetrieveByCode line runs (which is a Criteria.UniqueResult under the covers) I get a TransientObjectException telling me that "the object references an unsaved transient instance - save the transient instance before flushing" for the Bar type.
When I run the same code path without creating the temporary Bar object it works. It appears that the Bar created as a temporary oject is tracked by NHibernate, yet I want it to forget that it ever existed as it is only a placeholder.
Any thoughts on how to achieve this?
UPDATE: Doing some more testing on this it seems to be the change tracking in Foo that is causing trouble. If I call Session.Evict(item) after retrieving it, but before making any changes and then re-attach the object using Session.Update(item) after I am done it seems to work, however it updates the child objects which is not what I want - I only want to manage the relationship.
UPDATE 2: I changed the FlushMode from Auto to Commit. It seems to have disabled the queueing of any interim changes to the object. Having researched NH behavior a bit further it seems that Update works more like a "re-attach" call rather than an explicit "update now" call.
UPDATE 3: It appears changing FlushMode caused other issues with transactions that required several operational steps. I reverted back to try another approach:
if(item.Bar.Id == null)
{
var barCode = item.Bar.Code;
item.Bar = null;
item.Bar = barRepository.RetrieveByCode(barCode);
}
Why do you want it to work that way? Why not simply set item.Bar using the retrieved Bar object:
item.Bar = barRepository.RetrieveByCode("123");
You might be able to make your current pattern work using Load:
if(item.Bar.Id == null)
{
var bar = barRepository.RetrieveByCode(item.Bar.Code);
item.Bar = session.Load<Bar>(bar.Id);
}
I'm importing data that may or may not exist already in my database. I'd like NHibernate to associate any entities with the existing db one if it exists (probably just setting the primary key/id), or create a new one if it doesn't. I'm using S#arp architecture for my framework (MVC 2, NHibernate, Fluent).
I've added the [HasUniqueDomainSignature] attribute to the class, and a [DomainSignature] attribute to the properties I want to use for comparison. The only way I can think to do it (which is not an acceptable solution and may not even work) is the following (psuedo C#):
foreach (Book importedBook in importedBooks){
foreach (Author author in importedBook.Authors){
if (!author.IsValid()){ // NHibernate Validator will check DomainSignatures
author = _authorRepository.GetByExample(author); // This would be to get the db object with the same signature,
//but I don't think I could even update this as I iterate through it.
}
}
}
As you can see, this is both messy, and non-sensical. Add to that the fact that I've got a half dozen associations on the Book (subject, format, etc), and it doesn't make any sense. There's got to be an easy way to do this that I'm missing. I'm not a novice with NHibernate, but I'm definitely not an expert.
I might not be understanding the problem, but how can the data "may or may not exist in the database"? For example, if a Book has 2 Authors, how is the relationship stored at the database level if the Author doesn't exist?
It seems as if you're trying to use NHibernate to import your data (or create an entity if it doesn't exist) which doesn't seem correct.
Most database implementations support a conditional UPDATE-or-INSERT syntax. Oracle, for example, has a MERGE command. In combination with a Hibernate <sql-insert> block in your mapping you should be able to work something out. I don't know Fluent but I assume it supports this too.
Just realize I never gave an answer or approved another's answer. I ended up just writing a new SaveOrUpdate which takes a parameter to check for existing before persisting. I also added an attribute to my domain models to overwrite when saving/updating (although in retrospect it's only on updating that it'd be overwriting).
Here's the code if it can help anyone else in this dilemma:
public TEntity SaveOrUpdate<TEntity>(TEntity entity, bool checkForExistingEntity)
{
IRepository<TEntity> repository = new Repository<TEntity>();
if (checkForExistingEntity) {
if (entity is Entity) {
IEnumerable<PropertyInfo> props = (entity as Entity).GetSignatureProperties();
Dictionary<string, object> parameters =
props.ToDictionary(propertyInfo => propertyInfo.Name, propertyInfo => propertyInfo.GetValue(entity, null));
TEntity duplicateEntity = repository.FindOne(parameters);
if (duplicateEntity != null) {
// Update any properties with the OverwriteOnSaveUpdate attribute
foreach (var property in RepositoryHelper.GetUpdatableProperties(typeof(TEntity)))
{
object initialValue = property.GetValue(entity, null);
property.SetValue(duplicateEntity, initialValue, null);
}
// Fill in any blank properties on db version
foreach (var property in typeof(TEntity).GetProperties())
{
if (property.GetValue(duplicateEntity, null) == null) {
object initialValue = property.GetValue(entity, null);
property.SetValue(duplicateEntity, initialValue, null);
}
}
return duplicateEntity;
}
}
}
return SaveOrUpdate(entity);
}
In the dbml designer I've set Update Check to Never on all properties. But i still get an exception when doing Attach: "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." This approach seems to have worked for others on here, but there must be something I've missed.
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
test.Name = "test2";
using(TheDataContext dc = new TheDataContext())
{
dc.Members.Attach(test, true);
dc.SubmitChanges();
}
The error message says exactly what is going wrong: You are trying to attach an object that has been loaded from another DataContext, in your case from another instance of the DataContext. Dont dispose your DataContext (at the end of the using statement it gets disposed) before you change values and submit the changes. This should work (all in one using statement). I just saw you want to attach the object again to the members collection, but it is already in there. No need to do that, this should work just as well:
using(TheDataContext dc = new TheDataContext())
{
var test = dc.Members.FirstOrDefault(m => m.fltId == 1);
test.Name = "test2";
dc.SubmitChanges();
}
Just change the value and submit the changes.
Latest Update:
(Removed all previous 3 updates)
My previous solution (removed it again from this post), found here is dangerous. I just read this on a MSDN article:
"Only call the Attach methods on new
or deserialized entities. The only way
for an entity to be detached from its
original data context is for it to be
serialized. If you try to attach an
undetached entity to a new data
context, and that entity still has
deferred loaders from its previous
data context, LINQ to SQL will thrown
an exception. An entity with deferred
loaders from two different data
contexts could cause unwanted results
when you perform insert, update, and
delete operations on that entity. For
more information about deferred
loaders, see Deferred versus Immediate
Loading (LINQ to SQL)."
Use this instead:
// Get the object the first time by some id
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
// Somewhere else in the program
test.Name = "test2";
// Again somewhere else
using(TheDataContext dc = new TheDataContext())
{
// Get the db row with the id of the 'test' object
Member modifiedMember = new Member()
{
Id = test.Id,
Name = test.Name,
Field2 = test.Field2,
Field3 = test.Field3,
Field4 = test.Field4
};
dc.Members.Attach(modifiedMember, true);
dc.SubmitChanges();
}
After having copied the object, all references are detached, and all event handlers (deferred loading from db) are not connected to the new object. Just the value fields are copied to the new object, that can now be savely attached to the members table. Additionally you do not have to query the db for a second time with this solution.
It is possible to attach entities from another datacontext.
The only thing that needs to be added to code in the first post is this:
dc.DeferredLoadingEnabled = false
But this is a drawback since deferred loading is very useful. I read somewhere on this page that another solution would be to set the Update Check on all properties to Never. This text says the same: http://complexitykills.blogspot.com/2008/03/disconnected-linq-to-sql-tips-part-1.html
But I can't get it to work even after setting the Update Check to Never.
This is a function in my Repository class which I use to update entities
protected void Attach(TEntity entity)
{
try
{
_dataContext.GetTable<TEntity>().Attach(entity);
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
catch (DuplicateKeyException ex) //Data context knows about this entity so just update values
{
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
}
Where TEntity is your DB Class and depending on you setup you might just want to do
_dataContext.Attach(entity);