ASP.NET Core OData Part 1

Update: see the second post here.

Introduction

OData is an open standard for making an object-oriented domain model available as an HTTP REST interface.In a nutshell, it provides a specification for returning domain models as result of HTTP requests, querying them over the URL and even creating functions and actions over the domain model. In what .NET Core is concerned, it is a way by which you can expose your Entity Framework Core – or any other ORM that has a LINQ interface – to the web, without writing too much boilerplate code, as an ASP.NET Core Web API.

It is surprising that not many people know about OData, also, there are some caveats to it and not much documentation on using it with ASP.NET Core, so I decided to write a few posts on the subject. Here is the first!

Setting Up

Let’s pretend you have a domain model with just two classes, Parent and Child:

image

We should also have an Entity Framework Core context that exposes them:

public class ParentChildContext : DbContext

{

public ParentChildContext(DbContextOptions options) : base(options) { }

public DbSet<Parent> Parents { get; set; }

public DbSet<Child> Children { get; set; }

}

I won’t go into details as to explain this, I’m pretty sure you all know about Entity Framework Core contexts! Just make sure you add a reference to the Microsoft.EntityFrameworkCore NuGet package, or, if you’re using SQL Server, Microsoft.EntityFrameworkCore.SqlServer. Now register your context to the DI framework, in the ConfigureServices method:

services.AddDbContext<ParentChildContext>(options =>

{

options.UseSqlServer(“<connection string>”);

});

In your ASP.NET Core’s project you need to add a reference to the Microsoft.AspNetCore.OData NuGet package. This includes the server-side implementation of OData version 4 for ASP.NET Core.

You need to add its required services in the ConfigureServices method:

services.AddOData();

This registers the services, but now we need to add an endpoint for an actual domain model. ASP.NET Core OData now supports endpoint routing, so everything can be done smoothly:

app.UseEndpoints(endpoints =>

{ endpoints.MapODataRoute(“odata”, “odata”, GetEdmModel(app.ApplicationServices)); });

We registered this endpoint with name odata (first parameter) and also with the same prefix (second parameter), you can happily change this. As you can see, in this route, we are returning an EDM Data Model. We build one as this:

private static IEdmModel GetEdmModel(IServiceProvider serviceProvider)

{

var builder = new ODataConventionModelBuilder(serviceProvider);

builder.EntitySet<Parent>(“Parents”);

builder.EntitySet<Child>(“Children”);

return builder.GetEdmModel();

}

One thing that you’ll notice is that this is a conventions-based model builder, ODataConventionModelBuilder. this one takes care of some things for you, such as inferring the id property for each entity, that’s why you don’t have to explicitly state it. If your entity has an id property with a funny name, other than Id or EntityId, then you will need to specify it:

builder

.EntitySet<Parent>(“Parents”)

.EntityType

.HasKey(x => x.Id);

There is no need to specify the other properties, because they are all inferred automatically too from the generic parameter.

Do keep in mind the entity set name that you give, it is common to have a pluralized form of the entity name, but it is not required.

Now we need to create a controller that exposes this. At the very least, we will need two methods:

[ODataRoutePrefix(“Parents”)]

public class ParentController : ODataController

{

private ParentChildContext _ctx;

public ParentController(ParentChildContext ctx)

{

this._ctx = ctx;

}

[ODataRoute]

public IQueryable<Parent> Get()

{

return this._ctx.Parents.AsQueryable();

}

[ODataRoute(“{id}”)]

public Parent Get([FromODataUri] int id)

{

return this._ctx.Parents.Find(id);

}

}

This controller is specific to the Parents entity set, so, if you wish, you need to have another one for the Children. This is specified in the [ODataRoutePrefix] attribute.

Also notice how we are returning IQueryable<Parent> from the Get action method that does not take parameters and a single Parent from the other. You can also declare IActionResult or ActionResult<T>:

[ODataRoute]

public IActionResult Get()

{

return this.Ok(this._ctx.Parents.AsQueryable());

}

//alternative

[ODataRoute]

public ActionResult<IQueryable<Parent>> Get()

{

return this.Ok(this._ctx.Parents.AsQueryable());

}

The latter, the one that uses ActionResult<T>, has some advantages, which you can read about here.

And, of course, all of the methods can be made asynchronous too:

[ODataRoute]

public async Task<IQueryable<Parent>> Get()

{

return await this._ctx.Parents.ToListAsync();

}

[ODataRoute(“{id}”)]

public async Task<Parent> Get([FromODataUri] int id)

{

return await this._ctx.Parents.FindAsync(id);

}

We will see a problem with the implementation of the first method in the next post.

Now, the first method, the one without parameters, returns all of the entities in the database, and the second, as one would expect, returns possibly one from its id property.

Conclusion

We’re all done here, so we can now access the endpoints we just created, try to navigate to the following URLs:

  • /odata: returns information about the exposed entity sets (Parents and Children)

image

  • /odata/parents –> ParentController.Get(): returns all records for the Parent entity
  • /odata/parents(1) –> ParentController.Get(1): returns possibly one record, if it exists in the database for the given primary key of the Parent entity

This is the very basics, in the future posts we will explore other options, such as querying over the URL and creating functions and actions.

References

You can find more information in the following pages:

Accessing the HttpContext from a DbContext

Sometimes it might be necessary to access the current HttpContext from inside a DbContext, namely, from inside the OnConfiguring or OnModelCreating methods. Why? Well, for once, because of multitenancy: we may want to be able to decide the connection string to use based on the requesting or the host’s domain, the current user or some other request parameter. Here the internal dependency injection (Instance) can’t help, because it cannot be used inside these methods.

The only option we have is to inject the IHttpContextAccessor class through our DbContext class’ constructor, and, from it, get hold of the HttpContext.

First we need to register the IHttpContextAccessor service in ConfigureServices:

services.AddHttpContextAccessor();

We also need to register our context for dependency injection:

services.AddDbContext<MyContext>();

A context registered this way needs to have a special constructor that has a parameter of type DbContextOptions (or DbContextOptions<MyContext>), in our case, we just need to add an extra parameter of type IHttpContextAccessor:

public class MyContext : DbContext
{
    public MyContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor) : base(options)
    {
        this.HttpContextAccessor = httpContextAccessor;
    }

    protected IHttpContextAccessor HttpContextAccessor { get; }
}

Finally, when you need to access the HttpContext, you just need to retrieve it from the IHttpContextAccessor:

protected internal override void OnConfiguring(DbContextOptionsBuilder builder)
{
    var httpContext = this.HttpContextAccessor.HttpContext;
    var tenantService = httpContext.RequestServices.GetService<ITenantService>();
    var connectionString = tenantService.GetConnectionString(httpContext);

    builder.UseSqlServer(connectionString);

    base.OnConfiguring(builder);
}

Here the hypothetical ITenantService provides a method GetConnectionString that returns a connection string for the current HttpContext, and we use it with the SQL Server provider.

Hope you find this useful! Winking smile

Succinctly Series Readers Awards

image

My e-book Entity Framework Core Succinctly was silver winner on the Succinctly Series Readers Awards!

Many thanks to all who voted for it! And congratulations to Joseph D. Booth for winning the gold award for his Natural Language Processing Succinctly and to Alessando Del Sole (@progalex) for his bronze award for Xamarin.Forms Succinctly!

https://www.syncfusion.com/awards/succinctlyseries/2018succinctlyreadersawards

Succinctly Books Index

This page lists all the books I wrote or reviewed for Syncfusion’s Succinctly series.

Books I wrote:

Books I reviewed:

Entity Framework Core Succinctly Released

My latest (and fifth) ebook for Syncfusion’s Succinctly collection is out: Entity Framework Core Succinctly! It covers Entity Framework Core 2.0 and you can download it for free – need to register first, though.

This book is inspired by my previous one on Entity Framework Code First, but quite a lot has changed.

Huge thanks to Jeff Boenig for the technical review and to Hillary Bowling, Tres Watkins and Jacqueline Bieringer of Syncfusion for all their support.

Entity Framework Core Pitfalls: No TransactionScope Support

Entity Framework Core, as of version 2.0, does not support enlisting in ambient transactions, like those provided by TransactionScope. It is being tracked by issue #9561. This is by design and is related to a limitation of System.Data.SqlClient, which will be fixed when .NET Core 2.1 comes out. The ticket for SqlClient is #3114.

I already talked about the hard relation between Entity Framework and TransactionScope before. This time, however, with EF Core 2.1, it seems that it will be fixed, and this is a good thing. For now, however, you will have to roll out your own transaction management strategy which includes starting your own transactions explicitly and either committing them or rolling them back at the end. Please refer to the Microsoft documentation available at https://docs.microsoft.com/en-us/ef/core/saving/transactions.

Soft Deletes with Entity Framework Core 2 – Part 2

My previous post explained how to setup soft deletes for queries automatically. This time, I’m going to talk about the other part: actually replacing a delete for an update that sets the is deleted column.

The key here is to intercept the SaveChanges method, find out all entities that are ISoftDeletable and are marked for deletion and then set their IsDeleted property and change their state to be modified instead of deleted:

public override int SaveChanges()
{
foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State == EntityState.Deleted))
{
if (entry.Entity is ISoftDeletable)
{
entry.Property(_isDeletedProperty).CurrentValue = true;
entry.State = EntityState.Modified;
}
}

return base.SaveChanges();
}
This way Entity Framework will know what to do and just update them instead of deleting them. Next time, they won’t be loaded as they have been marked as deleted.

Soft Deletes with Entity Framework Core 2 – Part 1

Entity Framework Core 2, already covered here, includes a cool feature called global filters. By leveraging global filters, we can apply restrictions automatically to entities, either loaded directly or through a collection reference. If we add this to shadow properties (in the case of relational databases, columns that exist in a table but not on the POCO model), we can do pretty cool stuff.

In this example, I am going to create a soft delete global filter to all entities in the model that implement a marker interface ISoftDeletable.

public interface ISoftDeletable
{
}
We just need to override the DbContext’s OnModelCreating method to automatically scan all known entities to see which implement this interface and then create the restriction automatically:
private const string _isDeletedProperty = "IsDeleted";
private static readonly MethodInfo _propertyMethod = typeof(EF).GetMethod(nameof(EF.Property), BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(typeof(bool));

private static LambdaExpression GetIsDeletedRestriction(Type type)
{
var parm = Expression.Parameter(type, "it");
var prop = Expression.Call(_propertyMethod, parm, Expression.Constant(_isDeletedProperty));
var condition = Expression.MakeBinary(ExpressionType.Equal, prop, Expression.Constant(false));
var lambda = Expression.Lambda(condition, parm);
return lambda;
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
if (typeof(ISoftDeletable).IsAssignableFrom(entity.ClrType) == true)
{
entity.AddProperty(_isDeletedProperty, typeof(bool));

modelBuilder
.Entity(entity.ClrType)
.HasQueryFilter(GetIsDeletedRestriction(entity.ClrType));
}
}

base.OnModelCreating(modelBuilder);
}
So, for each entity known from the context we add a shadow property called IsDeleted of type bool. Of course, needless to say, it must also exist on the database. The reason I’m making it a shadow property is to avoid people tampering with the entities, by setting or unsetting its value. This way, the restriction is always performed and it is invisible to us. After we create the property, we add a restriction to the entity’s type.
Simple, don’t you think? This way, if you want to enable or disable it for a number of entities, just have them implement the ISoftDeletable interface.

Entity Framework Core Extensions: Getting Primary Keys and Dirty Properties

Two simple hacks: finding the primary key values:

public static IDictionary<string, object> GetKeys(this DbContext ctx, object entity)
{
if (ctx == null)
{
throw new ArgumentNullException(nameof(ctx));
}

if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}

var entry = ctx.Entry(entity);
var primaryKey = entry.Metadata.FindPrimaryKey();
var keys = primaryKey.Properties.ToDictionary(x => x.Name, x => x.PropertyInfo.GetValue(entity));

return keys;
}

This returns a dictionary because some entities may have composite primary keys.

As for getting the dirty (modified) properties’ names for an entity:

public static IEnumerable<string> GetDirtyProperties(this DbContext ctx, object entity)
{
if (ctx == null)
{
throw new ArgumentNullException(nameof(ctx));
}

if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}

var entry = ctx.Entry(entity);
var originalValues = entry.OriginalValues;
var currentValues = entry.CurrentValues;

foreach (var prop in originalValues.Properties)
{
if (object.Equals(originalValues[prop.Name], currentValues[prop.Name]) == false)
{
yield return prop.Name;
}
}
}
Picking on the last one, if we wish to reset an entity to it’s original values:
public static void Reset(this DbContext ctx, object entity)
{
if (ctx == null)
{
throw new ArgumentNullException(nameof(ctx));
}

if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}

var entry = ctx.Entry(entity);
var originalValues = entry.OriginalValues;
var currentValues = entry.CurrentValues;

foreach (var prop in originalValues.Properties)
{
currentValues[prop.Name] = originalValues[prop.Name];
}

entry.State = EntityState.Unchanged;
}

Mind you, this one will not reset collections or references, just plain properties.

Hope it helps!

This is the eight post in a series of posts about bringing the features that were present in Entity Framework pre-Core into EF Core. The others are:

  • Part 1: Introduction, Find, Getting an Entity’s Id Programmatically, Reload, Local, Evict

  • Part 2: Explicit Loading

  • Part 3: Validations

  • Part 4: Conventions

  • Part 5: Getting the SQL for a Query

  • Part 6: Lazy Loading

  • Part 7: Entity Configuration in Mapping Classes

This time I’m going to talk about the possibility to log the generated SQL to the output. In the past, we could do it by assigning a writer to DbContext.Database.Log:

ctx.Database.Log = Console.Log;
This would cause all SQL statements to be sent to the console. We can do something similar in EF Core, by leveraging the logging framework:
var loggerFactory = new LoggerFactory()
.AddDebug((categoryName, logLevel) => (logLevel == LogLevel.Information) && (categoryName == DbLoggerCategory.Database.Command.Name))
.AddConsole((categoryName, logLevel) => (logLevel == LogLevel.Information) && (categoryName == DbLoggerCategory.Database.Command.Name));

var optionsBuilder = new DbContextOptionsBuilder<MyContext>()
.UseLoggerFactory(loggerFactory)
.UseSqlServer(@"<connectionString>");

var ctx = new MyContext(optionsBuilder.Options);
We are creating a new logger factory and populating it with the console and debug providers. You will need to add the Microsoft.Extensions.Logging.Console and Microsoft.Extensions.Logging.Debug NuGet packages (you don’t need both). For each of these providers, we are filtering the output by LogLevel.Information and category DbLoggerCategory.Database.Command.Name (“Microsoft.EntityFrameworkCore.Database.Command”), which are the values used when outputting SQL.

Then, we are creating a DbContextOptionsBuilder and in it we are replacing the default logger factory with our own. We then use it to create an option which we pass to our DbContext’s constructor, or we could do the same in the OnConfiguring method.

The output will look something like this:

SELECT [p].[ProjectId], [p].[CreatedAt], [p].[CreatedBy], [p].[CustomerId], [p].[Description], [p].[End], [p].[Name], [p].[Start], [p].[UpdatedAt], [p].[UpdatedBy]
FROM [Projects] AS [p]

Hope this helps! Winking smile