So boring that EF is still under construction but I love the multiple ways it allows me to do the thing.
I think that for small size projects, attribute decoration is ideal, and much more convenient than code mapping.
anyway I’ve found myself in a situation where I’ve a class “product” that has multiple images, and I want that when someone deletes a product, all the associated images goes away too.
For sure I can do this by code, but my project is quite small and I would like to have an attribute that does the trick
So as this is a quite challenging thing I’ve started writing my custom attribute
My goal was to replace something like this
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasMany(r => r.Images).WithOptional().WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
with something like this
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
[UniqueValue(typeof(LillaDb), "Id")] // for this one see my previous post
public virtual string Title { get; set; }
public virtual string Permalink { get; set; }
[DataType(DataType.MultilineText)]
public virtual string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
[OnDeleteCascade] // here is our new friend!
public virtual ICollection<Image> Images { get; set; }
}
to do this I’ve created a simple attribute, but the difficult part is to make it apply.. so the simple attribute contains a compelx applier function but it was very fun to program, here is the result
namespace Domain.Attributes
{
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
/// <summary>
/// Apply the "on cascade delete" behaviour on the has many mapping should be applyed only on ICollection<> property that has one to many mapping.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class OnDeleteCascadeAttribute : Attribute
{
public static void ApplyAttributeBehaviour(Type dbContextType, DbModelBuilder modelBuilder)
{
Type thisType = dbContextType;
Type modelBuilderType = typeof(DbModelBuilder);
var genericEntityMethod = modelBuilderType.GetMethod("Entity");
var repositoryTypes =
thisType
.GetProperties()
.Where(x =>
x.PropertyType.IsGenericType &&
x.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
.Select(x => x.PropertyType.GetGenericArguments()[0]);
foreach (var repositoryType in repositoryTypes)
{
var repositoryProperties =
repositoryType
.GetProperties()
.Where(x =>
IsDerivedFrom(typeof(ICollection<>), x.PropertyType) && // TODO: add more of these line to support other type of collection-like types
(x.GetCustomAttributes(typeof(OnDeleteCascadeAttribute), true).Count() > 0));
foreach (var property in repositoryProperties)
{
Type collectionType = property.PropertyType.GetGenericArguments()[0];
var entityMethod = genericEntityMethod.MakeGenericMethod(repositoryType);
object entity = entityMethod.Invoke(modelBuilder, null);
var entityHasManyMethod = entity.GetType().GetMethod("HasMany").MakeGenericMethod(collectionType);
ParameterExpression rParameter = Expression.Parameter(repositoryType, "r");
Expression lambda = Expression.Lambda(
Expression.MakeMemberAccess(
rParameter,
property),
rParameter);
object hasManyResult = entityHasManyMethod.Invoke(entity, new[] { lambda });
object withOptionalResult = hasManyResult.GetType().GetMethod("WithOptional", Type.EmptyTypes).Invoke(hasManyResult, null);
withOptionalResult.GetType().GetMethod("WillCascadeOnDelete", new[] { typeof(bool) }).Invoke(withOptionalResult, new object[] { true });
//modelBuilder.Entity<Repository>().HasMany(r => r.Property).WithOptional().WillCascadeOnDelete(true);
}
}
}
private static bool IsDerivedFrom(Type baseGeneric, Type generic)
{
if (!baseGeneric.IsGenericTypeDefinition) throw new ArgumentException("baseGeneric must be a generic type definition");
if (!generic.IsGenericType) return false;
return baseGeneric.IsAssignableFrom(generic.GetGenericTypeDefinition());
}
}
}
and finally I can change my “OnModelCreating” code like this, to apply the behaviour
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
OnDeleteCascadeAttribute.ApplyAttributeBehaviour(this.GetType(), modelBuilder);
base.OnModelCreating(modelBuilder);
}
not a a gain at all, but of course a lot of fun if you like attributes it is definitively what you are searching for
Tags: