I am new to .NET and Azure, and am trying to create a simple Web API, to help me learn. I have two collections of DocumentDB documents. The documents in each collection are defined as follows:
public class Log
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "studentName")]
public string StudentName { get; set; }
[JsonProperty(PropertyName = "assignment")]
public string Assignment { get; set; }
[JsonProperty(PropertyName = "dueDate")]
public DateTime DueDate { get; set; }
[JsonProperty(PropertyName = "goal")]
public string Goal { get; set; }
[JsonProperty(PropertyName = "reflection")]
public string Reflection { get; set; }
[JsonProperty(PropertyName = "sessions")]
public List<Session> Sessions { get; set; }
}
public class Session
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "date")]
public DateTime Date { get; set; }
[JsonProperty(PropertyName = "duration")]
public TimeSpan Duration { get; set; }
[JsonProperty(PropertyName = "comment")]
public string Comment { get; set; }
}
Here is an example of what I have for the Log documents:
{
"id": "2",
"studentName": "Joe student",
"assignment": "Web APIs",
"dueDate": "0001-01-01T00:00:00",
"goal": "Keep mistakes to a minimum.",
"reflection": "I'm getting there.",
"sessions": [ ]
}
Here is an example of what I have for the Session documents:
{
"id": "7",
"date": "2015-04-26T00:00:00",
"duration": "00:30:00",
"comment": "Practiced for 30 minutes"
}
I would like to relate Sessions with Logs such that each Log may have several sessions, but each session will be related to a single Log, and also a single Student. I think I want the sessions as separate documents, because I may want to get a list of Sessions by either Student, or Log. There would be Student documents as well. I am struggling to add Sessions to a Log, without simply duplicating the session within the log. I would Like to get something like this, which lists the id's of related sessions:
{
"id": "2",
"studentName": "Joe student",
"assignment": "Web APIs",
"dueDate": "0001-01-01T00:00:00",
"goal": "Keep mistakes to a minimum.",
"reflection": "I'm getting there.",
"sessions": [1, 2, 7, 19, 22]
}
I would then like to display the log, by replacing the ids with the content of the actual documents.
Here are some related working code snippets ( Not necessarily correct) to show you my design structure:
public class PracticeLogRepository : DocumentDB
{
// ********** Logs section ********** //
private PracticeSessionRepository _sessionsRepository;
// specifies the database and document collection used by the repository
public PracticeLogRepository() : base("Project3", "Logs") { }
// Gets a list of practice Logs
public Task<List<Practice.Log>> GetLogsAsync()
{
return Task<List<Practice.Log>>.Run(() => Client
.CreateDocumentQuery<Practice.Log>(Collection.DocumentsLink)
.ToList());
}
// Gets the practice Log with the matching id
public Task<Practice.Log> GetLogAsync(string id)
{
return Task<Practice.Log>.Run(() => Client
.CreateDocumentQuery<Practice.Log>(Collection.DocumentsLink)
.Where(pl => pl.Id == id)
.AsEnumerable()
.FirstOrDefault());
}
...
public class PracticeSessionRepository : DocumentDB
{
// ********** Session section ********** //
// specifies the database and document collection used by the repository
public PracticeSessionRepository() : base("Project3", "Sessions") { }
// Gets a list of practice Sessions
public Task<List<Practice.Session>> GetSessionsAsync()
{
return Task<List<Practice.Session>>.Run(() => Client
.CreateDocumentQuery<Practice.Session>(Collection.DocumentsLink)
.ToList());
}
// Gets the practice Log with the matching id
public Task<Practice.Session> GetSessionAsync(string id)
{
return Task<Practice.Session>.Run(() => Client
.CreateDocumentQuery<Practice.Session>(Collection.DocumentsLink)
.Where(pl => pl.Id == id)
.AsEnumerable()
.FirstOrDefault());
}
...
public class LogController : ApiController
{
private PracticeLogRepository _logsRepository;
public LogController()
{
_logsRepository = new PracticeLogRepository();
}
// GET: api/Log
public async Task<IHttpActionResult> Get()
{
var logs = await _logsRepository.GetLogsAsync();
if (logs != null)
return Ok(logs);
return NotFound();
}
// Get: api/Log/{id}
public async Task<IHttpActionResult> Get(string id)
{
var log = await _logsRepository.GetLogAsync(id);
if (log != null)
return Ok(log);
return NotFound();
}
...
public class SessionController : ApiController
{
private PracticeSessionRepository _sessionsRepository;
public SessionController()
{
_sessionsRepository = new PracticeSessionRepository();
//_logsRepository = new PracticeLogRepository();
}
// GET: api/Session
public async Task<IHttpActionResult> Get()
{
var sessions = await _sessionsRepository.GetSessionsAsync();
if (sessions != null)
return Ok(sessions);
return NotFound();
}
// Get: api/Session/{id}
public async Task<IHttpActionResult> Get(string id)
{
var session = await _sessionsRepository.GetSessionAsync(id);
if (session != null)
return Ok(session);
return NotFound();
}
...
Since I am teaching myself, and am extremely new .NET and C#, and of course DocumentDB, I am struggling to code this. I would really appreciate a simple example of how I should create the Log document, and also how to update it by adding sessions.
If you need to see more of my code, just ask. I just don't want to over complicate this.
I'd like to make sure I'm understanding the question, here's a recap of my understanding:
You have a 1-to-many relationship between Log and Session.
You have a 1-to-1 relationship between Log and Student.
You'd like to query Sessions by either Student or Log.
You'd like to populate data from these relationships (e.g. get the session data)
Please keep in mind DocumentDB is a NoSQL database and does not support inter-document JOINs.
I'd recommend revisiting how you approach modeling your data - in particular how you represent relationships (e.g. whether to keep Sessions and Logs as separate documents).
The following article talks about modeling data in detail: http://azure.microsoft.com/en-us/documentation/articles/documentdb-modeling-data/
To summarize the key points of the article - I would pick the appropriate trade-off between normalizing (e.g. keeping Sessions and Logs as separate documents) and de-normalizing (e.g. keeping Sessions and Logs in the same document) given your application's use case. In general, I prefer de-normalizing when you have a read-heavy application and normalizing when you have a write-heavy application.
If you choose to normalize - you can simply make a follow-up request to get the session data for a log. Extra credit: You can even take this a step further and remove the need of having to make multiple network requests by writing a stored procedure.
If you choose to de-normalize, you can simply query for the entire document and everything will be populated automatically. The drawback is you may have to fan-out writes to multiple documents if you update session data.