Porting EF6 to EF7 or EF Core

Home / Porting EF6 to EF7 or EF Core

Since my foray into utilizing .NET Core to port an older CRUD app using Angular 1.x and Entity Framework 6.x, my first stumbling block is dealing with breaking changes between EF 6.x and newer versions of EF.


Right off the bat, we find, as reported by the EF team, many objects and interfaces no longer exist.

https://msdn.microsoft.com/en-us/magazine/dn890367.aspx
http://blogs.msdn.com/b/adonet/archive/2014/10/27/ef7-v1-or-v7.aspx

If you’re performing low-level API/object interactions, then, your code, as mine is, will be pretty much broken and need to be rewritten. One such example is, as mentioned in the first link above, anything that touches the ObjectContext, ObjectStateEntry, or IObjectContextAdapter. I often use these objects/interfaces to determine which objects are modified/new when the DbContext’s “SaveChanges” method is called in order to perform things such as setting specific property values on my entities.

As an example, cating the DbContext as an ObjectContext to achieve this goal, like so, no longer works:

ObjectContext context = ((IObjectContextAdapter)this).ObjectContext;

//Find all Entities that are Added/Modified that inherit from my BaseEntity
IEnumerable<ObjectStateEntry> objectStateEntries =
    from e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
    where
        e.IsRelationship == false &&
        e.Entity != null &&
        (
            typeof(BaseEntity<int>).IsAssignableFrom(e.Entity.GetType()))
        )
    select e;

// Set the create/modified date as appropriate
foreach (var entry in objectStateEntries)
{
    var entityBase = entry.Entity as BaseEntity<int>;
    if (entry.State == EntityState.Added)
    {
        entityBase.CreateDate = currentTime;
        entityBase.CreateUserId = userId;
    }

    entityBase.ModifyDate = currentTime;
    entityBase.ModifyUserId = userId;
}

However, the DbContext now has a ChangeTracker interface that sort-of allows us to do the same thing:

var entities = this.ChangeTracker
    .Entries()
    .Where(x => x.State == EntityState.Modified || x.State == EntityState.Added &&
    x.Entity != null && typeof(BaseEntity<int>).IsAssignableFrom(x.Entity.GetType())))
    .Select(x => x.Entity)
    .ToList();

// Set the create/modified date as appropriate
foreach (var entity in entities)
{
    var entityBase = entity as BaseEntity<int>;
    if (entry.State == EntityState.Added)
    {
        entityBase.CreateDate = currentTime;
        entityBase.CreateUserId = userId;
    }

    entityBase.ModifyDate = currentTime;
    entityBase.ModifyUserId = userId;
}

Something else that is gone from EF7/EF Core is model validation on save. Code like this will no longer work:

try
{
    return base.SaveChanges();               
}
catch (DbEntityValidationException dbEx)
{
    // Iterate over the errors and write the trace information to make it a little easier to debug
    // We coudl also wrap this up with our RuleException for any modeldb issues that we don't catch with our 
    // domain rules.
    foreach (var validationErrors in dbEx.EntityValidationErrors)
    {
        foreach (var validationError in validationErrors.ValidationErrors)
        {
            Trace.TraceInformation("Property: {0}, Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
        }
    }

    throw;
}

I can live without validaiton at the DbContext level and agree, to an extent, with this quote from one of the EF developers:

.. Validation happens on the client, then again in the action to protect against malicious requests. Most validation also happens in the database too (nullability of columns, max length of data, etc.). Obviously not all apps go that far, but validating during SaveChanges is usually just front loading an exception you would get from the database and adding overhead to do that.

Although, one could validate the object themselves uses the configured annotations/fluent mappings. Your objects could also implement IValidatableObject and that interface could be used:

public override int SaveChanges()
{
	var validationErrors = ChangeTracker
		.Entries<IValidatableObject>()
		.SelectMany(e => e.Entity.Validate(null))
		.Where(r => r != ValidationResult.Success);

	if(validationErrors.Any())
	{
		// Possibly throw an exception here
	}

	return base.SaveChanges();
}

The last thing in my initial findings is with the constructor for the DbContext. Instead of (easily) passing a connection string name into the DbContext, we have to override the OnConfiguring method of the DbContext. I don’t know, offhand, how well this plays with injection, transforms, or other configuration passing methods. There’s a lot of work ahead to move what would now be considered a legacy application to .NET Core.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
	optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=MyTestDb;Trusted_Connection=True;");
}

Leave a Reply