public SaveConstraintAttribute(SaveConstraint behaviour) { Behaviour = behaviour; }
/// <summary> /// Adds entity to corresponding db set if an entity with same primary key does not exist in database, /// or updates existing instance with the same primary key. /// </summary> /// <typeparam name="TEntity">Entity class</typeparam> /// <param name="ctx">Context instance</param> /// <param name="entity">Entity instance to add or update.</param> public static void AddOrUpdateEntity <TEntity>(this DbContext ctx, TEntity entity) where TEntity : class { Type clrType = typeof(TEntity); EntityType entityType = ctx.GetEntityType(clrType); if (entityType == null) { throw new ArgumentException($"Class `{clrType.Name}` is not an entity type for database context {ctx.GetType().Name}."); } // name of key property string keyPropertyName = entityType.KeyProperties.First().Name; // key property PropertyInfo keyProperty = clrType.GetProperty(keyPropertyName); // key value object key = keyProperty.GetValue(entity); // Create "e" portion of lambda expression ParameterExpression parameter = Expression.Parameter(clrType, "e"); // create "e.Id" portion of lambda expression MemberExpression expProperty = Expression.Property(parameter, keyProperty.Name); // create "'id'" portion of lambda expression var expKeyConstant = Expression.Constant(key); // create "e.Id == 'id'" portion of lambda expression var expEqual = Expression.Equal(expProperty, expKeyConstant); // finally create entire expression: "e => e.Id == 'id'" Expression <Func <TEntity, bool> > predicate = Expression.Lambda <Func <TEntity, bool> >(expEqual, new[] { parameter }); // search existing entity var existing = ctx.QueryEntitiesWithRelated <TEntity>().FirstOrDefault(predicate); if (existing != null) { var navProperties = entityType.NavigationProperties.Select(np => ctx.Entry(entity).Member(np.Name)).ToArray(); var collectionNavProperties = navProperties.OfType <DbCollectionEntry>().ToArray(); if (collectionNavProperties.Any()) { UpdateRelatedCollections(ctx, existing, entity, collectionNavProperties); } var singleNavProperties = navProperties.OfType <DbReferenceEntry>().Where(np => np.CurrentValue != null).ToArray(); foreach (var navProperty in singleNavProperties) { ctx.Entry(navProperty.CurrentValue).State = EntityState.Added; } ctx.Entry(existing).CurrentValues.SetValues(entity); } else { ctx.Set <TEntity>().Add(entity); } var saveBehaviourProperties = clrType.GetProperties().Where(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(SaveConstraintAttribute))).ToList(); foreach (var property in saveBehaviourProperties) { SaveConstraint constraint = property.GetCustomAttribute <SaveConstraintAttribute>().Behaviour; var edmMember = entityType.DeclaredMembers.First(p => p.Name == property.Name); // do not modify property if new value is default value if (constraint == SaveConstraint.NotEmpty && GetDefaultValueOfType(property.PropertyType) == property.GetValue(entity)) { if (edmMember.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty) { // do nothing, navigation property with null value not saved by default } else if (edmMember.BuiltInTypeKind == BuiltInTypeKind.EdmProperty) { ctx.Entry(existing).Property(property.Name).IsModified = false; } } // do not modify property if it's a referenced entity and it's already exist in db if (constraint == SaveConstraint.NotExists && existing != null) { if (edmMember.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty) { object newPropertyValue = property.GetValue(entity); object existingPropertyValue = ctx.SearchEntityByKey(newPropertyValue); if (newPropertyValue != null && existingPropertyValue != null) { ctx.Entry(newPropertyValue).State = EntityState.Detached; } } else if (edmMember.BuiltInTypeKind == BuiltInTypeKind.EdmProperty) { throw new Exception($"{property.Name} property of type {clrType.Name} is not navigation property. {nameof(SaveConstraint.NotExists)} constraint can be applied for navigation properties only."); } } // do not save properties marked with "Never" if (constraint == SaveConstraint.Never) { if (edmMember.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty) { object newPropertyValue = property.GetValue(entity); if (newPropertyValue != null) { ctx.Entry(newPropertyValue).State = EntityState.Detached; } } else if (edmMember.BuiltInTypeKind == BuiltInTypeKind.EdmProperty) { ctx.Entry(existing).Property(property.Name).IsModified = false; } } } }