Entity core framework fix at 2026.100?

Anybody seeing this after the conversion last night?

In this system it doesn’t look like anything got changed but the conversion description is concerning. After the dumpster fire last year, I thought Epicor had said they would only flag and not modify custom code during conversion? :dumpster_fire: Unacceptable. Epicor, do not touch my code

I do see there is at least a help article filed about it, confusingly, under 2026.1 (didn’t we just get upgraded to 2026.100?).

I remember talking about EF Core in another thread. But I can’t find it now.
To eventually move off SQL Server ( :safe_harbor:) they have to get onto EF Core. It’s the future of Entity Framework. EF 6 goes end of life soon. Microsoft has stopped all development of Entity Framework and is only now working on EF Core.

Right, that is explained in the help article. I just don’t understand why they would modify rather than flag existing custom code.

I’m guessing because they want to move quicker than that. And just flagging it won’t get most peoples attention. People will just ignore the warnings and let it go.

Apparently we learned nothing last year.

Our Electronic Bank Interface by CSG no longer works since the move to 2026.1 so i think it might be tied in as I had the error during the initial first log in

Did you look at the log? The conversion implies it will log changes. It’s very likely that’s what broke it.

Seriously how can Epicor do this AGAIN… Did they not promise not to do this again? Analysis yes, but change really.. For heavens sake… I thought we were actually making inroads in the Vendor Customer relationship after Insights…

No wonder the upgrade didn’t happen before Insights…

I am extremely disappointed in this.

Where do we send the invoices for fixing our code they broke.

Of course you could argue that if all your code was EF Core compliant then you should not have a problem… But should you vendor even touch code? … I think not…

I guess we will wait and see how many people have a problem…

Monday should be fun. I’m not going near Pilot until then.

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

Conversion Reference

This document details the specific code transformations performed by the Entity Framework Core migration tool for Epicor customizations.

Overview

This tool converts legacy Epicor custom code from Entity Framework 6 (EF6) to Entity Framework Core (EF Core) using Roslyn code analysis and rewriting.

It processes:

  • BPM Directives (Business Process Management custom code)
  • Epicor Functions (custom function libraries)
  • Product Configurator rules
  • Electronic Interfaces (EDI/integration customizations)

1. What It Converts (Replaces)

1.1 Namespace Replacements

EF6 Namespace EF Core Replacement
System.Data.Entity Microsoft.EntityFrameworkCore
System.Data.Entity.SqlServer Microsoft.EntityFrameworkCore

What Happens

  • Using directives are replaced (old ones commented out)
  • Fully qualified names are rewritten

Example

// BEFORE
using System.Data.Entity;

// AFTER
// using System.Data.Entity;
using Microsoft.EntityFrameworkCore;

1.2 DbFunctions Method Replacements

EF6’s DbFunctions methods are converted to EF Core equivalents.

Date Arithmetic Methods

EF6 Method EF Core Replacement
DbFunctions.AddDays(date, days) date.AddDays(days) or date.Value.AddDays(days)
DbFunctions.AddMonths(date, months) date.AddMonths(months) or date.Value.AddMonths(months)

Date Difference Methods

EF6 Method EF Core Replacement
DbFunctions.DiffDays(start, end) EF.Functions.DateDiffDay(start, end)
DbFunctions.DiffHours(start, end) EF.Functions.DateDiffHour(start, end)
DbFunctions.DiffMinutes(start, end) EF.Functions.DateDiffMinute(start, end)
DbFunctions.DiffSeconds(start, end) EF.Functions.DateDiffSecond(start, end)
DbFunctions.DiffMonths(start, end) EF.Functions.DateDiffMonth(start, end)
DbFunctions.DiffYears(start, end) EF.Functions.DateDiffYear(start, end)

Date Truncation Methods

EF6 Method EF Core Replacement
DbFunctions.TruncateTime(date) date.Date or date.Value.Date

Notes

  • Automatically handles nullable versus non-nullable DateTime values
  • Fully qualified DbFunctions calls are rewritten to use Microsoft.EntityFrameworkCore.SqlServerDbFunctionsExtensions
  • Simple DbFunctions calls are rewritten to use EF.Functions

1.3 LINQ Default Methods

Methods with default-value parameters are converted because EF Core does not support those overloads.

Method Transformation
FirstOrDefault(defaultValue) Removes the defaultValue parameter when it is a literal default
LastOrDefault(defaultValue) Removes the defaultValue parameter
SingleOrDefault(defaultValue) Removes the defaultValue parameter
DefaultIfEmpty(defaultValue) Removes the defaultValue parameter

Supported literal defaults:

  • null
  • false
  • 0

Example

// BEFORE
var result = query.FirstOrDefault(null);

// AFTER
var result = query.FirstOrDefault();

Warning Generated

If the default value is not a simple literal (null, false, or 0), a diagnostic warning is generated for manual review.


1.4 String Function Overload Removal

Culture-specific string comparison overloads are removed from LINQ queries because EF Core does not support them.

Methods Affected

  • String.Compare()
  • String.Contains()
  • String.Equals()
  • String.StartsWith()
  • String.EndsWith()

Removed Parameters

String.Compare()

Removes:

  • StringComparison
  • CultureInfo
  • CompareOptions

Other Methods

Removes:

  • StringComparison

Applies Only When

  • The method is used inside a LINQ query expression
  • The method includes non-string or non-char comparison parameters

2. What It Deletes

2.1 Assembly References

Removed from BPM Directive XML

  • EntityFramework.dll
  • EntityFramework.SqlServer.dll

Removed from Function Library References

DELETE FROM Ecf.EfxLibraryReference
WHERE LibraryID = @libraryId
  AND ReferenceID IN (
      'EntityFramework.dll',
      'EntityFramework.SqlServer.dll'
  )

2.2 Using Directives

The following directives are removed and commented out:

using System.Data.Entity;
using System.Data.Entity.SqlServer;

3. What It Comments Out

3.1 Identity Column Assignments

Assignments to auto-increment (identity) columns are commented out because EF Core does not allow explicit identity value updates.

Example

// BEFORE
row.ABTAmountUID = 123;
row.MtlQueueSeq++;

// AFTER
// row.ABTAmountUID = 123;
// row.MtlQueueSeq++;

Common Identity Columns

  • ABTAmountUID
  • ABTDocLineUID
  • MtlQueueSeq
  • TranNum
  • HeadNum
  • Sequence
  • AttributeSetID
  • DeferredSeq

Operations Commented Out

  • Assignment (=)
  • Increment (++)
  • Decrement (--)
  • Compound assignment (+=, -=, etc.)

Scope

More than 180 Epicor tables with identity columns are analyzed.


4. What It Validates (Warnings and Errors)

4.1 Warnings

ECF9002

The 'FirstOrDefault' function that takes a default value is not supported in EF Core.

Generated when a LINQ default method uses a non-literal default value.

ECF9001

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

Generated when code attempts to assign an identity column value.


4.2 Errors

ECF9003

The DbFunction '{MethodName}' is not supported in EF Core.

Generated for unsupported EF6 database functions.

ECF9004

The method overload is not supported in EF Core LINQ queries.

Generated for unsupported string-comparison overloads.


5. Conversion Results

Success States

Fully Converted

Code updated successfully and compiles under EF Core.

Partially Converted

Code updated, but warnings were generated and manual review is required.

Not Modified

No EF6-specific code was detected.


Failure States

Conversion Failed

EF6 code was detected but could not be automatically converted. The database is not updated.

Error During Processing

An exception occurred during conversion.


Backup and Rollback

Original Code Preservation

Original source code is preserved in all conversion messages.

Electronic Interfaces

Backup files are written to the Company/EI folder.

BPM Directives and Functions

Original source code is included in conversion logs.


6. What Requires Manual Review

The converter flags the following scenarios for manual intervention:

  1. Unsupported DbFunctions
  2. Non-literal default values in LINQ methods
  3. Complex culture-specific string comparisons
  4. Identity column assignment logic
  5. Custom EF6 extensions not recognized by the converter

Waaay more constructive than my rant. Thank you. @hkeric.wci

You are truly are a legend, you contribions to the community should be celebrated.

That’s an upgrade from last time where it removed the old code and tried to replace it with new code that didn’t always work.

The one thing I am unsure about is the IDENTITY columns. We do use NextSeq Library to get the next sequence number, and sometimes we assign an identity column, so does Epicor. I assume not all are marked as IDENTITY columns, hence may still work.

Another burning question for @JeffLeBert when he gets a breather from work. I know he is extremely busy with postgres.

Isn’t there examples in the github repo related to the ef core. If I am wrong I am thinking not.. Of course just like AI I am HI and have been known to be wrong on more than one occasion.

@Epic_Santiago posted this X-Platform · GitHub

Thanks for taking the time to share this @hkeric.wci and thank you @JeffLeBert for speaking with users.

I think you are thinking of .NET Framework to .NET Core. This time its Entity Framework 6 to EF Core.

Yes I do declare you are correct…

Edit, Actually no I thin it was in this link I noticed something.

An identity column should be a guid or uuid, so you are good. You aren’t making those with the NextSeq library.