Is there a way to force NHibernate to treat string keys as case insensitive?

I use NHibernate's second level cache on an object whose primary key is a string column. The database is not case sensitive, however, when I retrieve an object using the same ID but with a different body, then NHibernate treats the object as another object in the cache. For example, I have a settings table with the Name column as the primary key. If I try to restore the object using session.Get<Setting>("TestMode") , Get<Setting>("testmode") or Get<Setting>("testmode") , which will go to the database 3 times and put 3 separate object in the cache of the second level. If I then changed something to my installation object via NHibernate, it will update the cached object, which actually corresponds to the casing on the entity, since it is stored in the database (in this case, the one called TestMode). The problem is that the rest of the objects will still be in the cache, but will be obsolete.

I understand why NHibernate treated identifiers as case sensitive by default, but there seems to be no way to set case sensitivity at the entity level or configuration level as far as I can find. Can this be done, and if so, how?

If this helps, I use Fluent NHibernate. The column "Identifier" is indicated as such:

 Id(x => x.Name).GeneratedBy.Assigned().Column("Name"); 

UPDATE The following are the implementations of Equals () and GetHashCode () from the Setting object.

 public override bool Equals(object obj) { if (obj == null) return false; if (obj is Setting) { if (object.ReferenceEquals(this, obj)) return true; if (this.Name.Equals(((Setting)obj).Name, StringComparison.OrdinalIgnoreCase)) // Ignore case because our databases are case insensitive! return true; } return false; } public override int GetHashCode() { return Name.ToUpperInvariant().GetHashCode(); } 

UPDATE 2 I profiled the NHibernate Profiler and made retries for "TestMode", "testmode", "TestMode", "Testmode", "TESTMODE" (in that order), it shows only cache hit for the second "TestMode". It shows "Setting the load on the cache of the 2nd level (TestMode / * id * /)", all the others show SELECT ... FROM Setting setting0_ WHERE setting0_.Name = "testmode" (in any case, which requested a call). When I click on the link “Look at 1 row (s) obtained as a result of this statement”, it shows me a row with the “Name” column having the correct “TestMode” body, so it explicitly retrieves the object with the right shell for the column itself, but it seems that it definitely associates the entity with the identifier with which the Get () function was called. It does not look like it should be normal behavior, but I could not find any information about the specifics of the operation of the 2nd level cache, as well as just how to use it in the NHibernate cookbook.

Although I doubt this should change, I use HashtableCacheProvider for all of these tests. (for production we will use a paid caching provider)

+4
source share
3 answers

If you think of cache as a dictionary with a string key, it is obvious that the keys "TEST" and "test" are different because C # is case sensitive.

But something here does not seem right. How do you know that NH puts three objects in the second level cache? Assuming you are using a case-insensitive database, all three queries generated by the Get method will return the same string, and the object's ID property will be set using the value returned by the query and not the value provided by the Get method. Objects will also be cached using the object identifier. Do all three Gets instances return instances with an identifier in the same case ("TestMode")?


An update based on changes to your question, if I understand you correctly:

  • Get ("TestMode") caches the object as expected and retrieves it from the cache on subsequent requests for "TestMode"
  • Get ("testmode") or any other case returns to the database

This is the behavior I would expect. The value specified for id is used to try to find the object in the cache. If a match is found, the object is retrieved from the cache; if the database is not requested. After restoring the object, NHibnernate tries to add it to the cache using its identifier as a key, but it already exists, so the cached value is updated, replaced, or ignored.

The key point is that the identifier provided to the request is used as the key to search for the object in the cache, but the identifier of the object is used as the key when it is added to the cache.

+1
source

The cache depends on the implementation of Equals() .

So, change the implementation of Equals() for this class to something that takes into account the problems with the case. Either compare with insensitive settings, or simply .ToUpper()/.ToLower() identifiers.

UPDATE: the question appears to be duplicated in the NHibernate problem with the assigned string identifier and various case characters . Try using the decision you made to answer

0
source

I think you can get around this by specifying your own row type, say CaseInsensitiveStringType , and then

 Id(x => x.Name) .GeneratedBy .Assigned() .Column("Name") .CustomType<CaseInsensitiveStringType>(); 
0
source

Source: https://habr.com/ru/post/1387206/


All Articles