I am using NHibernate to persist my entities in the database. MY entities have some relationships between them resulting into some mapped collections. I my case I use the Iesi.ISet interface to map these collections. I was wondering if it's possible for nhibernate to check if the properties containing those collections to be automatically set when I execute save if they are null.
This is how it should work. I have a property with a collection called "MyCollection" that is null before I save it to the database. I want NHibernate to set a collection to that property so that it's not null anymore on save. Is this possible?
This is what your constructors are for.
If you very much want to hide such initialization behind NHibernate you might be able to inject the code for this using an NHibernate interceptor or event listener.
Related
Background
In my application we were running into issues when trying to add a new entity with existing children to the database after mapping it from a DTO using AutoMapper. The entity and its children were not attached to the context, and so the children would be treated as new and EF would attempt to insert duplicates. My solution to this was to automatically attach an entity to the context whenever an object was mapped to a BaseEntity type (BaseEntity is the base class for all of our Model objects, it has an Id property and nothing else). Here is the code:
public TDestination Map<TDestination>(object source) where TDestination : class
{
var result = _mapper.Map<TDestination>(source);
if (typeof(TDestination).IsSubclassOf(typeof(BaseEntity)) && result != null)
_context.Attach(result); //_context is a DbContext
return result;
}
This worked fine in my initial test cases, but now I've run into an issue where the entity I'm attaching has a child that is already attached to the context. This throws "The instance of entity type 'MyChildEntity' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.".
How can I attach an entity to the context when a child is already attached? I'm trying to keep this method extremely generic so that it can be used by any object that we are trying to map from a DTO to a BaseEntity.
What I've Tried
I've tried grabbing the associated EntityEntry and recursively detach all of its children using the following method before attempting to call Attach():
private void DetachChildren(EntityEntry entity)
{
foreach (var member in entity.Members.Where(x => x.CurrentValue != null))
{
if (IsBaseEntityType(member.CurrentValue.GetType()))
{
var childEntity = _context.Entry(member.CurrentValue);
childEntity.State = EntityState.Detached;
DetachChildren(childEntity);
}
}
}
I've confirmed that this loop does reach the problem child and sets its state to detached, but I still end up getting the same error when calling Attach().
Welcome to the hell that is working with detached entities.
Automapper can be leveraged to update existing entities rather than forming an entity and attaching it. Given an object like an Order:
public void UpdateOrder(OrderDTO orderDTO)
{
var order = _context.Orders.Single(x => x.OrderId = orderDTO.OrderId);
_mapper.Map(orderDTO, order);
_context.SaveChanges();
}
The benefits of this approach is that it handles whether the order happens to be tracked or not, asserts the order exists for something like an Update where it is assumed it does, and when SaveChanges runs, only the fields that actually changed will be updated. If only 1 field changed, the update statement updates that single field. Attaching a new object and setting EntityState to Modified will update all fields. This could introduce unexpected attack vectors to change data you don't expect since a DTO needs to pass enough info to construct a whole entity to avoid unintentionally #null-ing data. The mapping from DTO to entity should ensure that only editable fields are copied across.
In the case where the OrderDTO will contain one or more child collections to update, you will likely need to use a mapping that excludes those collections, then use AfterMap in the mapping configuration to inspect the child collection for new vs. existing vs. removed entities and handle those accordingly. (Add vs. mapper.Map vs. Remove)
Generally the updates can be structured to perform atomic operations that make the entity interactions as straight forward as possible. For instance UpdateOrderDetails(orderDTO) would update information about the order itself, where there would be separate methods such as AddOrderLine(newOrderLineDTO) vs. UpdateOrderLine(orderLineDTO) vs. RemoveOrderLine(orderLineId) etc. rather than having all order line operations and other related changes done through a single UpdateOrder method accepting a whole modified object graph.
The alternative when dealing with graphs and the possibility of tracked entities is that you need to check each and every related entity against the DbSet's .Local or other means to check to see if the entity is tracked. If it is, then you have to replace the references and copy any applicable changes to the already tracked entity. Telling a DbContext to ignore an existing entity isn't always a simple matter as there can be references to that entity in other tracked entities. Generally you'll want to detect a tracked entity reference then update your references to use that tracked reference and update it as needed. It is lots of mucking around with references, and definitely does not work well with Generic methods
Generic operations are tempting from a DNRY standpoint, but dealing with situations where some entities might be tracked vs. not, and then handling type mismatches etc. (source = object = can be anything..) adds a lot of complexity in place of simpler methods to handle operations.
So after I set up my object to be set in MyDbContext db like so:
Article newBlogPost = new Article()
{
ArticleComments = new List<Comment>()
};
db.BlogPosts.Add(newBlogPost);
db.SaveChanges();
I access this same object later in a different view, like so:
model.articleList = db.BlogPosts.ToList();
When I go into debugging mode, I check the value of model.articleList and it is simply null, instead of a newly instantiated List<Article>. It is as if when I add this List<Article> object to the database, it disposes of it as soon as I call db.SaveChanges(); Note that I have other simpler attributes in this Article object like strings, ints etc. that save correctly.
Does DbContext not allow containers to be stored inside? Or am I doing something wrong?
Working in C# we're so used to dealing with objects and their inner state that it may be a mind-bending truth that ORMs don't save collections.
Fundamentally this is because relational databases don't store collections (contrary to object data databases). In an RDBMS, a parent record is a stand-alone entity. A child record 'has' the reference to its parent. Conversely, in an object space the parent contains, 'has', its children and the children can be totally oblivious of their parent. This is the 'chasm' an ORM crosses for us.
Now consider a few things about EF:
When you query a DbSet (like db.BlogPosts), EF populates new objects from database data. It doesn't reuse objects that it has cached. Cached objects, if any, are in the DbSet.Local collection.
When you get objects from the database, EF doesn't create collections for their collection properties, unless you also load these collections, for instance by Include or lazy loading.
So unloaded collections are null, unless you initialize these collections yourself, e.g. in the object's constructor.
So db.BlogPosts.ToList() creates brand new objects. Any information about non-null collections they had when they were saved is lost, because there is no way to store this information.
Is that possible to serialize/deserialize a string property of an Entity by using its getters and setters on access, on save?
There are two main goals I would like to do for string properties.
1-Keep a json serialized string for special, complex or custom column types.
2-Encrypt on saving, decyrpt on access confidential information, i.e. Email address
Is that possible?
If so, do we have a limited usage of linq queries on this entity?
If so, would fetching all records and trigger their decryption/deserialization would work?
You could use the SavingChanges event to modify the entity right before saving it.
Then use the ObjectMaterialized event to handle when the properties are loaded into the entity.
Obviously, these events could be a little bit of overkill but I think you can have a lot of control and you may not want to attach event handlers every time you use the context.
I am using EF4, and I using the System.Data.Entity dll in my class. However, I can't see the load method of my navigation property of my entity.
How can I access to this method?
What I am doing is create e 4.0 project of .NET, create my edmx from a database, right click in the model edmx and I generate a DBContext.
However, I can access to the local property of my context, that I think that is a feature of EF4.
Thanks.
Daimroc.
For the DbContext approach, the IsLoaded property and Load method have been moved:
context.Entry(yourEntity)
.Collection(entity => entity.NavigationProperty)
.IsLoaded;
context.Entry(yourEntity)
.Collection(entity => entity.NavigationProperty)
.Load();
Entry method: http://msdn.microsoft.com/en-us/library/gg696578(v=vs.103).aspx
Collection method: http://msdn.microsoft.com/en-us/library/gg671324(v=vs.103).aspx
IsLoaded property: http://msdn.microsoft.com/en-us/library/gg696146(v=vs.103).aspx
Load method: http://msdn.microsoft.com/en-us/library/gg696220(v=vs.103).aspx
When you write the query (all this also works with lambda syntax as well btw)
From a in context.EntitySet.Include("Navigation Property NAme")
select a
and all will be there ;) .. or more to the point the navigation properties will be populated.
can be wierd with lots of joins though
you can also
From a in context.EntitySet.Include("Navigation1.Navigation2.Navigation2")
.Include("SomeOtherNavigation")
select a
where navigation1 and SomeOtherNavigation are navigation properties on the entity set
Failing that you can turn on lazy loading in the context. But its evil imo.
Also it doesnt really scale well when you go for an SOA approach as you need to include prior to knowing about the lazy load in your repository.
Ie you run the query in your repository, send stuff over the service and then want the navigation properties in the front end ... by which time its too late to lazy load ... EG you are using a wcf service that doesn't use data services.
Important to note that you ahve to use Include on the EntitySet, it is not on an IQueryable<>.
A real example:
var csg = from ucsg in c.UserCarShareGroups.Include("UserCarShareGroups.User.UserVehicles")
where ucsg.User.UserName.ToLower() == userName.ToLower()
&& ucsg.CarShareGroup.Carpark.Name.ToLower().Equals(carparkName.ToLower())
&& ucsg.DeletedBy == null
select ucsg.CarShareGroup;
(the database has case sensitive collation for some reason)
An alternate approach (and maybe more relevant is here)
Entity Framework - Trouble with .Load()
However I like doing it the way I said because it is explicit and I know exactly what is being pulled out. Especially when dealing with large datasets.
An answer to a question that combines my concerns with Load() is here:
Entity Framework 4 load and include combine
Should I use NHibernate Collection Cascading to perform collection save-updates, or should I use repositories to persist entity collection entities ?
It does not usually make sense to enable cascade on a <many-to-one> or <many-to-many> association. Cascade is often useful for <one-to-one> and <one-to-many> associations.
If the child object's lifespan is bounded by the lifespan of the parent object, make it a life cycle object by specifying cascade="all-delete-orphan".
Otherwise, you might not need cascade at all. But if you think that you will often be working with the parent and children together in the same transaction, and you want to save yourself some typing, consider using cascade="persist,merge,save-update".
Better look at code generator tools like My Generation,CodeSmith for NHibernate template.If you want to use My Generation then take a look at http://www.mygenerationsoftware.com/TemplateLibrary/User/?guid=06437130-d390-4145-8e86-ed9682ff444b template.Really good.