Nov 3 2011

OnDeleteCascade Attribute for Entity Framework 4.2

Category: — Duke @ 19:22

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 A bocca aperta 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 Sorriso if you like attributes it is definitively what you are searching for Sorriso

Tags:

1.
Haiti et le cholera Haiti et le cholera says:

Can you tell us more about this? I'd love to find out some additional information.

My page  Haiti et le cholera - impotenceanddiabeteshelp.com/.../

2.
How to make extra money online How to make extra money online says:

Wow, that's what I was searching for, what a material! present here at this blog, thanks admin of this web site.

My weblog ...  How to make extra money online - http://mycashtown.com/

3.
Jerald Jerald says:

Great delivery. Sound arguments. Keep up the great effort.

Review my webpage; circa il colera ( Jerald - argumentompharma.com/acquisto_tetracycline_it_1/ )

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading