none
DbContext: Why isn't there AddOrAttach() method? RRS feed

  • Question

  • I'm using EF 4.3 from NuGet package, DatabaseFirst approach, POCO objects.

    Let's say I have objects like Category and Element, where Category has a collection of Elements (Category consists of many elements. Elements are shared between categories, they are standalone objects). Now, I create a new Category, and add some preexisting Elements.

    The problem arises when I try to save this new Category entity using the code below:

    public void SaveCategory(Category val)
            {
                using (MyContext ctx = new MyContext())
                {
                    if (val.Id == 0)
                    {
                        ctx.Categories.Add(val);
                    }
                    else
                    {
                        ctx.Categories.Attach(val);
                    }
                    ctx.SaveChanges();
                }
            }

    The database throws an error that an Id for Element already exists in the database. Obviously, it pulled the entire object graph, marking not only the Category as new, but the Elements as well, and now it tries to insert everything. The solution here would be to alter the 'Add' part so that after adding it includes the following piece:

    foreach(var el in val.Elements)
    {
        ctx.Entry(el).State = EntityState.Unchanged;
    }
    And it works. But, it is very very unpleasant work to do, since the database is quite complex, and to catch each and every collection correctly and each of the child collections recursively as well is very prone to errors to say the least. Not to mention I'd have to do this for each and every entity that gets sent for saving.

    Instead, I would like to have an AddOrAttach method, that would add the entity if Id == 0, attach if otherwise, and for every other child object do exactly the same, recursing the tree just as it does now. I'm also willing to subclass the DbSet, and change the tt to use my new class, but I don't know what exactly the Add/Attach do, and I can't call on them because they'll just pull in the whole tree.

    How can I correctly implement this method? Is there any other way around this problem?

    Tuesday, April 3, 2012 7:21 PM

All replies

  • Interesting!

    JP Cowboy Coders Unite!

    Tuesday, April 3, 2012 7:26 PM
  • Hi veljkoz,

    Welcome!

    About Insert or update Pattern, you can use the code as follows:

     class Program
        {
            static void Main(string[] args)
            {
                using (var db= new IdentityColumnContext())
                {
                    var c = new Category { Name="fafa" };
                    var p = db.Products.First();
                    c.Products.Add(p);
                    InsertOrUpdate(db,c);
                }
            }
            public static void InsertOrUpdate(DbContext context, Category category)
            {
                context.Entry(category).State = category.Id == 0 ?
                                               EntityState.Added :
                                               EntityState.Modified;
                context.SaveChanges();
            }
        }

    ==================================
     when using database generated integer primary keys it is common to treat an entity with a zero key as new and an entity with a non-zero key as existing. This pattern can be achieved by setting the entity state based on a check of the primary key value.

    ==================================

    Have a nice day.


    Alan Chen[MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, April 4, 2012 6:56 AM
    Moderator
  • Hi Alan_chen,

    What you are proposing mandates that I have to modify the collection only with the entities that are already in the context, or, looking at a bigger picture, to have all existing entities already in the context, and only then use them when working with other (new) entities. That is not possible, and a highly unlikely scenario, at least from my point of view (for example, imagine working with entities on GUI, and context is residing on another server). Furthermore, this would mean that I have to add each of the items individually, which is also painfull if the object graph is bigger.

    What I was going for is to have a way of altering the behaviour of add/attach. Is there some event/method that I can use to catch the moment that some entity gets in the context? That way I could edit the entry based on it's Id, and even use my custom flags if need arises.

    Thanks,

    Veljko

    Wednesday, April 4, 2012 7:28 AM
  • You could create an extension method with a slightly different signature than the EF add to accomplish what you want.  Because extension methods are static and the signature allows CLR to easily find the entry point you only need to create 1 in your entire project. 

    Other than that maybe your request is more geared for MSFT data group to consider this an enhancement request?


    JP Cowboy Coders Unite!

    Wednesday, April 4, 2012 2:39 PM
  • Hi,

    I'm well aware of how the extension methods work, that is not the issue. It's the implementation of that method that is the problem.

    Wednesday, April 4, 2012 4:14 PM
  • So you've made it clear a number of times this is missing function.  Why don't you open a TechNet issue instead of beating a dead horse.  Everyone here is only trying to help you and you find no alternatives.

    JP Cowboy Coders Unite!

    Wednesday, April 4, 2012 4:42 PM
  • javaman, I'm trying to find a solution - sorry if it seems as 'beating a dead horse'. I was open for alternatives as well, but none were provided.

    The problem here is that I cannot tap into the attachment process because it's all closed off behind DbSet and it's internal classes, with no events or virtual methods I could use. And after opening the Entity Framework assembly in the decompiler I've found that it's practically impossible to make it work differently without a lot of manual work, and practically writing my own EF classes from the start.

    So, for now I'm forced to go for self tracking entities, even though it's not a real solution for an enterprise, for now it would suffice. I'll leave this question unanswered until somebody comes with a better idea.

    Sunday, April 8, 2012 6:29 PM