Search code examples
c#sql-servergenericsrecursionef-power-tools

cascading delete using recursive method


I need a way to delete a user and all it's constraints in other tables. I know of cascading delete in sql server but I can't use this for some reasons.

Let's say a user have several orders, each of that orders have some products. so I send the user in the method, it finds the orders, in a foreach loop it goes into that order and so on.

So I'm up to write a method to do this recursively; It must receive an object and find all its relations and goes through all of it.

I use EF power tools reverse engineer code first to generate these from database. here is my class:

public partial class myDbContext: DbContext
{
    ...

    public DbSet<Users> Users{ get; set; }
    public DbSet<Orders> Orders{ get; set; }
    public DbSet<Products> Products{ get; set; }
    public DbSet<OrderProducts> OrderProducts{ get; set; }

    ...
}
public partial class Users
{
    public int UserID { get; set; }
    public string Username { get; set; }
    public virtual ICollection<Orders> Orders{ get; set; }
}
public partial class Orders
{
    public int OrderID { get; set; }
    public virtual Users users { get; set; }
    public virtual ICollection<OrderProducts> OPs { get; set; }
}
public partial class OrderProducts
{
    public int OPID { get; set; }
    public virtual Orders orders { get; set; }
    public virtual Product products { get; set; }
}

using this method I am able to find all virtual ICollections in the user object.

private void DeleteObjectAndChildren(object parent) 
    {
        using (var ctx = new myDbContext())
        {
            Type t = parent.GetType();

            //these are all virtual properties of parent
            var properties = parent.GetType().GetProperties().Where(p => p.GetGetMethod().IsVirtual);
            foreach (var p in properties)
            {
                var collectionType = p.PropertyType.GetGenericArguments();
                //collectionType[0] gives me the T type in ICollection<T>

                //what to do next?
            }
        }
    }

using collectionType[0] I see that it is Orders, I must have something like this to be able to query:

var childType = ctx.Set<collectionType[0]>;

but I can't get the right casting and all.

and if this is completely wrong, any hints would be appreciated to get me to the right direction.


Solution

  • In case of someone need what I was looking for. This class gets an object and finds all relations with ICollection<T> type and traverse through them recursively and deletes base childs.

    public class RemoveEntityAndAllRelations
    {
        private DbContext _context;
    
        public void SetContext(DbContext ctx)
        {
            _context = ctx;
        }
    
        public void RemoveChildren(object parent)
        {
            Type obj = parent.GetType();
    
            MethodInfo method = typeof(RemoveUserAndAllRelations).GetMethod("GetICollections");
            MethodInfo generic = method.MakeGenericMethod(obj);
    
            var properties = (PropertyInfo[])generic.Invoke(this, null);
            if (properties.Any())
            {
                foreach (PropertyInfo propertyInfo in properties)
                {
                    object child = propertyInfo.GetValue(parent, null);
                    RemoveChildren(child);
                }
    
                try
                {
                    dynamic dd = parent;
                    _context.Entry(dd[0]).State = EntityState.Deleted;
                    _context.SaveChanges();
                }
                catch (Exception)
                {
                      //do what you like
                }
            }
            else
            {
                try
                {
                    dynamic dd = parent;
                    _context.Entry(dd[0]).State = EntityState.Deleted;
                    _context.SaveChanges();
                }
                catch (Exception)
                {
                      //do what you like
                }
            }
        }
        public PropertyInfo[] GetICollections<TEntity>() where TEntity : class
        {
            return typeof(TEntity).GetProperties().Where(m =>
                m.PropertyType.IsGenericType &&
                m.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
            ).ToArray();
        }
    }
    

    and use it like

    using (var ctx = new myDbContext())
    {
        var user = ctx.Users.FirstOrDefault(x => x.UserID == userId);
        RemoveEntityAndAllRelations controller = new RemoveUserAndAllRelations();
    
        controller.SetContext(ctx);
        controller.RemoveChildren(user);
    
        ctx.Core_Users.Remove(user);
        ctx.SaveChanges();
    }