Thursday, 29 November 2018

Trackable Entities for EF Core! -Good sample

The idea behind my open source Trackable Entities project is quite simple: track changes to an object graph as you update, add and remove items, then send those changes to a back end service where they can be saved in a single transaction.  It’s an important thing to be able to do, because it’s difficult to wrap multiple round trips in a single transactionwithout holding locks for a long time.  On the other hand, you could break up related operations into multiple transactions, but then you lose the benefit of atomicity, which enables you to roll back all the changes in a transaction should one of them fail.
To get started with Trackable Entities for Entity Framework Core, download the NuGet package and check out the project repository.  You can also clone the sample applications and follow the instructions.

Brief Introduction

For example, you update an order with related details.  Some details are unchanged, while others are modified, added or removed.  All these changes should be atomic, that is, they should all succeed or none.  The problem with regard to Entity Framework is that, in the context of a web service, updates must be conducted in a disconnected manner, which means that you need to inform EF about which entities require updating and what kind of changes it needs to perform.  In the case of an order with multiple details that need to be updated all at once, there needs to be a way to communicate this information to the web service so that you can attach entities to a DbContext and individually set the state of each entity.  The way Trackable Entities accomplishes this is by means of a simple enum called TrackingState.
public enum TrackingState
{
Unchanged,
Added,
Modified,
Deleted
}
view rawTrackingState.cs hosted with ❤ by GitHub
To track changes, entities implement an ITrackable interface, which includes a ModifiedProperties property to support partial updates.
public interface ITrackable
{
TrackingState TrackingState { get; set; }
ICollection<string> ModifiedProperties { get; set; }
}
view rawITrackable.cs hosted with ❤ by GitHub
In addition, Trackable Entities provides an IMergeable interface to correlate updated entities so they can be merged back into the original object graph on the client.
public interface IMergeable
{
Guid EntityIdentifier { get; set; }
}
view rawIMergeable.cs hosted with ❤ by GitHub
Once entities have been marked as Added, Modified or Deleted, all you have to do is call the ApplyChanges extension method for DbContext.
// Inform EF about changes in an object graph
context.ApplyChanges(order);
// Persist all changes in a single transaction
context.SaveChanges();
view rawApplyChanges.cs hosted with ❤ by GitHub

Trackable Entities for EF Core

Because Trackable Entities is an extension of Entity Framework, it has only been available for the full .NET Framework running on Windows.  But now that EF has been ported to .NET Core and can run on Linux and MacOS, it’s time for Trackable Entities to come along for the ride!  This makes it possible to create a Web API with ASP.NET Core that can run both locally on a Mac and in a Docker container running on a Linux VM in the Cloud.  How cool is that?!
To get started you can either use Visual Studio for WindowsVisual Studio for Mac, or if you’re adventurous, Visual Studio Code.  It doesn’t matter which option you choose, because in the end your web app will run anywhere .NET Core will run.  In this blog post I’ll walk you through a demo using VS for Mac — just for fun.  But if you prefer, feel free to check out the sample I created using the “classic” version of Visual Studio on Windows.
Note: You’ll need to install the SDK for .NET Core 2.0 or higher.
Start by creating a new project in VS using the ASP.NET Core Web API project template.
aspnet-core-project.png
Then add a .NET Standard Class Library for server-side trackable entities.
net-standard-project.png
Add the TrackableEntities.Common.Core (pre-release) and System.ComponentModel.Annotations NuGet packages.
te-common-core.png
Add classes that implement ITrackable and IMergeable interfaces. You’ll also need to add a using directive for System.ComponentModel.DataAnnotations.Schema, so that you can decorate TrackingStateModifiedProperties and EntityIdentifier properties with a [NotMapped]attribute, to indicate these properties do not belong to the database schema.
public class Product : ITrackable, IMergeable
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int? CategoryId { get; set; }
public decimal? UnitPrice { get; set; }
public bool Discontinued { get; set; }
public byte[] RowVersion { get; set; }
public Category Category { get; set; }
[NotMapped]
public TrackingState TrackingState { get; set; }
[NotMapped]
public ICollection<string> ModifiedProperties { get; set; }
[NotMapped]
public Guid EntityIdentifier { get; set; }
}
view rawProduct.cs hosted with ❤ by GitHub

EF Core Migrations

Although it is possible to generate entities based on database tables (Database First), in this demo we’ll go the other way and start with entities that will be used to generate database tables and relationships (Code First).  Both approaches use the EF .NET Core CLI, which you install by adding the Microsoft.EntityFrameworkCore.Design package and manually editing the .csproj file for the Web API project to insert a DotNetCliToolReferenceand change the project target from netstandard2.0 to netcoreapp2.0 (which is required to run the EF CLI). You’ll also need to add a package for the EF provider you’re using, which in this case is Microsoft.EntityFrameworkCore.Sqlite, as well as a reference to the server-side entities project you created earlier.  Then run dotnet restore from the command line.  (Visual Studio does not yet automatically restore tooling packages.)  Here is what your .csproj file will then look like.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" />
<PackageReference Include="TrackableEntities.EF.Core" Version="1.0.0-beta" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TrackableEntities.Core.Sample.XPlat.Entities.WebApi\TrackableEntities.Core.Sample.XPlat.Entities.WebApi.csproj" />
</ItemGroup>
</Project>
view rawWebApi.csproj.xml hosted with ❤ by GitHub
Next, add a specific DbContext-derived class to the Web API project.
public class NorthwindSlimContext : DbContext
{
public NorthwindSlimContext(DbContextOptions<NorthwindSlimContext> options) : base(options) { }
public DbSet<Category> Categories { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderDetail> OrderDetails { get; set; }
public DbSet<Product> Products { get; set; }
}
Lastly, you’ll need to add a class that implements IDesignTimeDbContextFactory to create your context class with the appropriate options and connection string.
public class NorthwindSlimContextFactory : IDesignTimeDbContextFactory<NorthwindSlimContext>
{
public NorthwindSlimContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<NorthwindSlimContext>();
optionsBuilder.UseSqlite("Data Source=northwindslim.db");
return new NorthwindSlimContext(optionsBuilder.Options);
}
}
Now all you need to do to create a database from your entities is to execute the following two commands, after which a northwindslim.db file will appear in the project directory.
dotnet ef migrations add initial
dotnet ef database update

Configure Dependency Injection

To inject your EF context class into controllers, you’ll need to register it with ASP.NET Core’s dependency injection system.  Add code to the ConfigureServices method of the Startup class in which you call services.AddDbContext, passing options that include the provider and connection string.  You’ll also want to configure the JSON serializer to preserve references in order to accommodate cyclical references in object graphs.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(
options => options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.All);
services.AddDbContext<NorthwindSlimContext>(
options => options.UseSqlite("Data Source=northwindslim.db"));
}

Web API Controllers

Next add the TrackableEntities.EF.Core NuGet package to the Web API project.  Then you can add a controller to the project in which you use LINQ to execute queries against the database and return objects from your GET actions.
[Produces("application/json")]
[Route("api/Customer")]
public class CustomerController : Controller
{
private readonly NorthwindSlimContext _context;
public CustomerController(NorthwindSlimContext context)
{
_context = context;
}
// GET: api/Customer
[HttpGet]
public async Task<IActionResult> GetCustomers()
{
var customers = await _context.Customers
.ToListAsync();
return Ok(customers);
}
// GET: api/Customer/ALFKI
[HttpGet("{id}")]
public async Task<IActionResult> GetCustomer([FromRoute] string id)
{
var customer = await _context.Customers.SingleOrDefaultAsync(m => m.CustomerId == id);
if (customer == null)
return NotFound();
return Ok(customer);
}
}
Then to utilize Trackable Entities for persisting object graphs with changed entities, all you need to do is call context.ApplyChanges.
// PUT: api/Order
[HttpPut]
public async Task<IActionResult> PutOrder([FromBody] Order order)
{
// Apply changes to context
_context.ApplyChanges(order);
try
{
// Persist changes
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Orders.Any(o => o.OrderId == order.OrderId))
return NotFound();
throw;
}
// Populate reference properties
await _context.LoadRelatedEntitiesAsync(order);
// Reset tracking state to unchanged
_context.AcceptChanges(order);
// Return updated order
return Ok(order);
}
Note that after calling context.SaveChangesAsync() there is a line of code that calls context.LoadRelatedEntitiesAsync, passing the root entity.  This will traverse the object graph, loading reference properties so that entities returned to the client will have them populated.  For example, when updating an Order it isn’t necessary to send the entire Customer entity when all you have to do is set the order’s CustomerId property.  But the client will usually want to have the Customer property populated when it is returned from the service, which is what LoadRelatedEntitiesAsync does. Lastly, the call to context.AcceptChanges is there to reset TrackingState on each entity to Unchanged, so that the client can can have a fresh start before making additional changes.
You can now execute dotnet run on the command line and the Web API will start listening for requests on the default port 5000.
dotnet-run.png
You can then open a browser and navigate to an API endpoint to make sure you can retrieve entities.  You can also use a REST client such as Postman to make POST, PUT and DELETE requests.
customer-alfki.png

Trackable Entities on the Client

On the client side Trackable Entities provides change-tracking for .NET apps by means of a ChangeTrackingCollection.  You can create a .NET Core console app to which you can add the TrackableEntities.Client NuGet package. Even though this package was written for the full .NET Framework version 4.6.1, you can use it in a .NET Core app because both are compliant with .NET Standard 2.0.
Because client-side entities have a different set of concerns than server-side entities, firing events on property changes and using change tracking collections for navigation properties, you’ll probably want to generate client entities from the database schema you created earlier using the EF Core CLI tooling.  The easiest way to accomplish this is to use T4 templates.  Fortunately, Trackable Entities has a package for that — TrackableEntities.CodeTemplates.Client.Net45.  You’ll need to add it to a traditional .NET Class Library project in Visual Studio for Windows, so that you can add an ADO.NET Entity Data Model that will use it to generate the client entities.
You can then write client-side code that changes entities, gets only the changed entities, and sends them to your Web API service for applying changes and saving them to the database.  See the sample app’s repository for the complete code.
To run the console app, keep the Web API running and execute dotnet run.  Follow the prompts to retrieve, add, update and delete entities at various places in the object graph.
console-client.png
The cool thing is that all this is happening on a Mac, and there’s nothing to stop you from deploying it to a container service such as Amazon EC2Microsoft Azure or Google Container Engine.  With Trackable Entities and EF Core the future is now.g

No comments:

Post a Comment

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...