7

I have this scenario: I have these classes:

public class A
{
   public int Id {get;set;}
   public virtual ICollection<B> bCollection {get; set; }
}

public class B
{
   public int Id {get;set;}

}

public class C1 : BaseClass1
{
   public int Id{get;set;}
   public virtual B B{get;set;}
}

public class C2 : BaseClass2
{
   public int Id {get;set;}
   public virtual B B {get;set;}
}

...
 public class C100 : BaseClass100
{
   public int Id {get;set;}
   public virtual B B {get;set;}
}

class A has collection of class B and class Ci have one class B and different base classes. when in class A collection there is only B that Ci not reference it, I can delete class A and all the B collection also deleted(cascade delete). But when in class A collection there is B that classes Ci has reference to it I can't delete class A instance...

My expected behavior:

Class A will be deleted and all the B collection that class A has, and if Ci has reference to some of the B in the collection it will be null in the end of the delete.( class Ci intances will not be deleted!), also I don't want to iterate throw all my Ci class to see if it has reference to the B collection that need to be deleted.

I don't want this code:

MyDbContext db=new MyDbContext();
Hash<int> bCollectionToDelete=GetBCollectionToDeleteHashSet();
var C1_db_collection= db.C1Collection.ToList();
foreach(var c1Item in C1Collection)
{
    if(bCollectionToDelete.Contains(c1Item.refrenceIdToB)
    {
       c1Item.refrenceIdToB=null;
    }
}

 var C2_db_collection= db.C2Collection.ToList();
foreach(var c2Item in C1Collection)
{
    if(bCollectionToDelete.Contains(c2Item.refrenceIdToB)
    {
       c2Item.refrenceIdToB=null;
    }
}

...
 var C100_db_collection= db.C100Collection.ToList();
foreach(var c100Item in C100Collection)
{
    if(bCollectionToDelete.Contains(c100Item.refrenceIdToB)
    {
       c100Item.refrenceIdToB=null;
    }
}

someone know how to achieve it?

ilay zeidman
  • 2,654
  • 5
  • 23
  • 45
  • Since there is no clean cascade, you will have to do it in your code. Something like `A.B.Where(b => b.C == null)` will probably fetch the correct entries for you. – TGlatzer Jan 27 '14 at 11:01
  • but B doesn't has reference to C... – ilay zeidman Jan 27 '14 at 11:03
  • Well - let C know the IDs of the referenced B, then you can make a select of all to-be-deleted Bs to detect if they are in used in C. Or create the reference - it's your class isn't it? – TGlatzer Jan 27 '14 at 11:10
  • yes it is my class, so there is no automatic whay to do it I need to iterate all my C instances and foreach C to check if its B is in the deleted B collection? – ilay zeidman Jan 27 '14 at 12:08
  • I think that last comment is really to the point. I would suggest asking the opposite way and see if you can answer it: if the connection between B & C is only stored in C, where else should it be seen? – Mike M Feb 02 '14 at 03:15
  • @Mike I understand what you saying and that is why I made a bounty because I also can't think of something else, but maybe someone will have a "magic" solution that I will not need to iterate 100 classes... – ilay zeidman Feb 02 '14 at 05:44
  • And also I think it breaks the Open/Close principle: tomorrow there will be more classes and this will result more changes in all the functions that checks if Ci has reference to B... – ilay zeidman Feb 02 '14 at 05:52
  • I don't know a lot about how EF might automate this, but if you have to do it manually, certainly you can have your own "index", in terms of any .net collection that supports named access. But it seems like the most direct thing, if not automatic, would be to add the reference inside B. – Mike M Feb 02 '14 at 19:49
  • I can't have reference in B because each Ci instance can have reference to the same B, that's mean I should have 100 Lists in B...thats not sound reasonable... – ilay zeidman Feb 02 '14 at 23:32
  • The only solution I see here is to have a List of all the C's and have C implement iDispoable to destroy the B's and whenever a C gets created add it to the List of C's – Jester Feb 03 '14 at 11:31
  • is it possible to have the nullable foregin key to `B` in those `Ci` classes? – Mat J Feb 03 '14 at 12:11
  • yes, you mean that in c you will have a forign key to B and is nullable? – ilay zeidman Feb 03 '14 at 12:21
  • @ilayzeidman yes. In that case, Can't you configure a `On Delete Set Null` for that? – Mat J Feb 03 '14 at 12:31
  • @ilayzeidman are you using fluent API for configuring the entities? – Mat J Feb 03 '14 at 12:33
  • yes I do use mix of fluent API and data annotation – ilay zeidman Feb 03 '14 at 12:35
  • 1
    The basic problem here is EF don't have an option to configure the cascade action for delete and update. So you'll have to manually update that for each table either via code or management studio. – Mat J Feb 03 '14 at 13:04
  • This looks like a pretty gnarly model. – Cory Nelson Feb 03 '14 at 17:40
  • I dont know what is gnarly...:) but this is not my model but my model have this scenario... – ilay zeidman Feb 03 '14 at 17:45
  • you are setting all the C classes deriving from different BaseClass is this the case or all the C classes derive from the same class? – Pedro.The.Kid Feb 04 '14 at 15:52
  • @Pedro.The.Kid yes different base classes – ilay zeidman Feb 04 '14 at 17:08

3 Answers3

3

This may be a case where you write a stored procedure to handle your cascading delete logic, and the EF code just makes a single call to the 'DeleteMyObject' sp and then your ef code will need to refresh its context to reload the changes immediately after.

You will pay a slight performance penalty to refresh your context, but if I had to guess moving the logic to an sp, and the performance gain of doing inter-related deletes there, will more than offset the small performance penalty of having to refresh.

Not sure if SP's are an option for you, but they are there to use when you need them.

EDIT:

Very simplified example of stored procedure that accepts the primary key of a table 'MainTable' and then deletes any related records, if they exists, from 3 'ChildTables'. You actual logic may be a bit more complicated:

CREATE PROCEDURE DeleteMyObject @Id INT
as
   BEGIN TRAN
     delete from ChildTable1 WHERE ParentId=@Id
     delete from ChildTable2 WHERE ParentId=@Id
     delete from ChildTable3 WHERE ParentId=@Id
     delete from MainTable WHERE ID=@Id

   COMMIT TRAN

Example of calling SP that is not mapped into your EF model:

SqlParameter param1 = new SqlParameter("@Id", 12345); context.Database.ExecuteSqlCommand("DeleteMyObject @Id",param1);

Further reading: http://msdn.microsoft.com/en-us/data/gg699321.aspx

E.J. Brennan
  • 45,870
  • 7
  • 88
  • 116
0

Instead of searching in all C Classes you could let B remove him self

by using an interface

public interface IRemoveB
{
    void RemoveB();
}

public class C1 : BaseClass1, IDeleteB
{
    public int Id { get; set; }
    public virtual B B { get; set; }

    #region IDeleteB Member

    public void RemoveB()
    {
        this.B = null;
    }

    #endregion
}


public class B
{
    public int Id { get; set; }
    public ICollection<IDeleteB>  list{ get; set; }

}
public class A
{
    public int Id { get; set; }
    public virtual ICollection<B> bCollection { get; set; }

    public void prepareForDelete()
    {
        foreach (var item in bCollection)
        {
            foreach (var deref in item.list)
            {
                deref.RemoveB();
            }
        }
    }
}

by using an delegate

public class A
{
    public int Id { get; set; }

    public virtual ICollection<B> bCollection { get; set; }

    public void prepareForDelete()
    {
        foreach (var item in bCollection)
        {
            item.RemoveMe();
        }
    }
}


public class B
{
    public int Id { get; set; }

    public event EventHandler Remove;

    public void RemoveMe()
    {
        EventHandler removeHandler = Remove;
        if (removeHandler != null)
        {
            removeHandler(this, EventArgs.Empty);
        }
    }
}
public class C2 : BaseClass2
{
    public int Id { get; set; }

    private B internB;

    // maybe you need an otherform to set B because of your virtual 
    public B B
    {
        get { return internB; }
        set
        {
            if (internB != value)
                if (internB != null)
                    internB.Remove -= this.RemoveB;
                else if(value != null)
                    value.Remove += this.RemoveB;
            internB = value;
        }
    }

    public void RemoveB(object sender, EventArgs args)
    {
        internB.Remove -= this.RemoveB;
        B = null;

    }
}

Edit:

to Answer your Question's

You mean thet every ci inherit from the same interface?
Yes exactly. Here are more information about Interface

and if yes ICollection<IDeleteB> is stored in the database?
No it only exist in your program. Think of it like a temporary 1:N referenztable

and in the delegate example how it is store in the database?
No it's basically the same think like the Collection

and this only work if B in A == B in C which does mean it's the same instance of the Class B

Community
  • 1
  • 1
WiiMaxx
  • 5,322
  • 8
  • 51
  • 89
  • Sound interesting your example is not completely clear to me can you clear yourself? – ilay zeidman Feb 06 '14 at 10:10
  • what do you want to know ? (which part isn't clear enough so i can focused on that :) ) – WiiMaxx Feb 06 '14 at 10:17
  • You mean thet every ci inherit from the same interface? and if yes ICollection is stored in the database? and in the delegate example how it is store in the database? – ilay zeidman Feb 06 '14 at 10:35
  • ah i see my mistake. I thought we talk always about the same B object in A and C, but when `B in A != B in C` you cant use my solution the only way i can think of is a static `List` where A and C get's there B's from so `B in A == B in C` – WiiMaxx Feb 06 '14 at 10:48
-2

you can set the option of the delete cascade to true for each class Ci in the method OnModelCreating by adding the following

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // for each class Ci write the following
    modelBuilder.Entity<Ci>()
            .HasRequired(t=>T.B)
            .WithMany()
            .WillCascadeOnDelete(true);
}

by this the delete will delete the Ci for you if exists with the B class hope it will help you

Monah
  • 6,714
  • 6
  • 22
  • 52
  • I think you missed the row: "and if Ci has reference to some of the B in the collection, it will be null in the end of the delete", I mean I dont want to delete the ci instance... – ilay zeidman Feb 04 '14 at 11:11
  • yes since on deleting B => all Ci will be deleted by cascade automatically – Monah Feb 04 '14 at 11:12