Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I implement Denormalized References in RavenDB

Tags:

ravendb

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

like image 903
code mariner Avatar asked May 08 '12 09:05

code mariner


1 Answers

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.

like image 195
biofractal Avatar answered Nov 15 '22 19:11

biofractal