Experiments with Entity Framework Core and an ASP.NET Core API

The article shows some of the ways in which Entity Framework Core can be used together with ASP.NET Core. Both packages run on dotnet which makes it possible to run on Linux, Mac or Windows operating systems. The Entity Framework providers are implemented in separate data access projects using onion architecture.

Code: https://github.com/damienbod/AspNetCoreMultipleProject

Posts in this series:

2021-08-29: Updated to ASP.NET Core 5.0

2020-01-10: Updated to ASP.NET Core 3.1
2018-12-05: Updated to ASP.NET Core 2.2 and EF Core 2.2
2018-06-16: Updated to ASP.NET Core 2.1, EF Core 2.1
2017-08-16: Updated to ASP.NET Core 2.0, EF Core 2.0
2017-02-10: Updated to VS2017 and msbuild
2016-12-01: Updated to ASP.NET Core 1.1
2016-06-29: Updated to ASP.NET Core RTM
2016-05-17: Updated to ASP.NET Core RC2 dotnet

Multiple projects using Onion architecture

Onion architecture is used in this example. The ASP.NET Core MVC project is the outer ring and provides the infrastructure. The MVC project depends on the DomainModel project. The MVC project uses the IoC to connect a data access provider implementation to the IDataAccessProvider interface. No direct references to the data access implementations exist, only to the DomainModel. No dependencies exist to the SQLite or MS SQL Server database. This is really useful. I could change the Provider to any other data access layer, for example MongoDB without much effort. The business logic will remain unchanged.

EF7_ASP_NET5_MVC_6_02

Extra layers could also be added to the application as long as each layer only has dependencies to layers towards the center of the onion. For example, I could use view model DTOs instead of using the EF POCO entities directly in the MVC 6 API service, or maybe application services if required. No dependencies exist to the providers, except in the outer layer.

The providers are added in the csproj file

  <ItemGroup>
    <ProjectReference Include="..\DataAccessMsSqlServerProvider\DataAccessMsSqlServerProvider.csproj" />
    <ProjectReference Include="..\DataAccessMySqlProvider\DataAccessMySqlProvider.csproj" />
    <ProjectReference Include="..\DataAccessPostgreSqlProvider\DataAccessPostgreSqlProvider.csproj" />
    <ProjectReference Include="..\DataAccessSqliteProvider\DataAccessSqliteProvider.csproj" />
    <ProjectReference Include="..\DomainModel\DomainModel.csproj" />
  </ItemGroup>

And used in the Startup class.

public void ConfigureServices(IServiceCollection services)
{
	// Use a SQLite database
	var sqlConnectionString = Configuration.GetConnectionString("DataAccessSqliteProvider");

	services.AddDbContext<DomainModelSqliteContext>(options =>
		options.UseSqlite(
			sqlConnectionString,
			b => b.MigrationsAssembly("AspNetCoreMultipleProject")
		)
	);

	services.AddScoped<IDataAccessProvider, DataAccessSqliteProvider.DataAccessSqliteProvider>();

	//Use a MS SQL Server database
	//var sqlConnectionString = Configuration.GetConnectionString("DataAccessMsSqlServerProvider");

	//services.AddDbContext<DomainModelMsSqlServerContext>(options =>
	//    options.UseSqlServer(
	//        sqlConnectionString,
	//        b => b.MigrationsAssembly("AspNetCoreMultipleProject")
	//    )
	//);

	//services.AddScoped<IDataAccessProvider, DataAccessMsSqlServerProvider.DataAccessMsSqlServerProvider>();


	services.AddScoped<BusinessProvider>();

	services.AddControllers()
	  .AddNewtonsoftJson(options =>
	  {
		  options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
	  });

	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
		{
			Version = "v1",
			Title = "API",
		});
	});
}

EF-Core Migrations and configuration

To use Entity Framework Core migrations, start the command line and use the dotnet ef command from Entity Framework. Usually the connection string is stored in a config file. Only one configuration file is used for all libraries. This file is defined in the parent project, so the MigrationsAssembly needs to be defined in the AddDbContext options.

services.AddDbContext<DomainModelSqliteContext>(options =>
	options.UseSqlite(
		sqlConnectionString,
		b => b.MigrationsAssembly("AspNetCoreMultipleProject")
	)
);

Now the project can be used for the migrations. The ef command can be used to create the scripts and run the produced scripts in the database.


dotnet restore

dotnet ef migrations add sqliteMigration --context DomainModelSqliteContext

dotnet ef database update --context DomainModelSqliteContext

-or-

Add-Migration "sqliteMigration" -c DomainModelSqliteContext

Update-Database -c DomainModelSqliteContext

EF Core Shadow Properties

Shadow Properties is a nice feature from Entity Framework Core which allows you to add fields in the database table which are not part of your model.

A shadow property can be added to the Entity in the OnModelCreating method of a DBContext inplementation using the Property method.

protected override void OnModelCreating(ModelBuilder builder)
{
	builder.Entity<DataEventRecord>().HasKey(m => m.DataEventRecordId);
	builder.Entity<SourceInfo>().HasKey(m => m.SourceInfoId);

	// shadow properties
	builder.Entity<DataEventRecord>().Property<DateTime>("UpdatedTimestamp");
	builder.Entity<SourceInfo>().Property<DateTime>("UpdatedTimestamp");

	base.OnModelCreating(builder);
}

Here’s a model class

public class SourceInfo
{
	[Key]
	public long SourceInfoId { get; set; }
	public string Name { get; set; }
	public string Description { get; set; }
	public DateTime Timestamp { get; set; }
	public List<DataEventRecord> DataEventRecords { get; set; }
}

And the produced table in SQLite. You can see that the UpdatedTimestamp field has been added.

efcoreSQLiteMigratios_01

This can be used and set in the DBContext implementation. Here is an example setting the UpdatedTimestamp in the SaveChanges method.

public override int SaveChanges()
{
	ChangeTracker.DetectChanges();

	updateUpdatedProperty<SourceInfo>();
	updateUpdatedProperty<DataEventRecord>();

	return base.SaveChanges();
}

private void updateUpdatedProperty<T>() where T : class
{
	var modifiedSourceInfo =
		ChangeTracker.Entries<T>()
			.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);

	foreach (var entry in modifiedSourceInfo)
	{
		entry.Property("UpdatedTimestamp").CurrentValue = DateTime.UtcNow;
	}
}

This property can also be used in Linq requests:

public async Task<List<DataEventRecord>> GetDataEventRecords()
{
	// Using the shadow property EF.Property<DateTime>(dataEventRecord)
	return await _context.DataEventRecords
		.Include(s => s.SourceInfo)
		.OrderByDescending(dataEventRecord => EF.Property<DateTime>(dataEventRecord, "UpdatedTimestamp"))
		.ToListAsync();
}

Another example for shadow properties can be found towards the end of this video tutorial: https://channel9.msdn.com/Events/ASPNET-Events/ASPNET-Fall-Sessions/Entity-Framework-7

EF Core Include and Json Serialization

By using the Include statement, child entities can be returned together with the parent entities in one request. This can be implemented using the Include extension:

public async Task<List<SourceInfo>> GetSourceInfos(bool withChildren)
{
	// Using the shadow property EF.Property<DateTime>(srcInfo)
	if (withChildren)
	{
		return await _context.SourceInfos
			.Include(s => s.DataEventRecords)
			.OrderByDescending(srcInfo => EF.Property<DateTime>(srcInfo, "UpdatedTimestamp"))
			.ToListAsync();
	}

	return await _context.SourceInfos
		.OrderByDescending(srcInfo => EF.Property<DateTime>(srcInfo, "UpdatedTimestamp"))
		.ToListAsync();
}

If returning child parent entities directly in a Web API controller as JSON, you might possible get a circular dependency JSON serialization exception depending on your model. To prevent this, the ReferenceLoopHandling.Ignore needs to be configured in the default JsonOutputFormatter. This can be set in the Startup class in the ConfigureServices method.

services.AddControllers()
  .AddNewtonsoftJson(options =>
  {
	  options.SerializerSettings
		.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
  });

Testing with Fiddler

Here are some examples how you can test the application.

DataEventRecord with an existing SourceInfo

{
  "DataEventRecordId":0,
  "Name":"Funny data more",
  "Description":"no",
  "Timestamp":"2015-12-27T08:31:35Z",
  "SourceInfoId": 1
}

Or with a new sourceInfo.

{
  "DataEventRecordId":0,
  "Name":"Funny data more",
  "Description":"no",
  "Timestamp":"2015-12-27T08:31:35Z",
  "SourceInfo":
  { 
    "SourceInfoId":0,
    "Name":"Beauty",
    "Description":"first Source",
    "Timestamp":"2015-12-23T08:31:35+01:00",
  }, 
  "SourceInfoId": 0
}

Get all SourceInfos

http://localhost:5000/api/dataeventrecords/SourceInfos

Using Entity Framework Core together with ASP.NET Core MVC, it is really easy to create a API back-end for your application, if it fits your architecture, requirements. The two packages work really good together.

Links:

https://docs.microsoft.com/en-us/ef/core/

https://channel9.msdn.com/Events/ASPNET-Events/ASPNET-Fall-Sessions/Entity-Framework-7

http://ef.readthedocs.org/en/latest/modeling/shadow-properties.html

Step by Step implementing Onion architecture in ASP.NET MVC Application

http://jeffreypalermo.com/blog/the-onion-architecture-part-1/

ASP.NET Core 2.0 with SQLite and Entity Framework Core

ASP.NET Core 3.1 MVC File Upload with MS SQL SERVER FileTable

http://www.bricelam.net/

https://github.com/dotnetcurry/building-asp.net-mvc-6-apps

http://weblogs.asp.net/jeff/ef7-rc-navigation-properties-and-lazy-loading

https://msdn.microsoft.com/magazine/mt614250

10 comments

  1. […] Experiments with Entity Framework 7 and ASP.NET 5 MVC 6 – Damien Bod […]

  2. […] Experiments with Entity Framework 7 and ASP.NET 5 MVC 6 […]

  3. […] Experiments with Entity Framework 7 and ASp.NET MVC 6 by Damien Bod. […]

  4. Hey Damien, thanks for the great article.

    Quick question…you mention that the MVC 6 depends on the DomainModel project.
    When looking at the Startup.cs and the Controller.cs I see the following:
    using DomainModel;

    What I’m not understanding or seeing is how/where is that dependency configured?
    When opening the project.json of the AspNet5MultipleProject, I *do not* see any reference to the DomainModel.

    If no reference exists, then why are we capable of using DomainModel;

    I guess I’m not seeing it…

  5. […] Experiments with Entity Framework 7 and ASP.NET MVC 6 : Damien Bod 는 ASP.NET MVC 6와 Entity Framework 7을 함께 사용한 경험을 공유했습니다. […]

  6. […] Experiments with Entity Framework 7 and ASP.NET 5 MVC 6 // weblogs.asp.net/aspnet-team […]

  7. […] Un projet ASP.NET Core MVC et Entity Framework Core. […]

  8. In case anyone runs into this, the following will save a lot of headaches:

    .Include( ) not returning relationships (from principal table to dependent table), or only returning one row of data, FIX HERE:

    http://stackoverflow.com/questions/43127957/fix-include-not-working-in-net-core-include-on-relationship-only-retur

    Also, in Startup.cs make sure you have this at the top:

    using Microsoft.EntityFrameworkCore;
    using Newtonsoft.Json;
    using Project_Name_Here.Models;

  9. […] Source: Experiments with Entity Framework Core and ASP.NET Core MVC | Software Engineering […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.