Entity Framework 7, Insert or Update

Asked

Viewed 1,788 times

4

I’ve been looking for a method to check if an object exists in the base, if it exists, run a update, if there is no, insert.

I couldn’t find anything anywhere that would suit me, so I made the following code:

public static void InsertOrUpdate<T>(this DbSet<T> dbSet, T entity) where T : class
{
    PropertyInfo pId = entity.GetType().GetProperty("Id");
    if (pId != null)
    {
        object valId = pId.GetValue(entity);
        if (dbSet.Any(p => p.GetType().GetProperty("Id").GetValue(p).ToString() == valId.ToString()))
        {
            T e = dbSet.Where(p => p.GetType().GetProperty("Id").GetValue(p).ToString() == valId.ToString()).FirstOrDefault();

            foreach (PropertyInfo p in e.GetType().GetProperties().Where(x => x.CanWrite && x.Name != "Id"))
            {
                p.SetValue(e, p.GetValue(entity));
            }

            dbSet.Update(e);
        }
        else
        {
            dbSet.Add(entity);
        }
    }
}

As I always read something about slowing down the use of Reflection, and I try to optimize the code, the question is simple:

There is another way to do this method, or improve this code ?

At first, I have no problem with it, and it works perfectly. Just a matter of not hacking and optimizing the code.

I am using ASP.NET Core, with Entity Framework Core and I have no practice with these.

Edit:

I got that other method (looks better, but there’s still reflection):

public static void InsertOrUpdate<T>(this ApplicationDbContext context, T entity) where T : class
{
    PropertyInfo ps = entity.GetType().GetProperty("Id");
    if (ps != null)
    {
        DbSet<T> set = context.Set<T>();
        if (set.Any(x => ps.GetValue(x).ToString() == ps.GetValue(entity).ToString()))
        {
            context.Entry(entity).State = EntityState.Modified;
        }
        else
        {
            set.Add(entity);
        }
    }
}

1 answer

1


Create a class to create an extension method named InsertOrUpdate<T> generic, in this code you first search for the primary key(s) (s) of your settings that were made for your Model, with this is done the search in your table to see if the data has been persisted and with this information makes the decision if it is Add or Update that must be done, example:

public static class Utils
{
    public static void InsertOrUpdate<T>(this DbContext context, T model)
        where T: class
    {
        EntityEntry<T> entry = context.Entry(model);            
        IKey primaryKey = entry.Metadata.FindPrimaryKey();            
        if (primaryKey != null)
        {
            object[] keys = primaryKey.Properties.Select(x => x.FieldInfo.GetValue(model))
                                            .ToArray();
            T result = context.Find<T>(keys);
            if (result == null)
            {
                context.Add(model);
            }
            else
            {
                context.Entry(result).State = EntityState.Detached;
                context.Update(model);
            }
        }
    }
}

How to use:

using (DatabaseContext db = new DatabaseContext())
{
    Cliente c = new Cliente();                
    c.Nome = "StackOverFlow";               

    db.InsertOrUpdate(c);
    db.SaveChanges();

}

Note: this example will always generate a SQL to seek information (whether or not there is) and another SQL which may be between INSERT or UPDATE, could even check simpler where the property value was 0, would then make a INSERT and greater than 0 would make a UPDATE, but, this would only work for Model simple with an auto increment key, the above method works even for key that has more than one field and are not incremented by the bank and also worth remembering that it is ideal to always configure your Model in the way Fluent, as example just below:

Model:

public class Cliente
{
    public int Id { get; set; }
    public string Nome { get; set; }
}
public class Test
{        
    public int Chave1 { get; set; }        
    public int Chave2 { get; set; }
    public decimal Valor { get; set; }
}

Context:

public class DatabaseContext : DbContext
{
    public DbSet<Cliente> Cliente { get; set; }
    public DbSet<Test> Test { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Cliente>()
            .ToTable("Clientes")
            .HasKey(x => x.Id);

        modelBuilder.Entity<Cliente>()
            .Property(x => x.Id)
            .IsRequired()
            .UseSqlServerIdentityColumn();

        modelBuilder.Entity<Cliente>()
            .Property(x => x.Nome)
            .HasMaxLength(50)
            .IsRequired();

        modelBuilder.Entity<Test>()
            .ToTable("Test")
            .HasKey(x => new { x.Chave1, x.Chave2 });

        modelBuilder.Entity<Test>()
           .Property(x => x.Chave1)
           .IsRequired();

        modelBuilder.Entity<Test>()
            .Property(x => x.Chave2)
            .IsRequired();

        modelBuilder.Entity<Test>()
            .Property(x => x.Valor)
            .IsRequired();

    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("connection_strings", options =>
        {
        });
    }
}

If you still want to check if your key has the value 0 and in that case would give a INSERT and if the value is greater than 0 would make a UPDATE the extension method would change to this:

public static void InsertOrUpdate<T>(this DbContext context, T model)
    where T : class
{
    EntityEntry<T> entry = context.Entry(model);
    IKey primaryKey = entry.Metadata.FindPrimaryKey();
    if (primaryKey != null)
    {
        object key = primaryKey.Properties.Select(x => x.FieldInfo.GetValue(model))
                                          .FirstOrDefault();                
        if (key.ToString() == "0")
        {
            context.Add(model);
        }
        else
        {                    
            context.Update(model);
        }
    }
}

only that this code would work as already spoken only for primary key with an auto increment field.

Observing: the big problem of the code that is in your question is that the value of the primary key is fixed and this always requires being the primary key of your table with the name of Id, what is in the answer can be any name, just really need to be configured as it was explained earlier.

References

  • 1

    Thank you @Virgilio, for today I’m already stopping, but tomorrow I implement and give the feedback. Good night!

  • 1

    It worked perfectly @Virgilio, thank you very much!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.