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:
ITableDataprovides 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:
ITableDatais a globally unique string within the table, and that the user can set the
Idto a known value when creating the record. If you are translating the
Idto 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
TEntitythroughout) 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.
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
Versionproperties 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.
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
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:
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
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
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,
AsQueryable are different because I need to combine two different tables. Let’s start with
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.
ReadAsync method has the same logic for generating the
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.
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!