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

 

1
2
3
4
5
6
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
 
    modelBuilder.Entity<Product>().HasMany(r => r.Images).WithOptional().WillCascadeOnDelete(true);           
    base.OnModelCreating(modelBuilder);
}

with something like this

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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

 

1
2
3
4
5
6
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