Entity core framework fix at 2026.100?

Migrating from Entity Framework 6 to Entity Framework Core

Entity Framework Core (EF Core) is stricter than Entity Framework 6 (EF 6).

To help identify potential issues, we’ve added several code analyzers that run when you save your BPM or Epicor Function (EF).

To assist with addressing these issues, we have added a Conversion Workbench task called EntityFrameworkCoreFix number 114, which during migration auto-fixes or provides additional diagnostics for many (but not all) compatibility issues across BPMs, Epicor Functions, Electronic Interfaces and Product Configurator.

You may see warnings related to the following issues.

IDENTITY columns

Kinetic 2025.2 warns you about assignments of IDENTITY columns. 2026.1 will produce compiler errors as IDENTITY columns are read only.

Solution
The manual fix is to remove any code that attempts to set values for IDENTITY columns. Those are set by the database.

The EntityFrameworkCoreFix conversion will comment out simple assignment of an IDENTITY column. If the code is too complex to be auto-remediated the following diagnostic will be returned instead:

The value of the identity column {TableName}.{ColumnName} is set by the database. Do not set it in code.

DefaultIfEmpty(…value…) and Related Methods

EF Core does not support specifying a default value in the following methods:

  • DefaultIfEmpty
  • FirstOrDefault
  • LastOrDefault
  • SingleOrDefault

For example, the following EF 6-style query will fail at runtime in EF Core due to the false default:

var hasActiveTip = Db.Tip
    .Where(row => row.TipNum == 13)
    .Select(row => row.Active)
    .DefaultIfEmpty(false)
    .FirstOrDefault();

Solution
You can fix this manually or use AI tools like Copilot. For example, prompting Copilot with:
Fix the following query so that it does not pass false to DefaultIfEmpty:
followed by the above code. This produced the following corrected version:

var hasActiveTip = Db.Tip
    .Where(row => row.TipNum == 13)
    .Select(row => (bool?)row.Active)
    .FirstOrDefault() ?? false;

Using EntityFrameworkCoreFix, if a method used above is included in custom code with a default value, this can be auto-fixed as long as the default value is the default literal for the value being set (e.g, null, false, 0…).

Explanation

  • (bool?)row.Active casts the result to a nullable Boolean. If Active is already nullable, this cast is unnecessary.
  • FirstOrDefault() returns null if no rows match.
  • ?? false provides a default value of false when no result is found.

Note on filtering
You can still use filters with methods like FirstOrDefault, LastOrDefault, and SingleOrDefault. For example:

Tip tipRow = this.Db.FirstOrDefault(row => row.TipNum == 13)

Min, Max and Sum

EF Core will throw a Sequence contains no elements exception if the query you are calling, Min, Max or Sum on returns no rows. For example, if you are summing the quantity of something
in a query:

var result =
    (from row in Db.Table
     where row.Key == "ABC"
     select row.Quantity)
    .Max();

This would fail if there were no rows returned. If you want the column’s data types default value to be the default value for Max then you can do this:

var result =
    (from row in Db.Table
     where row.Key == "ABC"
     select row.Quantity)
    .DefaultIfEmpty()
    .Max();

If you need some other value than the column’s data types default then you can do the following:

var result =
    (from row in Db.Table
     where row.Key == "ABC"
     select (int?)row.Quantity)
    .Max() ?? 999;

For columns that are nullable, such as the integer Quantity column above, you have to cast the result to a nullable data type, int? in this case.

DbFunctions

EF Core does not support DbFunctions. If you need to keep your code compatible with both 2025.2 and 2026.1 you can use conditional compilation. If you are running in 2026.1, you can just modify the code as suggested below without the conditional compiled code. If you are modifying code prior to 2026.1 and want the code to run in both your current and future version of Kinetic, then you can use USE_EF_CORE which is a conditional compilation value that is defined automatically when running in EF Core. After you have fully upgraded to 2026.1 or later, you can remove the conditional code and keep only the EF Core part. The TruncateTime conversion is shown with the conditional compile, the examples following that only show the final code for brevity.

The following methods can be modified to an EF Core-compatible alternative.

  • TruncateTime
  • AddDays, AddMonths
  • DiffDays, DiffHours, DiffMicroseconds, DiffMilliseconds, DiffMinutes, DiffMonths, DiffNanoseconds, DiffSeconds, DiffYears

Solution

The following changes can be made manually, or automatically using EntityFrameworkCoreFix.

TruncateTime

var results =
    from row in Db.ICEVer
    where System.Data.Entity.DbFunctions.TruncateTime(row.DBSchemaDate) == new DateTime(2014, 7, 9)
    select row;

Should be changed to:

var results =
    from row in Db.ICEVer
#if USE_EF_CORE
    where row.DBSchemaDate.Value.Date == new DateTime(2014, 7, 9)
#else
    where System.Data.Entity.DbFunctions.TruncateTime(row.DBSchemaDate) == new DateTime(2014, 7, 9)
#endif
    select row;

AddDays & AddMonths

var results =
    from row in Db.ICEVer
    where System.Data.Entity.DbFunctions.AddDays(row.DBSchemaDate, 5) >= DateTime.Now
    select row;

Should be changed to:

var results =
    from row in Db.ICEVer
    where row.DBSchemaDate.Value.AddDays(5) >= DateTime.Now
    select row;

Diff[xx]

Diff methods listed above can be addressed by replacing with the appropriate Microsoft.EntityFrameworkCore.SqlServerDbFunctionsExtensions method. These methods begin with DateDiff. Below is an example of such replacement.

var results =
    from row in Db.ICEVer
    where System.Data.Entity.DbFunctions.DiffMonths(row.DBSchemaDate, DateTime.Now) > 5
    select row;

Should be changed to:

var results =
    from row in Db.ICEVer
    where Microsoft.EntityFrameworkCore.SqlServerDbFunctionsExtensions.DateDiffMonth(null, row.DBSchemaDate, DateTime.Now) > 5
    select row;

String functions

EF Core supports fewer overloads of standard C# functions in queries. We have added an analyzer to give compiler warnings for the most commonly used overloads. For the most part, these are string functions that specify different StringComparison parameters. For example:

var results =
    (from row in Db.Menu
     where string.Equals(row.MenuID, "ABC", StringComparison.OrdinalIgnoreCase)
     select row);

In most cases, you can just drop the StringComparison parameter. The comparison will use the comparison type defined by the database.

Solution
Using EntityFrameworkCoreFix the following methods can be auto-fixed in cases where the unsupported parameters (StringComparison, InvariantCase…) can simply be removed.

  • string.Compare
  • string.Contains
  • string.Equals
  • string.StartsWith
  • string.EndsWith

Complex values in queries

EF Core only allows “simple” values in queries. For example, if you retrieve parentRow from a query and you need to use values for you current query:

var results =
    from row in Db.ChildTable
    where row.Key1 = parentRow.Key1
    select row;

Since parentRow.Key1 is not a “simple” value, you will need to do something like this:

var parentKey = parentRow.Key1;
var results =
    from row in Db.ChildTable
    where row.Key1 = parentKey
    select row;

GroupBy

Similar to EF Core requiring “simple” value, group can cause issues as well. In the query below, if you were to use menuGroup.Key in another query, it would throw an exception.

var results =
    from menuRow in Db.Menu
    group menuRow by menuRow.SystemCode into menuGroup
    select new { menuGroup.Key, Count = menuGroup.Count() };

MARS (Multiple Active Result Sets)

MARS has been disabled. This means that a database connection can only have one query active at a time. For EF Core queries, you have to ensure that you have read through all the rows
in the result set before starting another query. A common solution is to add .ToList() to the end of the query. This will force all rows to be read.

For raw queries using a SqlReader, you have to dispose the reader before you execute another query. Commonly, you would add a using to the reader’s variable declaration. This will
automatically close the reader.

Note: It is recommended that direct access to SQL be avoided altogether.

Here is an example where we start a second read before completing the first one:

using var command1 = connection.CreateCommand();
command1.CommandText = "SELECT TOP 1 TipTitle FROM Ice.Tip";
var reader1 = command1.ExecuteReader();
reader1.Read();
var userId = reader1.GetString(0);

using var command2 = connection.CreateCommand();
command2.CommandText = "SELECT TOP 1 Action FROM Ice.ChangeLog";
var reader2 = command2.ExecuteReader();
reader2.Read();
var companyId = reader2.GetString(0);

And here is the fixed example where we close the first reader before opening the second:

using var command1 = connection.CreateCommand();
command1.CommandText = "SELECT TOP 1 TipTitle FROM Ice.Tip";
using (var reader1 = command1.ExecuteReader())
{
    reader1.Read();
    var userId = reader1.GetString(0);
}

using var command2 = connection.CreateCommand();
command2.CommandText = "SELECT TOP 1 Action FROM Ice.ChangeLog";
using (var reader2 = command2.ExecuteReader())
{
    reader2.Read();
    var companyId = reader2.GetString(0);
}

Executing raw SQL queries

A number of EF 6 methods are no longer supported. For example:

const string sqlStatement = "SELECT * FROM Ice.Menu";
Db.ExecuteStoreCommand(sqlStatement);

When Kinetic switches to using EF Core, you will need to replace that will supported method in EF Core.

Credits

@JeffLeBert