Building repositories for Azure Mobile Apps with ASP.NET 6
Over the last four days, I’ve delved into how to build a data sync service using Azure Mobile Apps on ASP.NET Core 6. I’ve covered the template, authentication, authorization, and logging. The basic setup is really good when you have a 1:1 relationship between the table and the DTO and you don’t need to do anything special, like support a non-EF Core ORM, or integrate real-time alerting. There are all sorts of reasons you don’t want to use the standard repository.
So you get to write your own.
Introducing ITableData and IRepository
Whenever I wrote a web API for a database service in the past, it inevitably started with a repository pattern. Not unsurprising, then, Azure Mobile Apps uses a repository pattern. It starts with a pair of interfaces:
ITableData
provides additional properties that you must implement in your DTO.IRepository<T>
provides the basic functionality for a repository for a specific DTO.
You don’t need to make your repository generic (even though all of the standard repositories are generics - they are general purpose, so they need to work with any DTO).
When you are writing a new repository, you may have to write a new abstract class to implement ITableData
as well. You will always have to write a class that implements IRepository<T>
. Let’s take a look at IRepository<T>
(stripped of the comments in the code):
It’s not a complex interface. In fact, it suspiciously simple. There are some things you must be aware of:
- The
Id
field inITableData
is a globally unique string within the table, and that the user can set theId
to a known value when creating the record. If you are translating theId
to a long, for example, it’s best to create a new property for it. - You need to be careful to ensure that the entities (identified by
TEntity
throughout) are “disconnected” - that is, changes to the entity after the call is completed do not affect the underlying database. - You need to ensure you throw
ConflictException<TEntity>
if the version (if not null) does not match. - The
AsQueryable()
method returns the complete dataset if the queryable is executed. The service adds onto this to do all the OData stuff it needs for filtering, ordering, and paging. - You are responsible for updating the
UpdatedAt
andVersion
properties in the DTO before storage. Your database storage layer (like EFCore) may do this for you. - You don’t need to worry about soft-delete, filtering, ordering, and paging. This is handled for you.
An example
Let’s look at an example. Let’s say you have a set of documents, and the document list is large. You want to allow users to subscribe to documents and they then get the updates to those documents. However, maintaining a list on the client is not feasible - there are too many of them to list them all and synchronize each one. Instead, you create a new subscriptions field that just has the UserId of the user and the DocumentId of the record. Something like this:
You can use a regular Entity Framework Core table to support the Subscription
table:
We’ve seen this pattern before in our earlier work. The repository for the Document
is a little different though. We want to combine two different tables using EF Core. Here is the updated DTO:
The DTO is the same as the Document
model, but I’ve added a new computed property.
The controller will look like this:
The DocumentTableRepository
doesn’t exist yet.
Implementing the DocumentTableRepository
Since both of my tables are in EF Core, I’m using the regular EntityTableData
as the basis for my DTO as well. Let’s look at the basics:
I haven’t implemented the actual methods yet. Let’s consider all of the mutation methods. We don’t mutate the DTO - we mutate the underlying document, so we have to create a Document
from a DocumentDTO
. Conveniently, this also “disconnects” the document from our input nicely:
I’ve written similar helpers for other parts. For example, my DocumentDTO
has a constructor for a Document
so I can convert back and all I need to do is fill in the IsSubscribed
property. I can now write the mutation methods basic on the Entity Framework Core ones. So, for example, here is the ReplaceAsync
method:
It’s worth figuring out what is happening here. However, the main change from the standard version to this one is that I change the provided entity into a document prior to storing it. The rest is the same as the regular code. I’ve left out the Disconnect
and LookupAsync
code that is a part of the standard codebase because it is the same.
ReadAsync and AsQueryable
The above mechanism is similar for the create and delete functionality. However, ReadAsync
and AsQueryable
are different because I need to combine two different tables. Let’s start with AsQueryable
:
This is probably not the most efficient way to do this in LINQ. I’m returning a queryable of the list of documents, but augmented with the IsSubscribed
property, which is found if there is a record for the user ID and document ID in the subscriptions table. Note that I don’t say “this is subscribed” or “this is not subscribed” within the queryable return value - I just return the entire data set. This will allow the mobile app to have a huge list and toggle between the “subscribed documents” and “all documents” views.
The ReadAsync
method has the same logic for generating the IsSubscribed
property:
Now, I’ve left off a lot of the auxiliary code in this, but it should be fairly easy to follow along and fill in the gaps.
Next steps
You should definitely take a look at the two implementations of the table repository I have written:
You will see that, for all that they do, they are actually quite simple to follow. My own experience is that table repositories should be straight forward. We aren’t doing a lot of work in there - just storing and reading data.
If you find you can’t do what you need without significant complexity in your table repository, perhaps Azure Mobile Apps is the wrong library. Azure Mobile Apps is an awesome and simple way to spin up an offline-aware REST endpoint for your data, but doesn’t solve every problem with data synchronization.
Let me know how you use Azure Mobile Apps on the discussion boards. I’d love to hear what you are doing!
Leave a comment