Curls, clouds and code

Entity Framework Core: insert or update, the lazy way

It seems as if I’m getting more lazy by the day. That’s great, because I didn’t really feel like manually mapping my data to my data models in order to have Entity Framework update them.

The thing is there are several options for you if you wish to run such operation:

As I said before. I’m lazy. Real lazy. Let me explain my situation.

I’ve been fiddling around quite a lot with GraphQL lately. During this process I figured out that the create and update logic I write on the client-side is quite simillar. This led me to think that, unless a special operation needed to be executed, most create and update logic could be combined in save, or in my case mutate operation. Depending on whether the primary key would be provided either an update or create operation should be executed.

So all in all I did not feel like writing stuff like this:

dbModel.FirstName = viewModel.FirstName;
dbModel.LastName = viewModel.LastName;
// etc...

Heck, I even ditched use of the viewmodels, mostly. I wanted to have an operation with which I could just pass on my object, and it would automagically determine which fields needed to be sent to the database.

Something like this is what I came up with. I discourage using this directly in production code. Use it by means of inspiration.

public static async Task Mutate<T>(
    this DbSet<T> dbSet,
    T subject,
    Guid? cursor = default) where T : class, IId
{
    var context = dbSet.GetService<ICurrentDbContext>().Context;

    bool entryExists = false;

    if (cursor != default) entryExists = await dbSet.AsNoTracking().AnyAsync(q => q.Id == cursor);

    if (entryExists == false) {
        subject.Id = cursor ?? Guid.NewGuid();
        dbSet.Add(subject);
    }
    else if (entryExists == true)
    {
        subject.Id = cursor.Value;

        var entry = dbSet.Attach(subject);

        entry.State = EntityState.Modified;

        typeof(T)
            .GetProperties()
            .Where(q =>
                q.CanWrite
                && Convert.GetTypeCode(q.GetValue(subject)) != TypeCode.Object
                && q.GetValue(subject) != q.GetType().GetDefault()
                && q.GetValue(subject) != null)
            .Select(q => q.Name)
            .ToList()
            .ForEach(property => entry.Property(property).IsModified = true);
                
        entry.Property(q => q.Id).IsModified = false;
    }
}

public static object GetDefault(this Type type)
{
    return type.IsValueType
        ? Activator.CreateInstance(type)
        : null;
}

public interface IId {
  Guid Id { get; set; }
}

It’s not as magical as it might look like. Although there are a few things to note:

I know there are certain people which are going to scream out loud about performance. Let me state this: performance was not a priority, nor did I benchmark it. If this ever becomes an issue I will improve it. If it doesn’t, well, then that’s great.

Happy hacking! And remember, life’s too short to keep writing CRUD code.


comments powered by Disqus