Last active
October 26, 2022 21:55
-
-
Save tystol/20b07bd4e0043d43faff to your computer and use it in GitHub Desktop.
Entity Framework CascadeDelete using Data Annotations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] | |
public class CascadeDeleteAttribute : Attribute { } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>(); | |
} | |
} |
Thanks a lot!
Thanks for cascade wizard. Vital info for entityframework.very Easy <3
How can this be applied to Entity Framework Core?
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
Thanks for this, it is really great, makes life so much easier than having to redo all my annotations as fluent code.