I'm using Fluent NHibernate for my data-persistence in a web application.
My problem... I have a base class that maps all entities with an ID property of type T (almost always an int or GUID) using GeneratedBy().Identity()
On application start-up, I have a boot-strapper that checks and verifies the needed seed-data is populated. My problem is, some of the seed-data that is populated needs a specific ID. (IDs that would correspond to an enum or system user)
Is there any way to force NHibernate to commit the record using the ID that I specify, rather than an auto-generated one? Any other commits to the repository thereafter can be auto-generated.
Id(x => x.Id).GeneratedBy.Assigned();
If you want the application to assign identifiers (as opposed to having NHibernate generate them), you may use the assigned generator. This special generator will use the identifier value already assigned to the object's identifier property. Be very careful when using this feature to assign keys with business meaning (almost always a terrible design decision).
Due to its inherent nature, entities that use this generator cannot be saved via the ISession's SaveOrUpdate() method. Instead you have to explicitly specify to NHibernate if the object should be saved or updated by calling either the Save() or Update() method of the ISession.
http://nhibernate.info/doc/nhibernate-reference/mapping.html#mapping-declaration-id-assigned
you can use
Id(I => I.Id).GeneratedBy.Increment(); //Increment by 1
Related
I have quite some entities in the DB and all of them have a column decimal DBSTATE indicating if the entry is active (1) or not (0). To make a quick work of getting the instances, I created the following generic function that returns only the active columns of entities:
IEnumerable<DBType> GetActiveEntries<DBType>()
where DBType : class, IDBEntry
{
return db.Set<DBType>().Where(e => e.DBStateInDB == (decimal)DBState.Active).AsEnumerable();
}
IDBEntry is an interface that all the model classes implement by returning its DBSTATE value, e.g. this is how REGULARCUSTOMER implements it (irrelevant parts are omitted):
public decimal DBStateInDB => this.DBSTATE
As it turns out, this is not possible, because EF can only work with its own types in queries, this is why the following non-generic function works:
IEnumerable<REGULARCUSTOMER> GetActives_TEMP()
{
return db.REGULARCUSTOMERs.Where(e => e.DBSTATE == (decimal)DBState.Active).AsEnumerable();
}
So my question: is it possible to somehow avoid writing separate functions/switch-cases for all the entities or I really stuck with that option?
I'd say you have to go around the problem.
One possible way of doing it would be to add Navigational properties (by creating a new entity type) to your entities, as DbSet<EntityState> (for example) which your entity would have a ForeignKey defining the State, this would oblige you to have a supplementary table which would contain the ID's of every entity and a bool for their state, making it clean and accessible. This also means that with your newly created Navigational property you could access it with YOUR_ENTITY.EntityState.State or YOUR_ENTITY.EntityState.Id, it would also give you the capacity to modify/access a entities State regardless of their type because of the new table. Your type enherited rules will still apply and changing the table will change the entity, regardless of Type.
But again, this is only a way of doing. It should be relatively simple to implement and can be quite powerful.
Let me know what you think.
In previous version of EF, to specify a computed column we would write:
modelBuilder
.Entity<Type>()
.Property(x => x.ComuptedProperty)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
This would make sense, because the SQL expression for the computed column is written once in database.
However, after migrating to EF Core, we realized that the syntax should be changed into:
modelBuilder
.Entity<Type>()
.Property(x => x.ComuptedProperty)
.HasComputedColumnSql("SQL Expression should be duplicated here");
This makes sense when we go code first. Because EF Core uses this SQL expression while creating the table.
However, for DB first scenarios this doesn't make sense at all. We tried to leave this parameter empty, and it throws an exception complaining:
The string argument 'sql' cannot be empty
Now things get even worse when you want to have a data access generator. How can we neglect this parameter?
Indeed when using HasComputedColumnSql you must specfiy the SQL query that will be used for the computed column when generating SQL Script for the associated table. Like you say, this is useful only for Code First approach.
In Database First approach, you can use one of the following methods frol PropertyBuilder<TProperty> type (description are from XML documentaiton of those methods):
ValueGeneratedOnAdd(): Configures a property to have a value generated only when saving a new entity,unless a non-null, non-temporary value has been set, in which case the set value will be saved instead. The value may be generated by a client-side value generator or may be generated by the database as part of saving the entity.
ValueGeneratedOnAddOrUpdate(): Configures a property to have a value generated when saving a new or existing entity.
ValueGeneratedOnUpdate(): Configures a property to have a value generated when saving an existing entity.
In your case because it's a computed column then the value maybe generated when adding and saving the data so you must use ValueGeneratedOnAddOrUpdate() method. Again EF documentation say that :
This just lets EF know that values are generated for added or updated entities, it does not guarantee that EF will setup the actual mechanism to generate values.
It's not a really common case, but in this particular one the task is to accomplished this :
Serialize a list of existing entities loaded with NHibernate in database A
Export the serialized entities to a file
Import the serialized entities file to database B, using NHibernate as the persisting agent
The problem is the entities maps is marked with Id generator :
public class EntityMap
{
public EntityMap()
{
Id(x => x.Id)
.GeneratedBy.Guid();
// Other properties
}
}
With that mapping, everytime I called :
ISession session = NHibernateSession.GetSession();
IList<Entity> entities = // Load entity from serialized object in a file
foreach(entity in entities)
{
session.Save(entity);
}
NHibernate keeps generating a new Id for those entities.
Is there a way to keep the mapping having Id generation strategy, but somehow able to persist an existing entity with it's Id predefined?
The logic here is simple:
You want to have application-assigned identifiers. Therefore, you should configure NHibernate for application-assigned identifiers.
What you're asking is basically to tell NHibernate to generate the identifiers for you, but then insisting to do it yourself anyway.
One can also note that NHibernate isn't really intended as a replication tool. Have you considered serializing the entities to an SQL script instead, that you can simple execute on the target database? That would circumvent NHibernate's id assignment.
For doing distributed database, my suggestions are:
To begin with, I would look to see if the database system itself have any sort of replication/data stream/data mirror that could be used for this. If that didn't work, I would probably try to write something similar - that is, bypass NH on the read side as well, just do simple table reads and write out INSERT statements to a file, that the recipient can apply. This works as long as there are no or few data transformations required. If the data needs complex transformations (which couldn't be avoided)... well if the logic gets complex enough we get back to doing it through NH.
Another way is to try to capture the input that modifies the original database (e.g. command pattern), and pass that same input to all the other databases also. Essentially letting each system react to the input - with a deterministic system and the same input, all databases will end up in the same state.
Currently our new database design is changing rapidly and I don't always have time to keep up to date with the latest changes being made. Therefore I would like to create some basic integration tests that are basically sanity checks on my mappings against the database.
Here are a few of the things I'd like to accomplish in these tests:
Detect columns I have not defined in my mapping but exist in the database
Detect columns I have mapped but do NOT exist in the database
Detect columns that I have mapped where the data types between the database and my business objects no longer jive with each other
Detect column name changes between database and my mapping
I found the following article by Ayende but I just want to see what other people out there are doing to handle these sort of things. Basically I'm looking for simplified tests that cover a lot of my mappings but do not require me to write seperate queries for every business object in my mappings.
I'm happy with this test, that comes from the Ayende proposed one:
[Test]
public void PerformSanityCheck()
{
foreach (var s in NHHelper.Instance.GetConfig().ClassMappings)
{
Console.WriteLine(" *************** " + s.MappedClass.Name);
NHHelper.Instance.CurrentSession.CreateQuery(string.Format("from {0} e", s.MappedClass.Name))
.SetFirstResult(0).SetMaxResults(50).List();
}
}
I'm using plain old query since this version comes from a very old project and I'm to lazy to update with QueryOver or Linq2NH or something else...
It basically ping all mapped entities configured and grasp some data too in order to see that all is ok. It does not care if some field exists in the table but not on the mapping, that can generate problem in persistence if not nullable.
I'm aware that Fabio Maulo has something eventually more accurate.
As a personal consideration, if you are thinking on improvement, I would try to implement such a strategy: since mapping are browsable by API, look for any explicit / implicit table declaration in the map, and ping it with the database using the standard schema helperclasses you have inside NH ( they eventually uses the ADO.NET schema classes, but they insulate all the configuration stuff we already did in NH itself) By playng a little with naming strategy we can achieve a one by one table field check list. Another improvement can be done by, in case of unmatching field, looking for a candidate by applying Levensthein Distance to all the available names and choosing one if some threshold requisites are satisfied. This of course is useless in class first scenarios when the DB schema are generated by NH itself.
I use this one too:
Verifying NHibernate Entities Contain Only Virtual Members
According to REST philosophy, a PUT operation should (taken from Wikipedia):
PUT http://example.com/resources/142
Update the address member of the collection, or if it doesn't exist, create it.
NHibernate seems to have two ways of dealing with entity IDs:
Auto-generate an ID, regardless of what value the user set.
Use the ID assigned by the user, but lose all auto-generation capabilities.
The problem here with a PUT operation is the part about creating the entity if it doesn't exist. My assumption is that if you PUT a resource that doesn't exist, it will create it with the same ID as specified by the URL (such as 142 if we use the above example). However, NHibernate doesn't allow you to set the ID if it's auto-generated.
So my question is, is there a way to get NHibernate to auto-generate an ID if the entity doesn't have one (or has the default value for the ID type, for example 0 for ints), but also save the entity with the ID that the user set?
Generally its a bad idea to use assigned ids.
The situation that you have is closer to the thing called NaturalId. You should use it I think. You will need to have two different properties, one for databases primary key, and second as a id that is visible to users.