0

I'm using EF Code First but my models have NO relationships (PKs - FKs). So I'm trying to find a way to workaround it by using EF6 Reflections in order to avoid an entry deletion that would have relationships (same property name).

  • Lookup over all my context entities in which has any specific property (FK);
  • For every entity found, check if this entity has any entry;
  • If its true, instead of deleting my entry, set a property "Canceled" as true;
  • If its false, keep entity state deleted and save my context changes;

    public override int SaveChanges()
    {
        foreach (var myEntity in ChangeTracker.Entries<IAuditable>())
        {
            if (myEntity.State == EntityState.Deleted)
            {
                ObjectContext objContext = ((IObjectContextAdapter)this).ObjectContext;
                var container = objContext.MetadataWorkspace.GetEntityContainer(objContext.DefaultContainerName, DataSpace.CSpace);
    
                var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(myEntity.Entity);
                var entityKeys = objectStateEntry.EntityKey.EntityKeyValues;
                var entity = myEntity;
                var hasAnyFk = false;
                foreach (var entityKey in entityKeys)
                {
                    if (hasAnyFk)
                    {
                        break;
                    }
    
                    var keyName = entityKey.Key;
                    foreach (var entitySet in container.EntitySets)
                    {
                        hasAnyFk = entitySet.ElementType.Members.Any(es => es.Name == keyName);
                        if (hasAnyFk)
                        {
                            break;
                        }
                    }
                }
    
                if (hasAnyFk)
                {
                    var deletedProperty = myEntity.OriginalValues.PropertyNames.Where(p => myEntity.Property(p).Name == "Deleted").FirstOrDefault();
                    if (deletedProperty != null)
                    {
                        myEntity.State = EntityState.Modified;
                        myEntity.CurrentValues[deletedProperty] = true;
                    }
                }
            }
        }
    

    return base.SaveChanges(); }

  • If you are doing code first why not just add the relationships? This seems like a really strange pattern to choose. In any case, if your EF classes are in one assembly then I suppose you can scan the assembly for any classes that have the specified property. You'll want to cache all this information so you aren't scanning each time though. – stephen.vakil Dec 09 '16 at 18:14
  • @stephen.vakil even adding the relationships, I don't want use on cascade features. I guess it's not strange considering what I want to do - setting property flags instead of literally delete an entry. By setting the relationships won't solve my needing. – Rubens Mussi Cury Dec 09 '16 at 18:40
  • It will help. One way you have to scan the entire assembly for like-named fields. The other you can interrogate navigation properties using the built-in EF libraries. You don't have to enable cascade delete in EF. – stephen.vakil Dec 09 '16 at 18:49
  • @stephen.vakil indeed, it would help but the app doesn't have the proper relations set and I'm trying to find a way to avoid unexpected deletions. If you would have any better approach, it's going to be very welcome. – Rubens Mussi Cury Dec 09 '16 at 19:03
  • 1
    Look at System.Data.Metadata.Edm and samples of querying it, e.g. http://stackoverflow.com/questions/15718301/how-i-can-read-ef-dbcontext-metadata-programmatically – stephen.vakil Dec 09 '16 at 19:07
  • @stephen.vakil I appreciate your hints. If you could, please have a look my updated post. My only trouble now is about how to make a Select in order to check if the found entity with has any entry. `hasAnyFk = entitySet.ElementType.Members.Any(es => es.Name == keyName); if (hasAnyFk) { ?? How to make a dynamic select using this entity ?? }` – Rubens Mussi Cury Dec 12 '16 at 15:19
  • After your edit the problem isn't clear anymore. The question now basically is a requirement description and a piece of code that executes it (or doesn't? -- but that's not clear anymore). – Gert Arnold Dec 12 '16 at 16:08
  • @Gert Arnold, the only thing I did on my edition was to put the whole code together and includes what I asked in my 2 first topics. What you answered was good and I really appreciate it, but please notice your code refers the topics 3 and 4 of my question. – Rubens Mussi Cury Dec 12 '16 at 18:10
  • Sure, that's OK, it's just that it's hard to piece together from your code which part you're still having troubles with. By the way, I think that adding relationships in the class model is a more secure way to control deletions than property name matching. It certainly makes life a lot easier, not only for deletions, but also for querying. – Gert Arnold Dec 12 '16 at 18:17
  • I have no questions about that putting relationships would help and make this much easier... but I can't do that for now. Its a temporarily workaround. If you have something to suggest I'd appreciate. – Rubens Mussi Cury Dec 12 '16 at 18:30

1 Answers1

0

You can handle this is an overload of SaveChanges:

public override int SaveChanges()
{
    foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State == 
                                   System.Data.Entity.EntityState.Deleted).ToList())
    {
        var delPropName = "IsDeleted";
        if (entry.OriginalValues.PropertyNames.Contains(delPropName))
        {
            var delProp = entry.Property(delPropName);
            delProp.CurrentValue = true;
            entry.State = System.Data.Entity.EntityState.Modified;
        }
    }
    return base.SaveChanges();
}

Here, entry.OriginalValues.PropertyNames is used to check if the property exists in the entity and then its value is set and the entry's state is changed to Modified. Note that I loop through this.ChangeTracker.Entries() after applying ToList(), otherwise the content of the collection changes while looping through it.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • Thank you for your answer. However I had already done this part - checked deleted property and setting true instead of deleting the entry. *My main challenge consists in - Lookup over all my context entities in which has any specific property (FK); - For every entity found, check if this entity has any entry;**. If I have no related entry with same PK, I can delete. – Rubens Mussi Cury Dec 12 '16 at 14:56
  • Any idea how to do this in Efcore? `entry.OriginalValues.PropertyNames` are not available – Hamza Khanzada Jan 30 '21 at 20:52
  • @HamzaKhanzada entry.Members.Any(f => f.Metadata.Name == "ModifiedOn") – Aarif Dec 22 '21 at 03:03