Changing the value of an Id in RavenDB - ravendb

We have an entity named Organization that we use the UniqueConstraints-bundle on. We have a property named NetName that is a UniqueConstraint and an automaticly generated Id.
Since this is unneccesary we want to use the NetName-property as Id instead. So that we don't need UniqueConstraints to know that it is unique and also get the benefit from being able to use Load when we have the NetName.
We needed to clean up our netname a bit before using it as an Id so we created a new temporary-property called TempUniqueNetName that now holds the value of:
"organizations/"+ CleanupId(this.NetName)
So we are now ready to simply move that value to our Id. But we can't get it to work. Our problem is that with the PatchRequest below we end up with a new property named Id in the database but the acctual Id still has the same value (see screenshot). Is there a better (correct) way to change the value of an Id?
The Entity:
class Organization {
public string Id { get; set; }
[UniqueConstraint]
public string NetName { get; set; }
public string TempUniqueNetName{ get; set; }
}
We want to do something like this:
_documentStore.DatabaseCommands.UpdateByIndex(typeof(Organizations).Name,
new IndexQuery(),
new[]
{
new PatchRequest()
{
Type = PatchCommandType.Rename,
Name = "TempUniqueNetName",
Value = new RavenJValue("Id")
}
});

I don't think you can change the document key via patching. It's not actually stored with the document or the metadata - it's copied into the #id metadata on load to give you the illusion that it's there, and the Raven Client copies it again into your own identity property in the document. But really, it's a separate value in the underlying esent document store. Raven would have to know specifically how to handle this and fake it for you.
You could manually copy the doc from the old id to the new one and delete the old, but that could be time consuming.
There isn't a great answer for renaming a document key right now. There really should be a DatabaseCommand for rekeying a single document, and separate PatchCommandType to rekey when patching. Perhaps this will be added to raven in the future.

You can check implemtation of PUT-DELETE usage for updating IDs in my github repo.
It should look something like this:
store.DatabaseCommands.Put(updatedKey, null, document.DataAsJson, newMetadata);
store.DatabaseCommands.Delete(oldKey, null);
https://github.com/Sevsoad/SagaUpdater/
Also here is some Raven documentation:
https://ravendb.net/docs/article-page/3.0/csharp/client-api/commands/documents/put

Related

Why does Entity Framework keep setting these properties to their default values?

Ecommerce web site, written using ASP.NET Core 3.1, Razor pages, EF 3.1.8 code first.
I have a model called Globals. When I first set this up, I included the default values for four of these (other properties not shown for clarity)...
[Column(TypeName = "decimal(18,2)")]
public decimal DeliveryCharge { get; set; } = 3.5M;
[Column(TypeName = "decimal(18,2)")]
public decimal FreeDeliveryOver { get; set; } = 70.0M;
public string StripePublicKey { get; set; } = "pk_test_...";
public string StripeSecretKey { get; set; } = "sk_test_...";
When I deployed the site to the production server, I manually set the two Stripe keys to the live values, so the site could take payment.
This all worked fine until one day, seemingly out of the blue, we got an exception from Stripe, as the site was using the test keys again. I checked the Globals table, and yes, the test keys were back in there.
Now there are only two of us developing this site, no-one else has access to it, so no-one has done this manually.
Baffled, I set them to the live values, somewhat worried about how this could have happened.
A week later, it happened again. A this point, it dawned on me that as the only place in the entire code base that had the test keys was this model, and the migrations that were generated, maybe EF was resetting the default values. I have no idea why this would be, as I would have thought that these values would only have bene used when the table was created, or these properties modified.
However, I removed them from the model, hoping that this would solve the problem.
Thankfully, the values on the live site have not been changed again, but I just ran the project in Visual Studio, and got an error as the Stripe keys in my local Globals table were null, presumably as I had removed the default values.
The odd thing is that the two decimal properties in there did not have their values reset to the defaults. I know this, as the FreeDeliveryOver one was changed to 50.0M shortly after we launched.
Anyone any idea what's going on here? I'm now worried that the live site is going to have the keys set to null, breaking the site when I'm not looking.
Is there any part of the app where you seed initial data?
Try to search the db table's entity name, to see if there is any programatic initalization.
Since the entity's default constructor would assign the values, is there any place where a new instance of this class is created? if so, you might have code where a new instance is returned with the default values, and later maybe attached and saved to the context by accident.
Does "globals" have row id's? are you referencing the same id when working with them and load them in the context?
( i will delete if non of the above is the case... )
After reading again the part where the decimals have stayed in place, is there any part where you update the values, but dont assign the string fields correctly? Dont forget that an update will go over the whole object, if its just .Update(data);

How to write null to Guid? field in DB?

I have a guid? field in my POCO and when I create a new entry in my DB and look at it through SSMSE, I see NULL in that field. After choosing a manager for this new user entry, the guid? field is updated to hold the id of the new manager. However, if I want to change the user back to having no manager, how can I set the field back to NULL?
Should I be using Guid instead of Guid? and write Guid.Empty to the DB instead?
Should I be using Guid instead of Guid? and write Guid.Empty to the DB instead?
Not really. You could do that, but you wouldn't really gain much. Semantically, if there is "no" value for a field, NULL is the correct value. What you are talking about is using a magic value.
how can I set the field back to NULL?
Set the Guid? to null on your POCO. For example, if your POCO looks something like this:
public class Poco
{
public Guid? Manager { get; set; }
}
Set it to null like this:
somepocoinstance.Manager = null;
Then commit the changes.

How do I serialize/deserialize a NHibernate entity that has references to other objects?

I have two NHibernate-managed entities that have a bi-directional one-to-many relationship:
public class Storage
{
public virtual string Name { get; set; }
public virtual IList<Box> Boxes { get; set; }
}
public class Box
{
public virtual string Box { get; set; }
[DoNotSerialize] public virtual Storage ParentStorage { get; set; }
}
A Storage can contain many Boxes, and a Box always belongs in a Storage. I want to edit a Box's name, so I send it to the client using JSON. Note that I don't serialize ParentStorage because I'm not changing which storage it's in.
The client edits the name and sends the Box back as JSON. The server deserializes it back into a Box entity.
Problem is, the ParentStorage property is null. When I try to save the Box to the database, it updates the name, but also removes the relationship to the Storage.
How do I properly serialize and deserialize an entity like a Box, while keeping the JSON data size to a minimum?
I would recommend you send a DTO to the client for display purposes (and it should contain the unique database ID). Then send the boxId + the new name back up to the server from the client (there is no need to send the entire DTO back). The server can do a database lookup using the ID to get the box object, update the name field to the one sent from the client, and save the box to the database.
There is no need in this scenario to serialize an NHibernate object, that just adds a lot of complexity and no value.
I would guess that ParentStorage is null because it is being lazily loaded. Either configuring it to be fetched eagerly or forcing a read by calling the getter before serialization may help make sure the Storage is serialized, and depending on your ID scheme this may work (I don't know for sure).
The gains from serialization in this case seem minimal, and may have unexpected consequences as the Box and Storage classes evolve. It seems simpler to send up a single string for the name, load the Box, set the string, and save that object. Then you don't have to worry as much about the optimizations Hibernate does underneath.

NHibernate Domain Model - Adding Child Objects to Collections

I'm still learning here and have a question about child collections. I have an aggregate root called Audio, which has a collection of AudioDownloads.
The downloads are records of each IP address which downloads the audio, i don't want to have duplicate records of the same IP for each Audio.
In my domain i have the following function:
public virtual void Add(AudioDownload download)
{
if (!AudioDownloads.Contains(download)) {
TotalDownloads++;
AudioDownloads.Add(download);
}
}
And this is how i am calling the Add function:
var download = new AudioDownload();
audio.Add(download);
This is returning all downloads from the database for this Audio (which chould be thousands!), also it's still adding the download even though one already exists.
I'm using S#arp with the DomainSignature approach for comparing my entities.
Here is my Domain:
public class AudioDownload : Entity, ITenantSpecific
{
public AudioDownload() { DateAdded = DateTime.Now; }
[DomainSignature]
public virtual Audio Audio { get; set; }
[DomainSignature]
public virtual string Ip { get; set; }
public virtual DateTime DateAdded { get; set; }
}
My question is...even if i can get AudioDownloads not to add duplicate entries, should i be doing it this way at all?
Thank you very much!
Paul
I expect that most ways to do this will always lead you to query all downloads from the database, which is probably not what you want.
Another approach that might be cheaper is just to have a unique key in the database defined based on AudioId and Ip. If you then insert a record that duplicates these you will get an exception from NHibernate telling you a unique key was violated: handle that exception gracefully (i.e. don't show it as an error, load the existing AudioDownload and use that in future) and you will have achieved your goal, I believe.
When you use this approach do not check whether the download is already contained in the collection, since that would still trigger loading of all records.
On the other hand: would it not be interesting to see that something was downloaded from the same Ip multiple times?

How do I Insert or Update (or overwrite) a record using NHibernate?

I need to write a row to the database regardless of whether it already exists or not. Before using NHibernate this was done with a stored procedure. The procedure would attempt an update and if no rows were modified it would fallback to an insert. This worked well because the application doesn't care if the record exists.
With NHibernate, the solutions I have found require loading the entity and modifying it, or deleting the entity so the new one can be inserted. The application does have to care if the record already exists. Is there a way around that?
Does the Id Matter?
Assigned Id
The object has a keyword as an assigned id and is the primary key in the table.
I understand that SaveOrUpdate() will call the Save() or Update() method as appropriate based on the Id. Using an assigned id, this won't work because the id isn't an unsaved-value. However a Version or Timestamp field could be used as an indicator instead. In reality, this isn't relevant because this only reflects on whether the object in memory has been associated with a record in the database; it does not indicate if the record exists or not in the database.
Generated Id
If the assigned id were truly the cause of the problem, I could use a generated id instead of the keyword as the primary key. This would avoid the NHibernate Insert/Update issue as it would effectively always insert. However, I still need to prevent duplicate keywords. With a unique index on the keyword column it will still throw an exception for a duplicate keyword even if the primary key is different.
Another Approach?
Perhaps the problem isn't really with NHibernate, but the way this is modeled. Unlike other areas of the application, this is more data-centric rather object-centric. It is nice that NHibernate makes it easy to read/write and eliminates the stored procedures. But the desire to simply write without regard to existing values doesn't fit well with the model of an object's identity model. Is there a better way to approach this?
I`m using
public IList<T> GetByExample<T>(T exampleInstance)
{
return _session.CreateCriteria(typeof(T))
.Add(Example.Create(exampleInstance))
.List<T>();
}
public void InsertOrUpdate<T>(T target)
{
ITransaction transaction = _session.BeginTransaction();
try
{
var res=GetByExample<T>(target);
if( res!=null && res.Count>0 )
_session.SaveOrUpdate(target);
else
_session.Save(target);
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}
but FindByExample method returns all objects alike not objects with the exact ID what do you suggest ? since I have only object as parameter I don't have access to its specific ID field so I cannot use session.get(Object.class(), id);
Typically, NHibernate can rely on the unsaved-value to determine whether it should insert or create the entity. However, since you are assigning the ID, to NHibernate it looks like your entity has already been persisted. Therefore, you need to rely on versioning your object to let NHibernate know that it is a new object. See the following link for how to version your entity:
http://web.archive.org/web/20090831032934/http://devlicio.us/blogs/mike_nichols/archive/2008/07/29/when-flushing-goes-bad-assigned-ids-in-nhibernate.aspx
Use the session.SaveOrUpdate(object) method.
You can do
Obj j = session.get(Object.class(), id);
if (j != null)
session.merge(myObj);
else
session.saveOrUpdate(myObj);
Query objects where keyword = x, take FirstOrDefault. If it's null, Add new object, if it exists, update object that you got and call saveOrUpdate on it.
This worked for me:
Implementation
public void InsertOrUpdate<TEntity, TId>(TEntity entity) where TEntity : IIdentificableNh<TId>
{
var anyy = session.Get<TEntity>(entity.Id);
if (anyy != null)
{
session.Evict(anyy); //dispatch all data loaded, to allow updating 'entity' object.
session.Update(entity);
}
else
{
session.Save(entity);
}
session.Flush();
}
Entity
public class Caracteristica : IIdentificableNh<int>
{
public virtual int Id { get; set; }
public virtual string Descripcion { get; set; }
}
I had to create an interface (IIdentificableNh) that allows me to access the Id property value.
Usage example:
session.InsertOrUpdate<Caracteristica, int>(new Caracteristica { Id = 2, Descripcion = "Caracteristica2" });
call hibernate.saveOrUpdate() which will check if the object is in the database, update it if it is, and save (i.e. insert) it if it is not.