NHibernate delete child from parent collection with delete cascade - c#

I have two simple entities named Country and City.
public class Country : Entity
{
public Country()
{
Cities = new List<City>();
}
public virtual string Name { get; set; }
public virtual IList<City> Cities { get; set; }
}
public class City : Entity
{
public virtual Country Country { get; set; }
public virtual string Name { get; set; }
}
The DB used is SQL Server and City has a foreign key to Country with cascade delete.
I am using Fluent NHibernate, this is the mapping configuration for the relation:
public CountryMap()
{
Id(x => x.Id, "IdCountry").GeneratedBy.Identity();
Map(x => x.Name).Not.Nullable().Length(50);
HasMany(x => x.Cities).KeyColumn("IdCountry").ForeignKeyConstraintName("FK_Cities_Countries")
.Not.KeyNullable().Cascade.AllDeleteOrphan().ExtraLazyLoad();
Table("Countries");
}
public CityMap()
{
Id(x => x.Id, "IdCity").GeneratedBy.Identity();
Map(x => x.Name).Not.Nullable().Length(50);
References(x => x.Country, "IdCountry").ForeignKey("FK_Cities_Countries")
.Not.Nullable().Not.Insert().Not.Update().Cascade.All().LazyLoad();
Table("Cities");
}
All works fine, but after delete a country, cities remain in parent collection and I want the cities to be removed from that collection. (As EF does)
The only way I found to get it working is refreshing the session (Clear, Evict...)

Manual deletion of the collection items is not a solution. It in facts breaks the cascading feature.
In case we do have mapping Cascade.AllDeleteOrphan(), we should expect that deletion of Parent will deleted Children as well - no need to do more then delete Parent. Just ... NHibernate does not care about clearing that collection in memory (app server/application)
In case you are searching for long, stable, solid solution I would strongly suggest:
Split READ and WRITE operations
The current frameworks we have (like Web API) are helping us to go this direction. We should create set of operations:
PUT, POST, DELETE ... to handle client requests for data amendments
GET ... to retrieve data by ID or by Criteria (Find())
Each of these operations should have its own session, its own transaction, and should represent the unit of work. All or nothing. Either operation is successful and all is persisted, or not (rollback)
That will help our application being successful and growing in a longer/long run. We do a split. We care about
1) DELETE, UPDATE or INSERT (or more of them arround some root entity, could see more here).
2) we do READ operation - expecting only SELECT operations, therefore working with up-to-date data. Also check this, including comments
While this could be a bit out of scope, this cite form 9.8. Exception handling doc is also the clue:
...If the ISession throws an exception you should immediately rollback the transaction, call ISession.Close() and discard the ISession instance. Certain methods of ISession will not leave the session in a consistent state...
I wanted to demonstrate, that the operation (session, transaction) should have only one goal (WRITE, READ) and be as short as possible...

With that kind of mapping, the easiest way to work is to remove the City from the Country collection and save the Country. With the cascade, the city will be deleted and the collection will be in the state you want.

Related

Why do I need to .Include() collections

I wrote a query which is pretty simple:
var locations = await _context.Locations
.Include(x => x.LocationsOfTheUsers)
.Include(x => x.Address)
.ThenInclude(x => x.County)
.Where(CalculateFilters(searchObj))
.ToListAsync(cancellationToken);
And everytime LocationsOfTheUsers were null so I decided to .Include(x => x.LocationsOfTheUsers) and I received results as expected but I'm not sure why do I have to include this collections since it's defined like this:
public class Location
{
public string Title { get; set; }
public long? RegionId { get; set; }
public Region Region { get; set; }
public long? AddressId { get; set; }
public Address Address { get; set; }
public long? CountyId { get; set; }
public County County { get; set; }
public ICollection<LocationsOfTheUsers> LocationsOfTheUsers { get; set; }
}
I thought this will be automatically included since it exist as ICollection in Location class.
So why is .Include() on LocationsOfTheUsers needed here?
Thanks guys
Cheers
In entity framework the non-virtual properties represent the columns of the tables, the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
So your property should have been defined as:
public virtual ICollection<LocationsOfTheUsers> LocationsOfTheUsers { get; set; }
One of the slower parts of a database query is the transfer of the selected data from the database management system to your local process. Hence it is wise to limit the selected data to the values you actually plan to use.
If you have a one-to-many relation between Schools and Students, and you ask for School [10] you don't want automatically to fetch its 2000 Students.
Even if you would like to have "School [10] with all its Students" it would not be efficient to use Include to also fetch the Students. Every Student will have a foreign key SchoolId with a Value of [10]. If you would use Include you would transfer this foreign key 2000 times. What a waste!
When using entity framework always use Select to fetch data and select only the properties that you actually plan to use. Only use Include if you plan to change the included items.
This way you can separate your database table structure from the actual query. If your database structure changes, only the query changes, users of your query don't notice the internal changes.
Apart from better performance and more robustness against changes, readers of your code can more easily see what values are in their query.
Certainly don't use Include to save you some typing. Having to debug one error after future changes will take way more time than you will ever save by typeing include instead of Select
Finally: limit your data early in your process, so put the Where in front.
So your query should be:
var predicate = CalculateFilters(searchObj)
var queryLocations = dbContext.Locations
.Where(predicate)
.Select(location => new
{
// Select only the location properties that you plan to use
Id = location.Id,
Name = location.Name,
// Locations Of the users:
UserLocations = location.LocationsOfTheUsers
.Select(userLocation => new
{
// again: only the properties that you plan to use
Id = userLocation.Id,
...
// Not needed, you already know the value
// LocationId = userLocation.LocationId
})
.ToList(),
Address = new
{
Street = location.Address.Street,
PostCode = location.Addrress.PostCode,
...
County = location.Address.County.Name // if you only want one property
// or if you want more properties:
County = new
{
Name = location.Address.County.Name,
Abbr = location.Address.Count.Abbr,
...
}),
},
});
I thought this will be automatically included since it exist as ICollection in Location class.
Well, it's not automatically included, probably for performance reasons as the graph of related entities and their recursive child entities may be rather deep.
That's why you use eager loading to explicitly include the related entities that you want using the Include method.
The other option is to use lazy loading which means that the related entities are loaded as soon as you access the navigation property in your code, assuming some prerequisites are fulfilled and that the context is still around when this happens.
Please refer to the docs for more information.
I believe you are using EntityFrameworkCore. In EntityFramework (EF6), lazy loading is enabled by default, However, in EntityFrameworkCore, lazy loading related entities is handled by a separate package Microsoft.EntityFrameworkCore.Proxies.
To enable the behaviour you are seeking, install the above package and add the following code
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
After this, the related entities will be loaded without the Include call.

Entity Framework hard cascade delete

I have a SQLite DB mapped with Entity Framework.
There are 2 tables : Collections (1:n) Albums.
When I delete a collection, all related albums have to be deleted as well.
I use CollectionRepo.Delete(collection); to achieve that. It uses the following code :
public int Delete(Collection entity)
{
Context.Entry(entity).State = EntityState.Deleted;
return Context.SaveChanges();
}
The problem is: when I execute this code, Context.SaveChanges(); give me an exception:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
It seems that Entity Framework wants to null on the foreign keys instead of deleting the entries. But this is absolutely not what I want because an album makes no sense without a parent (in my use case at least).
I could obviously manualy delete the albums first and then delete the empty collection but it seems to me a bit tricky. First, it seems to me that EF should be smart enough to do it on it's own to simplify the code and second, what if I have dozens of relations to collections and albums, I would end up with quite a big, hard to maintain, code base.
Collection Class
public class Collection
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public virtual List<Album> Albums { get; set; } = new List<Album>();
}
Album class
public class Album
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
[ForeignKey("Collection")]
public long CollectionId { get; set; }
public virtual Collection Collection { get; set; }
}
DbContext child class
public class DataEntities : DbContext
{
public virtual DbSet<Collection> Collections { get; set; }
public virtual DbSet<Album> Albums { get; set; }
public DataEntities() : base("name=Connection")
{
Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Album>()
.HasRequired(a => a.Collection)
.WithMany(c => c.Albums)
.HasForeignKey(a => a.CollectionId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Collection>()
.HasMany(c => c.Albums)
.WithRequired(a => a.Collection)
.WillCascadeOnDelete(true);
}
}
Applying detached object graph modifications has always been unclear in EF. This is one of the cases where it fails without a good reason.
Assuming the Collection entity passed to the Delete method has Albums collection populated (at least this is how I was able to reproduce the exception). The line
Context.Entry(entity).State = EntityState.Deleted;
does two things: attaches entity and all Album objects from the entity.Albums to the context, marks entity as Deleted, and (note!) the Album objects as Modified. This leads to incorrect behavior when you call SaveChanges, and at the end generates the exception in question.
There are two ways (workarounds) to fix this incorrect behavior.
The first one is to replace the above line with
Context.Collections.Attach(entity);
Context.Collections.Remove(entity);
The effect is similar to the described above, with the importand difference that now the related Album objects arte marked as Deleted, which allows successfully executing the SaveChanges.
The drawback is that now the SaveChanges issues a DELETE command for each Album before the command for deleting the Collection, which is inefficient and doesn't make much sense since the cascade delete would handle that perfectly inside the database.
The second option is to keep the code as is, but clear the related collection before attaching the entity:
entity.Albums = null;
Context.Entry(entity).State = EntityState.Deleted;
This allows successfully executing SaveChanges and it generates a single DELETE command only for the entity.
The drawback is that you need to write additional code and not forget any child collection which supports cascade delete, and not doing that for collections that need cascade update (i.e. with optional relation which requires updating the FK field with NULL).
The choice is yours.
Per your comments, you're mapping to a pre-existing database (EF did not generate it). CascadeOnDelete only affects the generation of the database. If the database doesn't have CascadeOnDelete configured on the table, then EF will be confused when it attempts to delete and Sqlite doesn't comply.
Also you have the mapping for the foreign key as non-nullable and required (redundant by the way) but in the database the foreign key is nullable. EF assumes that it's not valid because of what you told it.
If you fix your mapping (remove the required annotation from the CollectionID property and change its type to int? instead of just int you should fix your problem. Actually change the mapping in the DbContext class from HasRequired to HasOptional...from there it should work.
Either that or change the table definitions on your database itself.

Fluent NHibernate Mapping: one-to-one (or none)

I have a following database scheme setup which I can't really change.
User
----
Id (primary key)
[Some simple properties...]
UserAdditionalData
------------------
Id (primary key)
[Some simple properties...]
USERID (foreign key to User)
It's clear that the User table doesn't really have any recollection whether or not it is linked to a UserAdditionalData record, so I don't think I can call this a true one-to-one mapping here since they also don't share a mutually exclusive PK.
However, in practice, I would like to be able to work on a User object and for example check if it has a UserAdditionalData record and if so, access its properties.
I've set up my BDO as such:
public class User
{
[Some simple properties...]
public virtual UserAdditionalData UserAdditionalData { get; set; }
}
public class UserAdditionalData
{
[Some simple properties...]
public virtual User User { get; set; } /* I have this here,
but I don't really ever
have to access it in this
direction */
}
I've set up my mapping as such:
public UserMapping()
{
Table("USER");
[Some simple properties...]
HasOne(x => x.UserAdditionalData).Cascade.None();
}
public UserExtraMapping()
{
Table("USER_ADDITIONAL_DATA");
[Some simple properties...]
References(x => x.User, "USERID").Unique();
}
This all compiles, but I see that my UserExtra object (when accessed via a User object) is always null.
I've tried a lot of different ways to go about it, read a lot on implementing this as a one-to-many. However, I'm still not being able to get it to work.
Any help would be much appreciated.
Thanks!
[Small UPDATE]: I only have to query the database, not save to it if that's relevant in any way.
Based on your small update, I would go with a simplified mapping. We would profit from NHibernate real mapping power, and also optimize the User loading. All that because we do need Read-Only mapping.
Firstly, we should introduce simple int property UserId on the Additional class
// extra class is having an int property containig the foreign key
public class UserAdditionalData
{
public virtual int UserId { get; set; }
}
// that would be the mapping:
public UserExtraMapping()
{
...
Map(x => x.UserId, "USERID");
}
Now, we will use well optimized mapping for lazy loading many-to-one (I.e. in comparison with one-to-one which loads both ends always, here we will get the reference data only if really needed!)
public UserMapping()
{
...
References(x => x.UserAdditionalData)
.LazyLoad()
.PropertyRef(e => e.UserId)
.Not.Insert()
.Not.Update()
;
}
So, for readonly I would do the best to use many-to-one mapping (References())
see also:
5.1.10. many-to-one
mapping by code, ManyToOne (scroll down to Fluent NHibernate's equivalent)

Updating Bag items in NHibernate Merge

A little curiosity. I have a User object which contains a bag each of UserPhoto, UserMatchInterest, UserPreference objects. I have given each item in the bag a reference to the parent User and with nhibernate I have got the two way mapping sorted so that when you Create the user object for the first time, it automatically creates the UserPhoto, UserMatchInterest and UserPreference objects in the collection bags, setting the UserId to the parent User object, that works fine.
As an example, the UserPhoto table has a PhotoId PK column and a UserId FK column. The UserPhoto object has the PhotoId property and a User property (not UserId) and so rather than holding the UserId, it holds a reference to the parent and populates the DB column based on the Users PK.
The problem I have is when I want to update the User object all in one go. The rest of the User object updates fine, but when it comes to the photos, it creates new photos in the database. I can understand why, as they are not linked at all to the previous photo session objects which is acceptable as being an ASP.NET website I will be dealing with detached objects. But it leaves the old ones. So if you had photo ID 1 & 2, with UserId=1. After the update, you will have photos 1,2,3 & 4 with UserId=1. What I want is for photo 1 & 2 to be deleted and then insert 3 & 4 instead.
I have tried to retrieve them independently as a collection and delete them in the transaction first, but I get the message
Message = "deleted object would be re-saved by cascade (remove deleted object from associations)
Code to delete is as follows
// First delete existing photos, interests and preferences
var photos = from row in repository.GetItemsAsQuery<UserPhoto>()
where row.User.UserId == user.UserId
select row;
repository.DeleteItems(photos.ToList());
var interests = from row in repository.GetItemsAsQuery<UserMatchInterest>()
where row.User.UserId == user.UserId
select row;
repository.DeleteItems(interests.ToList());
var preferences = from row in repository.GetItemsAsQuery<UserPreference>()
where row.User.UserId == user.UserId
select row;
repository.DeleteItems(preferences.ToList());
// Now update the user object and re-add the above linked items
repository.UpdateItem(user);
The error is thrown on the repository.DeleteItems(interests.ToList()); line, the first delete passes fine - though it is all in a transaction.
My question is am I approaching this the right way to update an object in the DB which has bags of other objects it needs to update as well? I don't see any way to update existing photo objects without manually setting ID's - and the user may have replaced all photos or added/deleted anyway so it is probably cleaner to delete existing and re-add new, but how do I delete existing ones without getting this cascade error?
from your description i envision the following classes
public class User
{
public virtual int Id { get; set; }
public virtual ICollection<Photo> Photos { get; private set; }
}
public class Photo
{
public User Parent { get; set; }
public virtual string Name { get; set; }
public virtual byte[] Data { get; set; }
}
then the mapping would look like
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.Id);
HasMany(x => x.Photos)
.AsSet() // no duplicate entries, allows NH to optimise some things
.Cascade.AllDeleteOrphan()
.Component(c =>
{
c.ParentReference(x => x.Parent);
c.Map(x => x.Name);
c.Map(x => x.Data);
});
}
}
Note: the Cascade.AllDeleteOrphan will delete all children automaticly which are not part of the collection anymore

NHibernate recreates every associated item

I have a very simple object models.
public class Contact
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Device Device { get; set; }
public virtual IList<string> Numbers { get; set; }
public Contact()
{
Numbers = new System.Collections.Generic.List<string>(3);
}
}
As you can see, the class Contact has an association with Numbers, which is a list of strings.
Here's the mapping:
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
References(x => x.Device, "DeviceId");
Table("Contacts");
HasMany(x => x.Numbers)
.Table("ContactNumbers")
.Element("Number")
.KeyColumn("ContactId")
.LazyLoad()
.Cascade.All()
.Not
.Inverse();
Note that I can't and don't want the collection to be inverse=true, because it's just a collection of string. This means that Contact is responsible for updating Numbers entries.
Now my problem is that, whenever I try to add a new number to an existing Contact, it deletes all associated numbers and recreates them individually. Isn't NHibernate smart enough to detect changes and update only changed items?
I think there should be a simple solution for my problem but don't know what.
Any help would be appreciated.
This is actually documented in NHibernate's documentation.
Bags are the worst case. Since a bag permits duplicate element values
and has no index column, no primary key may be defined. NHibernate has
no way of distinguishing between duplicate rows. NHibernate resolves
this problem by completely removing (in a single DELETE) and
recreating the collection whenever it changes. This might be very
inefficient.
Try using an <idbag> mapping instead, and create a surrogate primary key for that table. Unfortunately, looks like <idbag> is not yet supported in FluentNHibernate.
Also, take a look at other collection mapping options.

Categories

Resources