Porting to .NET Core

Home / Porting to .NET Core

I spent the better part of my development time this weekend porting various code from the .NET “full” framework to .NET Core. This included porting EntityFramework 6.1.3 code to EntityFramework Core 1.1.1. It was about as big of a pain as you would expect.


Things started out pretty well. In my current work solutions, I have (4) projects that constitute the basic infrastructure for an Angular 1.x application. These include a web project, web content project (scripts, css, etc), utilities project, and data access layer. Using these projects has made it really handy to have a simple template for starting an Angular 1.x application. These projects are also rolled into Nuget packages to make updating things painless.

To start my conversion, I focused on the utility project and data access layer. The data access layer depends on the utility project since the utility project has all sorts of helpers which include everything from generic Expression builders to string extensions.

The first thing that I found that is different is Reflection. You can’t do things like “typeof(Obj).IsEnum.” Rather than using a Type object directly to get information about whether it IsPrimitive and such, you have to use a TypeInfo object.

For example, this:

public static bool IsPrimitiveType(this Type t)
{
         var isPrimitive = t.IsPrimitive || t.IsValueType || t == typeof(string) || t == typeof(decimal);
         return isPrimitive;
}

Becomes this:

public static bool IsPrimitiveType(this Type t)
{
         var ti = t.GetTypeInfo();
         var isPrimitive = ti.IsPrimitive || ti.IsValueType || t == typeof(string) || t == typeof(decimal);
         return isPrimitive;
}

Caching has changed relatively dramatically. HttpRuntime.Cache is no more. Now, IMemoryCache, must be injected and used.

Old:


// Get
var myobjtocache = HttpRuntime.Cache[cacheKey] as MyObj;

// Store
HttpRuntime.Cache.Insert(cacheKey, myobjtocache, null, Cache.NoAbsoluteExpiration,
TimeSpan.FromSeconds(_cacheExpiration), CacheItemPriority.Normal, null);

New (where _cache is my injected IMemoryCache)

// Get
_cache.TryGetValue(cacheKey, out myobjtocache);

// Store
var cacheEntryOptions = new MemoryCacheEntryOptions()
    .SetAbsoluteExpiration(TimeSpan.FromSeconds(_cacheExpiration))
    .SetPriority(CacheItemPriority.Normal);

_cache.Set(cacheKey, myobjtocache, cacheEntryOptions);

The BinaryFormatter is gone from .NET Core, so my routines for DeepCloning had to be removed. I don’t have a quick and easy replacement for that yet.

Previously, I blogged about the changes with HttpContext and using IHttpContextAccessor to get the context, so I won’t dwell on that much here.

Those were the most memorable parts of converting my utility library. Porting the Data Access Layer was pretty involved. I’ve talked about this porting process a bit before.

Here are a few things right off the bat that have to be considered:

  • DbContextTransaction becomes IDbContextTransaction
  • ConstraintException is no longer available.
  • DbEntityValidationException, which I’ve mentioned before, is no longer available since DbContext no longer performs entity property validation.
  • TransactionalBehavior is no longer available.
  • CommmandInterceptors are no longer availableilable
  • DbContext.Database.ExecuteSqlCommand is no longer available
  • DbContext.Database.SqlQuery<T> is no longer available
  • ObjectContext and ObjectStateManager and no more

DbContext.Database.SqlQuery<T> has been replaced with DbSet<T>.FromSql(..). I actually think this is a good improvement. the FromSql command is actually a fluent API that allows adding additional LINQ filters/expresisons to the query. This is pretty nice. Previously, EF was pretty ridged in how it handled mapping raw SQL to a list of objects. In fact, it would not even honor your metadata.

Since raw queries, like executing a non-query or scalar, are no longer directly supported, we have to resort to ADO to accomplish this. Fortunately, it’s pretty straight forward.

As an example, I have a method in my Repository that will execute a non-query using “ExecuteSqlCommand.” It’s a pretty simple one-liner:

_dbContext.Database.ExecuteSqlCommand(commandText, parameters);

In EF Core, we have to create a connection, command, and execute the non-query:

using (var connection = _dbContext.Database.GetDbConnection())
{
    connection.Open();

    using (var command = connection.CreateCommand())
    {
        command.CommandText = commandText;
        command.ExecuteNonQuery();
    }
}

If we expect a return value:

// Add the output parameter
SqlParameter returnParam = new SqlParameter()
{
    ParameterName = "@returnValue",
    SqlDbType = SqlDbType.Int,
    Direction = ParameterDirection.Output
};

sqlParams.Add(returnParam);

using (var connection = _dbContext.Database.GetDbConnection())
{
    connection.Open();

    using (var command = connection.CreateCommand())
    {
        command.CommandText = executeStr;
        sqlParams.Select(x => { command.Parameters.Add(x); return x; }).ToList();
        command.ExecuteNonQuery();

        //Need to assign the value of the stored procedure's return to result  
        returnValue = (int)command.Parameters["@returnValue"].Value;
    }
}

I use all sorts of extensions and what not to do things like generate my own queries or perform other operations that necesitate retrieving the metadata for the database entity mappings. I found that EF Core actually is much improved in this regard. For example, retrieving all of the Database column names for each property of an entity is easy:

private Dictionary<string, string> GetColumnNames(Type type)
{
    var entityType = _dbContext.Model.FindEntityType(type);
    var dict = new Dictionary<string, string>();
    foreach (var propertyType in entityType.GetProperties())
    {
        var propertyName = propertyType.Name;
        var columnName = propertyType.Relational().ColumnName;
        if (!string.IsNullOrEmpty(propertyName) && !string.IsNullOrWhiteSpace(columnName) && !dict.ContainsKey(propertyName))
        {
            dict.Add(propertyName, columnName);
        }
    }

    return dict;
}

Retrieving the table name for an entity is equally straight forward:

private string GetTableName(Type type)
{
    return _dbContext.Model.FindEntityType(type).SqlServer().TableName;
}

In my old EF 6.1.3 libraries, I had methods that used Reflection to retrieving fully materialized queries from any IQueryable. This was handy as it was used beyond logging/tracing. For one particular application, this was used to generate queries based on user selections. Those queries were saved to a database and used as part of a SQL Job that imported data. That is one scenario that comes to mind for being able to generate queries.

EF Core 1.1.1 makes this no easier. I did stumble across an EF Core based routine, which again is using Reflection, to achieve the goal of producing materialized queries.

private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();
private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler");
private static readonly PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider");
private static readonly MethodInfo CreateQueryParserMethod = QueryCompilerTypeInfo.DeclaredMethods.First(x => x.Name == "CreateQueryParser");
private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");
private static readonly FieldInfo QueryCompilationContextFactoryField = typeof(Database).GetTypeInfo().DeclaredFields.Single(x => x.Name == "_queryCompilationContextFactory");

/// <summary>
/// Retrieve trace string for IQueryable
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
public string GetTraceString<T>(IQueryable<T> query)
{
    if (!(query is EntityQueryable<TEntity>) && !(query is InternalDbSet<TEntity>))
    {
        throw new ArgumentException("Invalid query");
    }

    var queryCompiler = (IQueryCompiler)QueryCompilerField.GetValue(query.Provider);
    var nodeTypeProvider = (INodeTypeProvider)NodeTypeProviderField.GetValue(queryCompiler);
    var parser = (IQueryParser)CreateQueryParserMethod.Invoke(queryCompiler, new object[] { nodeTypeProvider });
    var queryModel = parser.GetParsedQuery(query.Expression);
    var database = DataBaseField.GetValue(queryCompiler);
    var queryCompilationContextFactory = (IQueryCompilationContextFactory)QueryCompilationContextFactoryField.GetValue(database);
    var queryCompilationContext = queryCompilationContextFactory.Create(false);
    var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor();
    modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
    var sql = modelVisitor.Queries.First().ToString();

    return sql;
}

There are other functions/methods that I have not figured out how to port yet. With EF 6.1.3, I had routines for using SqlBulkCopy, performing batch/bulk updates and deletes, and other handy functions. I’m not sure how to approach these yet. I also had routines that would use EdmFunctions to be able to automagically hook into scalar functions when queries are run.

Even with some of the short comings, I have my web / utility / DAL solution compiling in Dotnet Core. Next up, I actually have to confirm that my code works beyond compilation. 🙂

Here are some links to all of the various articles I read alone the way..

https://blogs.msdn.microsoft.com/dotnet/2016/09/29/implementing-seeding-custom-conventions-and-interceptors-in-ef-core-1-0/
https://blog.oneunicorn.com/2016/11/17/add-attach-update-and-remove-methods-in-ef-core-1-1/
https://github.com/VahidN/EPPlus.Core
http://stackoverflow.com/questions/34414310/httpruntime-cache-equivalent-for-asp-net-5-mvc-6
http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/
https://cmatskas.com/entity-framework-core-1-0-table-valued-functions-and-linq-composition/

Leave a Reply

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