Remove entity in NHibernate only by primary key - c#

I'm trying to implement a repository method for removing entities using only primary key, mainly because from a webapp I usually only are aware of the primary key when invoking a "delete request" from a web page.
Because of the ORM, the option today is to get the entity from the database, and then deleting it, which gives me an extra roundtrip.
I could use HQL delete, but since I want to create a generic delete method for all entities, that won't fly unless I use reflection to find out which field that is the primary key (doable, but doesn't feel correct).
Or is it in the nature of NHibernate to need the entity in order to correctly handle cascades?
I tried this approach, with the assumption that it would not load the entity unless explicitly necessary, however haven't had time to test it yet. Maybe someone can shed some light on how this will be handled?
var entity = session.Load<T>( primaryKey );
session.Delete( entity );
EDIT: Have now tested it and it seems that it still does a full select on the entity before deletion.

Load may return a proxy object but it isn't guaranteed. Your mapping may contain cascade deletes that will force NHibernate to load the object from the database in order to determine how to delete the object and its graph.
I would implement this using Load as you are doing. For some objects NHibernate may not need to do a select first. In cases where it does, that's the [usually] trivial price you pay for using an o/r mapper.

This was already asked and answered before: How to delete an object by using PK in nhibernate?
I even have two blog posts about it:
http://sessionfactory.blogspot.com/2010/08/deleting-by-id.html
http://sessionfactory.blogspot.com/2010/08/delete-by-id-gotchas.html

nHibernate is an O(bject)RM. I agree with you that it probably needs the objects to resolve dependencies.
You can of course use direct ADO.Net calls to delete your objects. That presents its own problems of course since you'll have to take care of any cascading issues yourself. If you do go down this road, don't forget to evict from the nHibernate session whatever objects you delete using this method.
But, if this delete is in a really sensitive part of your system, that might be the way to go.
I'd make 100% sure though that this is the case. Throwing away all the nHibernate gives you because of this would not be wise.
I get the sense you know this, and you're looking for a strictly nHibernate answer, and I don't think it exists, sorry.

Disclaimer: I am not able to test it as of now. But won't following thing help:
Person entity = new Person();
entity.Id = primaryKey;
session.Delete( entity );
Don't load the entity but build your entity having just the primary key. I would have loved to test it but right now my environment is not working.

Related

What is best practice for checking for an existing record with a primary key while using Entity Framework?

What is the best practice to handle the following situation?
It is known that many records (thousands) will be inserted with a fair possibility of a primary key exception. In some cases the exception should trigger some alternative queries and logic. In other cases it doesn't matter much and I merely want to log the event.
Should the insert be attempted and the exception caught?
or
Should a query be made to check for the existing record, then attempt the insert if none exists?
or Both?
I have noticed slightly better performance when merely catching the exception, but there's not a significant difference.
IMO It depends. If the client is responsible for generating a PK, using a UUID or Snowflake etc. where keys are expected to be unique then the first option is fine. Whether you bother with a retry after generating a new ID or simply fail the operation and ask the user to try again (as it should be a 1 in a billion exception, not the norm) is up to you. If the data is relying on sequences or user-entered meaningful keys it should be managed at the DB side using DatabaseGenerated.Identity and meaningless keys with related object graphs created and committed within a single SaveChanges call.
The typical concern around ID generation and EF is usually where developers don't rely on EF/the DB to manage the PK and FKs through navigation properties. They feel they need to know the PK in order to set FKs for related data, either saving the primary entity to get the PK or generating keys client-side. One of the key benefits of using an ORM like EF is giving it the related objects and letting it manage the inserting of PKs and FKs automatically.
There are couple of things over here.
One thing required is that you must have primary key vonstraint on column at the database Level
Now at the Entity Framework level, it is good if you check whether the record exists or not. So basically what happen you query for record using primary key and if it is found, then it return the entity and then you make changes to entity and at last SaveChanges will save that entity
Now if you are not able to find entity then you have to add entity
If you try without query then it is problematic for EF and specially if multiple request try to update same record
Now one more case is that, lets assume that there is possibility that multiple request can insert same record and so primary key constraint will help here and it will not allow duplication if you are generating primary key manually
For update too, there is possibility of data loss if you are not taking care of concurrency

ef core best practice to update complexe objects

We are leading into some issues with ef-core on sql databases in a web-api when trying to update complexe objects on the database provided by a client.
A detailed example: When receiving an object "Blog" with 1-n "Posts" from an client and trying to update this existing object on database, should we:
Make sure the primary keys are set and just use
dbContext.Update(blogFromClient)
Load and track the blog while
including the posts from database, then patch the changes from
client onto this object and use SaveChanges()
When using approach (1) we got issues with:
Existing posts for the existing blog on database are not deleted
when the client does not post them any more, needing to manually
figure them out and delete them
Getting tracking issues ("is already been tracked") if
dependencies of the blog (for example an "User" as "Creator") are
already in ChangeTracker
Cannot unit test our business logic without using a real DbContext
while using a repository pattern (tracking errors do just not exist)
While using a real DbContext with InMemoryDatabase for tests cannot rely on things like foreign-key exceptions or computed
columns
when using approach (2):
we can easily manage updated relations and keep an easy track of
the object
lead into performance penalty because of loading the
object which we do not really need
need to map many manual things
as tools like AutoMapper cannot be used to automaticlly map
objects with n-n relations while keeping a correct track by ef core (getting some primary key errors, as some objects are deleted from lists and are added again with the same primary
key, which is not allowed as the primary key cannot be set on insert)
n-n relations can be easily damaged by this as on database
there could be n-n blog to post, while the post in blog does hold
the same relation to its posts. if only one relation is (blog to
post, but not post to blog - which is the same in sql) is posted and
the other part is deleted from list, ef core will track this entry
as "deleted".
in vanilla SQL we would manage this by
deleting all existing relations for the blog to posts
updating the post itself
creating all new relations
in ef core we cannot write such statements like deleting of bulk relations without loading them before and then keeping detailed track on each relation.
Is there any best practice, how to handle an update of complexe objects with deep relations while getting the "new" data from a client?
The correct approach is #2: "Load and track the blog while including the posts from database, then patch the changes from client onto this object and use SaveChanges()".
As to your concerns:
lead into performance penalty because of loading the object which we do not really need
You are incorrect in assuming you don't need this. You do in fact need this because you absolutely shouldn't be posting every single property on every single entity and related entity, including things that should not be be changed like audit props and such. If you don't post every property, then you will end up nulling stuff out when you save. As such, the only correct path is to always load the full dataset from the database and then modify that via what was posted. Doing it any other way will cause problems and is totally and completely 100% wrong.
need to map many manual things as tools like AutoMapper cannot be used to automaticlly map objects with n-n relations while keeping a correct track by ef core
What you're describing here is a limitation of any automatic mapping. In order to map entity to entity in collections, the tool would have to somehow know what identifies each entity uniquely. That's usually going to be a PK, of course, but AutoMapper doesn't (and shouldn't) make assumptions about that. Instead, the default and naive behavior is to simply replace the collection on the destination with the collection on the source. To EF, though, that looks like you're deleting everything in the collection and then adding new items to the collection, which is the source of your issue.
There's two paths forward. First, you can simply ignore the collection props on the source, and then manually map these. You can still use AutoMapper for the mapping, but you'd simply need to iterate over each item in the collection individually matching it with the appropriate item that should map to it, based on your knowledge of what identifies the entity (i.e. the part AutoMapper doesn't know).
Second, there's actually an additional library for AutoMapper to make this easier: AutoMapper.Collection. The entire point of this library is to provide the ability to tell AutoMapper how to identify your entities, so that it can then map collections correctly. If you utilize this library and add the additional necessary configuration, then you can map your entities as normal without worrying about collections getting messed up.

Why Is FluentNHibernate Missing CascadeType.REPLICATE?

I must keep the same domain running at two places at the same time. One end must be able to run "offline", while still must receive and send data to the other end from time to time when "online". Basically we got a central server which aggregates data comming from the clients and serves some updated data (like the latest price of a product, new products, etc). I'm using NHibernate to take care of persistance.
I'm trying to use NHibernate's Replicate method
session.Replicate(detached, ReplicationMode.LatestVersion);
to get the object comming from the other end and incorporate/merge/attach to the "local" database.
It fails to execute because it can't cascade the references and collections. Reviewing the cascade options from FluentNHibernate (and even directly looking at NHibernate source code) I could not find the REPLICATE cascade type. From Hibernate's documentation:
CascadeType.REPLICATE
My question is: does anybody knows why FluentNHibernate is missing such option? Is there a different/better way to set this kind of cascade behaviour?
I tried the Cascade.Merge() option together with session.Merge(detached), but although the cascade works just fine, it give me some headaches, mainly because of the id generation and optmistic lock (versioning).
EDIT: NHibernate's source code DOES have a ReplicateCascadeStyle class that maps to the string "replicate". The Cascade / CascadeConverter classes (from Mapping.ByCode namespace) DOES NOT have Replicate as an option. So NHibernate itself supports cascade on Replicate, but only through manual mapping I guess.
OK, as I'm using Fluent NHibernate to map about 100+ classes, switch to xml mapping is not an option to me.
So I forked Fluent NHibernate on GitHub, added the missing Cascade.Replicate option and sent a pull request.
Hope it helps someone.

How to guard against NHibernate incomplete mappings

I am new to NHibernate/FluentNHibernate. I use FNH for my coding now as I find it is easier to use. However, I am working with some existing code base which is written in NHibernate. Today I found a bug in the code where the database wasn't getting updated as expected. After about 30 mins I found out that I hadn't updated the mapping xml even though I added a new class variable - so that row in the table wasn't getting updated. My question is, is there a way to identify such incomplete mappings with NHibernate easily so that I don't have to manually check the mapping always when something goes wrong? i.e. A warning message if I am updating an object which has non default data for any fields which aren't mapped?
Take a look at the PersistenceSpecification class in FluentNHibernate: http://wiki.fluentnhibernate.org/Persistence_specification_testing
You could wrap this up using reflection to test every property if that makes sense for your system.
You could also try to use the NHibernate mapping metadata and search for unmapped properties via reflection in a UnitTest.
By using the Metatdata, it is transparent for your application if you are using fluent nhibernate or other means to create the nhibernate mapping.
If you test your mappings in UnitTests you will know during test-time not during application startup if your mappings are alright.
This question seems to be related and this shows how to query the metadata.
The bug where the database did not get updated can be caused by issues other than not mapped field/property. There may be other mapping mistakes that are impossible to catch using reflection. What if you used wrong cascade or wrong generator? Or forgot association mapping?
If you want to catch majority of mapping issues you should create an integration test that will execute against real or in-memory database. Good overview of this approach is here.

Will NHibernate attempt to save the same object twice in this scenario?

Lets say I have two entities: Stable, and Pony.
Stable has an IList<Pony> with a HasMany mapping configured with Cascade.All().
That means that if I do session.Save(myStable), each Pony in the collection will be saved.
However, what happens if I do this?
Pony myLittlePony = new Pony(Color.Brown);
Stable myStable = new Stable(WoodTypes.RichMahogany);
myStable.Ponies.Add(myLittlePony);
session.Save(myStable);
session.Save(myLittlePony);
Will NHibernate try to save myLittlePony twice? or is NHibernate "smart" enough to know that myLittlePony has already been persisted to the DB?
Are there any performance implications from doing something like this?
NHibernate is pretty smart, and AFAIK, since myLittlePony would still be in the session and have no changes (IsDirty returning false), it won't trigger another flush to the persistence medium.
It will probably only be saved once* when the transaction is flushed but you need to mark one side of the relationship (typically the collection) as the inverse side to denote the non-owner of the relationship. This will insure that the behavior is consistent. If you don't mark the collection as the inverse side of the relationship NH will do an insert then update the child object with the foreign key from the parent. This will obviously fail if there's a non-nullable FK constraint.
My guess is that internally NH will save the object twice but the second save won't generate a database operation because the object will not have pending changes at that point.
It should only get saved once.
And actually nothing usually makes it to the database until you commit the transaction or flush the session.
If there is SQL Server then run SQL Profiler to see what happen but NHibernate shoud persist that object only ones.
.Save() doesn't do anything other than tell the session about your new object*. NH only works out what to do when the session flushes to the database, which is normally when the transaction commits. It will never write the same entity twice.
(* OK, it may go to the database if you're using a bad identity generator)
I believe what you're doing there is actually what you need to do. The only way that would work with an individual session.Save(myStable); you would need to correctly configure the cascade relationship for the stable class.

Categories

Resources