Skip to content

Instantly share code, notes, and snippets.

@axelheer
Created August 19, 2015 07:24
Show Gist options
  • Save axelheer/c3b2419c3b0b5f793774 to your computer and use it in GitHub Desktop.
Save axelheer/c3b2419c3b0b5f793774 to your computer and use it in GitHub Desktop.
Implementation of .AddOrUpdate which supports nullables too
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace System.Data.Entity
{
internal static class DbSeeder
{
public static TEntity[] AddOrUpdate<TEntity>(this DbContext context, Expression<Func<TEntity, object>> identifiers, params TEntity[] entities)
where TEntity : class
{
// EF .AddOrUpdate is a bit buggy...
var primaryKeys = PrimaryKeys<TEntity>();
var properties = Properties(identifiers);
for (var i = 0; i < entities.Length; i++)
{
// build where condition for "identifiers"
var parameter = Expression.Parameter(typeof(TEntity));
var matches = properties.Select(p => Expression.Equal(
Expression.Property(parameter, p),
Expression.Constant(p.GetValue(entities[i]), p.PropertyType)));
var match = Expression.Lambda<Func<TEntity, bool>>(
matches.Aggregate((p, q) => Expression.AndAlso(p, q)),
parameter);
// match "identifiers" for current item
var current = context.Set<TEntity>().SingleOrDefault(match);
if (current != null)
{
// update primary keys
foreach (var k in primaryKeys)
k.SetValue(entities[i], k.GetValue(current));
// update all the values
context.Entry(current).CurrentValues.SetValues(entities[i]);
// replace updated item
entities[i] = current;
}
else
{
// add new item
entities[i] = context.Set<TEntity>().Add(entities[i]);
}
}
return entities;
}
private static PropertyInfo[] Properties<TEntity>(Expression<Func<TEntity, object>> identifiers)
where TEntity : class
{
// e => e.SomeValue
var direct = identifiers.Body as MemberExpression;
if (direct != null)
{
return new[] { (PropertyInfo)direct.Member };
}
// e => (object)e.SomeValue
var convert = identifiers.Body as UnaryExpression;
if (convert != null)
{
return new[] { (PropertyInfo)((MemberExpression)convert.Operand).Member };
}
// e => new { e.SomeValue, e.OtherValue }
var multiple = identifiers.Body as NewExpression;
if (multiple != null)
{
return multiple.Arguments
.Cast<MemberExpression>()
.Select(a => (PropertyInfo)a.Member)
.ToArray();
}
throw new NotSupportedException();
}
private static PropertyInfo[] PrimaryKeys<TEntity>()
where TEntity : class
{
return typeof(TEntity).GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(KeyAttribute))
|| "Id".Equals(p.Name, StringComparison.Ordinal))
.ToArray();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment