Ravendb 4 - Create Index Programmatically - ravendb

How to add a index to specific database in RavenDb 4. I see the function
new SearchableIndex().Execute(_documentStore);
How to specify the database, without setting the default database as part of document store initialization. I wish to pass the database name as parameter.

You set the database for the DocumentStore using the Database property. When initializing, do it like this:
var _documentStore = new DocumentStore
{
Urls = new[] { "http://localhost:8080" },
Database = "databaseName"
};
new SearchableIndex().Execute(_documentStore);

Related

Renaming models in database with existing data when using RavenDb

Is there any 'easy' way to rename models in RavenDb when the database already has existing data? I have various models which were originally created in another language, and now I would like to rename them to English as the codebase is becoming quite unmaintainable. If I just rename them, then the data won't be loaded because the properties don't match anymore.
I would like the system to automatically do it on first load. Is there any best way how to approach this? My solution would be:
Check if a document exists to determine if the upgrade has been done or not
If upgrade has not been done, execute patch scripts to update fields
Update document to know that the upgrade has been done
I'd recommend you create new documents from the old documents.
This can be done pretty easily using patching via docStore.UpdateByIndex.
Suppose I had an old type name, Foo, and wanted to rename it to the new type name, Bar. And I wanted all the IDs to change from Foos/123 to Bars/123.
It would look something like this:
var patchScript = #"
// Copy all the properties from the old document
var newDoc = {};
for (var prop in this) {
if (prop !== '#metadata') {
newDoc[prop] = this[prop];
}
}
// Create the metadata.
var meta = {};
meta['Raven-Entity-Name'] = newCollection;
meta['Raven-Clr-Type'] = newType;
// Store the new document.
var newId = __document_id.replace(oldCollection, newCollection);
PutDocument(newId, newDoc, meta);
";
var oldCollection = "Foos";
var newCollection = "Bars";
var newType = "KarlCassar.Bar, KarlCassar"; // Where KarlCassar is your assembly name.
var query = new IndexQuery { Query = $"Tag:{oldCollection}" };
var options = new BulkOperationOptions { AllowStale = false };
var patch = new ScriptedPatchRequest
{
Script = patchScript,
Values = new Dictionary<string, object>
{
{ nameof(oldCollection), oldCollection },
{ nameof(newCollection), newCollection },
{ nameof(newType), newType }
}
};
var patchOperation = docStore.DatabaseCommands.UpdateByIndex("Raven/DocumentsByEntityName", query, patch, options);
patchOperation.WaitForCompletion();
Run that code once at startup, and then your app will be able to work with the new name entities. Your old entities are still around - those can be safely deleted via the Studio.

Update Document with external object

i have a database containing Song objects. The song class has > 30 properties.
My Music Tagging application is doing changes on a song on the file system.
It then does a lookup in the database using the filename.
Now i have a Song object, which i created in my Tagging application by reading the physical file and i have a Song object, which i have just retrieved from the database and which i want to update.
I thought i just could grab the ID from the database object, replace the database object with my local song object, set the saved id and store it.
But Raven claims that i am replacing the object with a different object.
Do i really need to copy every single property over, like this?
dbSong.Artist = songfromFilesystem.Artist;
dbSong.Album = songfromFileSystem.Album;
Or are there other possibilities.
thanks,
Helmut
Edit:
I was a bit too positive. The suggestion below works only in a test program.
When doing it in my original code i get following exception:
Attempted to associate a different object with id 'TrackDatas/3452'
This is produced by following code:
try
{
originalFileName = Util.EscapeDatabaseQuery(originalFileName);
// Lookup the track in the database
var dbTracks = _session.Advanced.DocumentQuery<TrackData, DefaultSearchIndex>().WhereEquals("Query", originalFileName).ToList();
if (dbTracks.Count > 0)
{
track.Id = dbTracks[0].Id;
_session.Store(track);
_session.SaveChanges();
}
}
catch (Exception ex)
{
log.Error("UpdateTrack: Error updating track in database {0}: {1}", ex.Message, ex.InnerException);
}
I am first looking up a song in the database and get a TrackData object in dbTracks.
The track object is also of type TrackData and i just put the ID from the object just retrieved and try to store it, which gives the above error.
I would think that the above message tells me that the objects are of different types, which they aren't.
The same error happens, if i use AutoMapper.
any idea?
You can do what you're trying: replace an existing object using just the ID. If it's not working, you might be doing something else wrong. (In which case, please show us your code.)
When it comes to updating existing objects in Raven, there are a few options:
Option 1: Just save the object using the same ID as an existing object:
var song = ... // load it from the file system or whatever
song.Id = "Songs/5"; // Set it to an existing song ID
DbSession.Store(song); // Overwrites the existing song
Option 2: Manually update the properties of the existing object.
var song = ...;
var existingSong = DbSession.Load<Song>("Songs/5");
existingSong.Artist = song.Artist;
existingSong.Album = song.Album;
Option 3: Dynamically update the existing object:
var song = ...;
var existingSong = DbSession.Load<Song>("Songs/5");
existingSong.CopyFrom(song);
Where you've got some code like this:
// Inside Song.cs
public virtual void CopyFrom(Song other)
{
var props = typeof(Song)
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(p => p.CanWrite);
foreach (var prop in props)
{
var source = prop.GetValue(other);
prop.SetValue(this, source);
}
}
If you find yourself having to do this often, use a library like AutoMapper.
Automapper can automatically copy one object to another with a single line of code.
Now that you've posted some code, I see 2 things:
First, is there a reason you're using the Advanced.DocumentQuery syntax?
// This is advanced query syntax. Is there a reason you're using it?
var dbTracks = _session.Advanced.DocumentQuery<TrackData, DefaultSearchIndex>().WhereEquals("Query", originalFileName).ToList();
Here's how I'd write your code using standard LINQ syntax:
var escapedFileName = Util.EscapeDatabaseQuery(originalFileName);
// Find the ID of the existing track in the database.
var existingTrackId = _session.Query<TrackData, DefaultSearchIndex>()
.Where(t => t.Query == escapedFileName)
.Select(t => t.Id);
if (existingTrackId != null)
{
track.Id = existingTrackId;
_session.Store(track);
_session.SaveChanges();
}
Finally, #2: what is track? Was it loaded via session.Load or session.Query? If so, that's not going to work, and it's causing your problem. If track is loaded from the database, you'll need to create a new object and save that:
var escapedFileName = Util.EscapeDatabaseQuery(originalFileName);
// Find the ID of the existing track in the database.
var existingTrackId = _session.Query<TrackData, DefaultSearchIndex>()
.Where(t => t.Query == escapedFileName)
.Select(t => t.Id);
if (existingTrackId != null)
{
var newTrack = new Track(...);
newTrack.Id = existingTrackId;
_session.Store(newTrack);
_session.SaveChanges();
}
This means you already have a different object in the session with the same id. The fix for me was to use a new session.

How to verify if NHibernate hbm.xml are matching with a particular SQL schema?

I had an ASP.NET MVC web application, using NHibernate as ORM on SQL Server 2008 R2. When we deployed to the server, we can update our database any time (some are ad-hoc changes).
The problem is when the database schema change, the application crashed because NHibernate .hbm.xml files are no longer matching with the DB schema.
How do I verify that my current *.hbm.xml file are matching with the database schema ? And how to detect the mismatch early in ASP.NET MVC ?
You can do the checking when application runs, could be in the global asax.
protected void Application_Start()
{
}
The connection string is the key to get the expected schema.
<property name="connection.connection_string">Server=.;Initial Catalog=TheExpectedSchema; ..</property>
First read the expected schema by reading it from nhibernate config and retrieve it from Initial Catalog part (if the database is oracle, probably use the User ID part).
NHibernate.Cfg.Configuration config = ...;
var conStr = config.Properties["connection.connection_string"];
var match = Regex.Match(conStr, "Initial Catalog *= *([^;]*) *");
var expectedSchema = match.Groups[1].Value;
Then read the actual schema by reading it from *.hbm.xml file.
<hibernate-mapping schema="TheActualSchema"
If the files are put under App_Data directory, read each file and use xml document to get the schema.
var appDataDir = new DirectoryInfo(HttpContext.Server.MapPath("~/App_Data"));
var files = appDataDir.GetFiles("*.hbm.xml");
foreach (var file in files)
{
var doc = new XmlDocument();
doc.Load(file.FullName);
var actualSchema = doc.DocumentElement.GetAttribute("schema");
if (actualSchema != expectedSchema)
{
// Proper handling here (an example would be throwing exception).
throw new Exception(string.Format("Expected schema: {0}, actual schema {1}", expectedSchema, actualSchema));
}
}

Ravendb doesn't save propery Raven/Hilo/LoggingMessages

I have a problem saving an object to Ravendb.
Everytime i save the object into Ravendb ,
it only save this below
Raven/Hilo/LoggingMessages
{
"Max": 32
}
I don't even have property called Max on LoggingMessages class.
And, it kept doing that.
I used this Ravendb in a project that use NserviceBus.
My assumption is that the Ravendb Client library that is used by NserviceBus is different with other Ravendb Client.
Because I have no problem saving an object of type LoggingMessage in other project that doesn't have NserviceBus.
LoggingMessage errormessage = new LoggingMessage();
errormessage.MessageBody = "test";
errormessage.MessageId = "test";
using (var store = new DocumentStore { ConnectionStringName = "RavenDB" } )
{
store.Initialize();
using (var session = store.OpenSession())
{
session.Store(errormessage);
session.SaveChanges();
}
}
That's how RavenDB generates IDs. Its a system document. Don't worry about that.
What's probably happening is that you are saving this document to the database that NServiceBus is using, but you are looking at either the RavenDB System Database, or you are writing it to a separate one.
In Raven Studio, check the "databases" list in the upper-right corner.
In your code, you can set the database name either in the connection string, or as a parameter to the new DocumentStore constructor, or as a parameter to the OpenSession method.

Multiple sessions with Fluent NHibernate and Ninject

I am working on setting up a multi-tenant, seperate database application and have made some good progress from reading this post below on stackoverflow.
Multitenancy with Fluent nHibernate and Ninject. One Database per Tenant
I see two sessions being setup. One is the 'master' session that will be used to get the tenant information and then the tenant session which is specific to the subdomain. I have the app switching nicely to the specified database based on domain and have questions on how to setup the 'master' database session and how to use it.
I tried registering a new session specifically for the master session be get an error regarding having already registered an ISession.
I'm new to nHibernate and not sure the best route to take on this.
NinjectWebCommon.cs
kernel.Bind<WebApplication1.ISessionSource>().To<NHibernateTenantSessionSource>().InSingletonScope();
kernel.Bind<ISession>().ToMethod(c => c.Kernel.Get<WebApplication1.ISessionSource>().CreateSession());
kernel.Bind<ITenantAccessor>().To<DefaultTenantAccessor>();
ITenantAccessor.cs
public Tenant GetCurrentTenant()
{
var host = HttpContext.Current.Request.Url != null ? HttpContext.Current.Request.Url.Host : string.Empty;
var pattern = ConfigurationManager.AppSettings["UrlRegex"];
var regex = new Regex(pattern);
var match = regex.Match(host);
var subdomain = match.Success ? match.Groups[1].Value.ToLowerInvariant() : string.Empty;
Tenant tenant = null;
if (subdomain != null)
{
// Get Tenant info from Master DB.
// Look up needs to be cached
DomainModel.Master.Tenants tenantInfo;
using (ISession session = new NHibernateMasterSessionSource().CreateSession())
{
tenantInfo = session.CreateCriteria<DomainModel.Master.Tenants>()
.Add(Restrictions.Eq("SubDomain", subdomain))
.UniqueResult<WebApplication1.DomainModel.Master.Tenants>();
}
var connectionString = string.Format(ConfigurationManager.AppSettings["TenanatsDataConnectionStringFormat"],
tenantInfo.DbName, tenantInfo.DbUsername, tenantInfo.DbPassword);
tenant = new Tenant();
tenant.Name = subdomain;
tenant.ConnectionString = connectionString;
}
return tenant;
}
Thanks for you time on this.
Add another session binding and add some condition. E.g.
kernel
.Bind<ISession>()
.ToMethod(c => c.Kernel.Get<NHibernateMasterSessionSource>().CreateSession())
.WhenInjectedInto<TenantEvaluationService>();