I would like to have a reference between two entities stored in the RavenDB document database. Since this is not a relational db I know that I am supposed to use the Denormalized Reference technique described on RavenDBs documentation. Whilst at first this seems fine, once I start to create a real-world domain ‘hierarchy’ including bidirectional references the effort of keeping all those references up to date feels disproportionate. I feel I may be going wrong somewhere.
Can you explain the best / simplest way to model a reasonably complex domain hierarchy using RavenDB?
Thanks
I am not sure whether this will go far enough to answer your question but here is how I go about creating a Denormalized Reference in RavenDB (this is taken from real code with non-essentials removed for clarity)
Domain
public class User : IUserIdentity
{
public string UserName { get; set; }
public IEnumerable<string> Claims { get; set; }
public string Id { get; set; }
public Guid FormsAuthenticationGuid { get; set; }
}
public class Assessment
{
public string Id { get; set; }
public UserReference User { get; set; }
public AssessmentState State { get; set; }
}
You can see that I have a Assessment
class that references a User
. This user reference are managed using the UserReference
class below.
Denormalized Reference
public class UserReference
{
public string Id { get; set; }
public string UserName { get; set; }
public static implicit operator UserReference(User user)
{
return new UserReference
{
Id = user.Id,
UserName = user.UserName
};
}
}
Note how the reference class also carries the UserName
. This value will not change very often but it may change so we need a way to update the UserName
property in the UserReference
property held in the Assessment
class. To make the change we must first find the correct Assessment
instances from RavenDB and for that we need an index.
Raven Index
public class Assessment_ByUserId : AbstractIndexCreationTask<Assessment>
{
public Assessment_ByUserId()
{
Map = assessments => from assessment in assessments
select new
{
User_Id = assessment.User.Id
};
}
}
This index needs to be invoked whenever a User
's UserName
value is updated. I have a UserService
class that helps me co-ordinate all my User related functions, so that is where I put this code.
I reuse this code for other references so it has been abstracted out a little. This may help you create the more complex hierarchies (or perhaps 'domain graph' is a better description) you want.
UserService
public static void SetUserName(IDocumentSession db, string userId, string userName)
{
var user = db.Load<User>(userId);
user.UserName = userName;
db.Save(user);
UpdateDenormalizedReferences(db, user, userName);
}
private static void UpdateDenormalizedReferences(IDocumentSession db, User user, string userName)
{
db.Advanced.DatabaseCommands.UpdateByIndex(
RavenIndexes.IndexAssessmentByUserId,
GetQuery(user.Id),
GetUserNamePatch(userName),
allowStale: true);
}
private static IndexQuery GetQuery(string propertyValue, string propertyName = "User_Id")
{
return new IndexQuery {Query = string.Format("{0}:{1}", propertyName, propertyValue)};
}
private static PatchRequest[] GetUserNamePatch(string referenceValue, string referenceName = "User")
{
return new[]
{
new PatchRequest
{
Type = PatchCommandType.Modify,
Name = referenceName,
Nested = new[]
{
new PatchRequest
{
Type = PatchCommandType.Set,
Name = "UserName",
Value = referenceValue
}
}
}
};
}
That is it. And you know, now that I lay it all out I can see what you mean. It is a lot of work just to update a reference. Perhaps the Service code can be made more DRY and reused for different relationship types, but I don't see how to get away from writing lots of indexes, one per referenced type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With