Skip to content

Instantly share code, notes, and snippets.

@tystol
Last active October 26, 2022 21:55
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save tystol/20b07bd4e0043d43faff to your computer and use it in GitHub Desktop.
Save tystol/20b07bd4e0043d43faff to your computer and use it in GitHub Desktop.
Entity Framework CascadeDelete using Data Annotations
using System;
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CascadeDeleteAttribute : Attribute { }
using System;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using System.Reflection;
public class CascadeDeleteAttributeConvention : IConceptualModelConvention<AssociationType>
{
private static readonly Func<AssociationType, bool> IsSelfReferencing;
private static readonly Func<AssociationType, bool> IsRequiredToMany;
private static readonly Func<AssociationType, bool> IsManyToRequired;
private static readonly Func<AssociationType, object> GetConfiguration;
private static readonly Func<object, OperationAction?> NavigationPropertyConfigurationGetDeleteAction;
static CascadeDeleteAttributeConvention()
{
var associationTypeExtensionsType = typeof(AssociationType).Assembly.GetType("System.Data.Entity.ModelConfiguration.Edm.AssociationTypeExtensions");
var navigationPropertyConfigurationType = typeof(AssociationType).Assembly.GetType("System.Data.Entity.ModelConfiguration.Configuration.Properties.Navigation.NavigationPropertyConfiguration");
var isSelfRefencingMethod = associationTypeExtensionsType.GetMethod("IsSelfReferencing", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
IsSelfReferencing = associationType => (bool)isSelfRefencingMethod.Invoke(null, new object[] { associationType });
var isRequiredToManyMethod = associationTypeExtensionsType.GetMethod("IsRequiredToMany", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
IsRequiredToMany = associationType => (bool)isRequiredToManyMethod.Invoke(null, new object[] { associationType });
var isManyToRequiredMethod = associationTypeExtensionsType.GetMethod("IsManyToRequired", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
IsManyToRequired = associationType => (bool)isManyToRequiredMethod.Invoke(null, new object[] { associationType });
var getConfigurationMethod = associationTypeExtensionsType.GetMethod("GetConfiguration", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
GetConfiguration = associationType => getConfigurationMethod.Invoke(null, new object[] { associationType });
var deleteActionProperty = navigationPropertyConfigurationType.GetProperty("DeleteAction", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
NavigationPropertyConfigurationGetDeleteAction = navProperty => (OperationAction?)deleteActionProperty.GetValue(navProperty);
}
public virtual void Apply(AssociationType item, DbModel model)
{
if (IsSelfReferencing(item))
return;
var propertyConfiguration = GetConfiguration(item);
if (propertyConfiguration != null && NavigationPropertyConfigurationGetDeleteAction(propertyConfiguration).HasValue)
return;
AssociationEndMember collectionEndMember = null;
AssociationEndMember singleNavigationEndMember = null;
if (IsRequiredToMany(item))
{
collectionEndMember = GetSourceEnd(item);
singleNavigationEndMember = GetTargetEnd(item);
}
else if (IsManyToRequired(item))
{
collectionEndMember = GetTargetEnd(item);
singleNavigationEndMember = GetSourceEnd(item);
}
if (collectionEndMember == null || singleNavigationEndMember == null)
return;
var collectionCascadeDeleteAttribute = GetCascadeDeleteAttribute(collectionEndMember);
var singleCascadeDeleteAttribute = GetCascadeDeleteAttribute(singleNavigationEndMember);
if (collectionCascadeDeleteAttribute != null || singleCascadeDeleteAttribute != null)
collectionEndMember.DeleteBehavior = OperationAction.Cascade;
}
private static AssociationEndMember GetSourceEnd(AssociationType item)
{
return item.KeyMembers.FirstOrDefault() as AssociationEndMember;
}
private static AssociationEndMember GetTargetEnd(AssociationType item)
{
return item.KeyMembers.ElementAtOrDefault(1) as AssociationEndMember;
}
private static CascadeDeleteAttribute GetCascadeDeleteAttribute(EdmMember edmMember)
{
var clrProperties = edmMember.MetadataProperties.FirstOrDefault(m => m.Name == "ClrPropertyInfo");
if (clrProperties == null)
return null;
var property = clrProperties.Value as PropertyInfo;
if (property == null)
return null;
return property.GetCustomAttribute<CascadeDeleteAttribute>();
}
}
@jmichas
Copy link

jmichas commented Dec 13, 2015

Thanks for this, it is really great, makes life so much easier than having to redo all my annotations as fluent code.

@nmvuong92
Copy link

Thanks a lot!

@lantirn
Copy link

lantirn commented Jan 8, 2018

Thanks for cascade wizard. Vital info for entityframework.very Easy <3

@philippfx
Copy link

How can this be applied to Entity Framework Core?

@jsancho
Copy link

jsancho commented Jul 25, 2018

a repeat of that last question. Is it feasible to adapt this logic to EF Core?
It seems as the new way is to rely on fluent mappings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment