none
Attach an object with two properties related to same object RRS feed

  • Question

  • Hi,

    I have a detached object (Task) with two properties:

    - StatusOne

    - StatusTwo

    Those properties has the same value, a reference to an object Status {StatusId=1}.

    When I try to attach Task object:

    context.Tasks.Attach(task);

    I get an error: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."

    If i set property StatusTwo to null, no error is raised. So I think that Attach method is trying to attach again Status {StatusId=1} object to the context.

    Are there anyway to avoid that?

    Thanks and best regards


    @ayus

    Monday, March 26, 2012 3:25 PM

Answers

  • Hi Alberto;

    As Tyler has stated in his post the issue is caused because you are using the MergeOption.NoTracking. This causes the state manager to not keep track of the entities in the ObjectContext and because of this when Entity Framework materializes entities from a query all entities are materialized even when they are the same object. When tracking is enabled as entities are added to the ObjectContext they are first checked to see if they already exist in the ObjectContext and if they do then the duplicate is thrown away and the one in the ObjectContext is used.

    Seeming that MergeOption.NoTracking is not really an issue in this case I would not use it and get the benefits of it.

        


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    • Marked as answer by Alberto Ayuso Tuesday, March 27, 2012 3:33 PM
    Tuesday, March 27, 2012 2:44 PM
  • Hi Alberto,

    The exception is thrown because _internalTask.StatusOne and _internalTask.StatusTwo are two different instances of the same entity. This happens because you're using MergeOption.NoTracking. If you use any other MergOption then the context will make sure that both Navigation Properties have the same instance assigned to them. I assume you're using NoTracking for performance, but I really can't see it making a difference when retrieving just 3 entities.

    If you'd like to keep NoTracking then you could simply set one or both Navigation Properties to null before attaching the object to the context for saving.

    Regards,

    Tyler

    • Marked as answer by Alberto Ayuso Tuesday, March 27, 2012 3:33 PM
    Tuesday, March 27, 2012 9:37 AM

All replies

  •   

    Can you show the code for the detached object and how it is instantiated and how you instantiate the two objects attached to the properties StatusOne and StatusTwo?

      


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Monday, March 26, 2012 4:04 PM
  • Yes, of course.

    I retrieve Task object with this code:

    ctx.Tasks.MergeOption = MergeOption.NoTracking;
    var task = (from t in ctx.Tasks
                .Include("StatusOne")
                .Include("StatusTwo")
                where t.TaskId == taskId
                select d).SingleOrDefault();
    return task;

    The context is disposed after that.

    Then I update some properties in Task object and try to attach to another context instance, with this code:

    ctx.Task.Attach(task);
    ctx.ObjectStateManager.ChangeObjectState(task, System.Data.EntityState.Modified);

    In ctx.Task.Attach(task) I get the error.

    Thanks Fernando


    @ayus

    Monday, March 26, 2012 4:20 PM
  • I forgot to mention that when I update some properties, none are StatusOne or StatusTwo properties.

    @ayus


    Monday, March 26, 2012 4:23 PM
  • Hi Alberto;

    So these two Include's point to two different tables?

    .Include("StatusOne")
    .Include("StatusTwo")

    Can you post the schema for three tables.

    Thanks

    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Monday, March 26, 2012 4:31 PM
  • Hi Fernando,

    No, it points to same table (Status):

    Status (0..1) -----> (*) Task [Navigation property in the model is StatusOne]

    Status (0..1) -----> (*) Task [Navigation property in the model is StatusTwo]

    Thanks


    @ayus

    Monday, March 26, 2012 4:36 PM
  • Hi Alberto;

    I'm sorry when I said if you can post the schema I ment something like the following so I can create the tables as you have them so that I can recreate the issues here and see what is happening.

    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    CREATE TABLE [dbo].[Customers](
    	[CustomerID] [nchar](5) NOT NULL,
    	[CompanyName] [nvarchar](40) NOT NULL,
    	[ContactName] [nvarchar](30) NULL,
    	[ContactTitle] [nvarchar](30) NULL,
    	[Address] [nvarchar](60) NULL,
    	[City] [nvarchar](15) NULL,
    	[Region] [nvarchar](15) NULL,
    	[PostalCode] [nvarchar](10) NULL,
    	[Country] [nvarchar](15) NULL,
    	[Phone] [nvarchar](24) NULL,
    	[Fax] [nvarchar](24) NULL,
     CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED 
    (
    	[CustomerID] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    
    GO

      


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Monday, March 26, 2012 5:05 PM
  • Hi Fernando,

    Sorry for the delay. Here you have the script for the database sample (SampleDB):

    -- --------------------------------------------------
    -- Entity Designer DDL Script for SQL Server 2005, 2008, and Azure
    -- --------------------------------------------------
    SET QUOTED_IDENTIFIER OFF;
    GO
    USE [SampleDB];
    GO
    IF SCHEMA_ID(N'dbo') IS NULL EXECUTE(N'CREATE SCHEMA [dbo]');
    GO
    -- --------------------------------------------------
    -- Dropping existing FOREIGN KEY constraints
    -- --------------------------------------------------
    IF OBJECT_ID(N'[dbo].[FK_Task_Status]', 'F') IS NOT NULL
        ALTER TABLE [dbo].[Task] DROP CONSTRAINT [FK_Task_Status];
    GO
    IF OBJECT_ID(N'[dbo].[FK_Task_Status1]', 'F') IS NOT NULL
        ALTER TABLE [dbo].[Task] DROP CONSTRAINT [FK_Task_Status1];
    GO
    -- --------------------------------------------------
    -- Dropping existing tables
    -- --------------------------------------------------
    IF OBJECT_ID(N'[dbo].[Status]', 'U') IS NOT NULL
        DROP TABLE [dbo].[Status];
    GO
    IF OBJECT_ID(N'[dbo].[Task]', 'U') IS NOT NULL
        DROP TABLE [dbo].[Task];
    GO
    -- --------------------------------------------------
    -- Creating all tables
    -- --------------------------------------------------
    -- Creating table 'Status'
    CREATE TABLE [dbo].[Status] (
        [StatusId] int IDENTITY(1,1) NOT NULL,
        [Name] varchar(max)  NOT NULL
    );
    GO
    -- Creating table 'Tasks'
    CREATE TABLE [dbo].[Tasks] (
        [TaskId] int IDENTITY(1,1) NOT NULL,
        [Name] varchar(max)  NOT NULL,
        [StatusOneId] int  NULL,
        [StatusTwoId] int  NULL
    );
    GO
    -- --------------------------------------------------
    -- Creating all PRIMARY KEY constraints
    -- --------------------------------------------------
    -- Creating primary key on [StatusId] in table 'Status'
    ALTER TABLE [dbo].[Status]
    ADD CONSTRAINT [PK_Status]
        PRIMARY KEY CLUSTERED ([StatusId] ASC);
    GO
    -- Creating primary key on [TaskId] in table 'Tasks'
    ALTER TABLE [dbo].[Tasks]
    ADD CONSTRAINT [PK_Tasks]
        PRIMARY KEY CLUSTERED ([TaskId] ASC);
    GO
    -- --------------------------------------------------
    -- Creating all FOREIGN KEY constraints
    -- --------------------------------------------------
    -- Creating foreign key on [StatusOneId] in table 'Tasks'
    ALTER TABLE [dbo].[Tasks]
    ADD CONSTRAINT [FK_Task_Status]
        FOREIGN KEY ([StatusOneId])
        REFERENCES [dbo].[Status]
            ([StatusId])
        ON DELETE NO ACTION ON UPDATE NO ACTION;
    -- Creating non-clustered index for FOREIGN KEY 'FK_Task_Status'
    CREATE INDEX [IX_FK_Task_Status]
    ON [dbo].[Tasks]
        ([StatusOneId]);
    GO
    -- Creating foreign key on [StatusTwoId] in table 'Tasks'
    ALTER TABLE [dbo].[Tasks]
    ADD CONSTRAINT [FK_Task_Status1]
        FOREIGN KEY ([StatusTwoId])
        REFERENCES [dbo].[Status]
            ([StatusId])
        ON DELETE NO ACTION ON UPDATE NO ACTION;
    -- Creating non-clustered index for FOREIGN KEY 'FK_Task_Status1'
    CREATE INDEX [IX_FK_Task_Status1]
    ON [dbo].[Tasks]
        ([StatusTwoId]);
    GO
    -- --------------------------------------------------
    -- Script has ended
    -- --------------------------------------------------

    And this is the script for sample data:

    USE [SampleDB]
    GO
    INSERT INTO [SampleDB].[dbo].[Status] ([Name]) VALUES ('Status A')
    INSERT INTO [SampleDB].[dbo].[Tasks] ([Name],[StatusOneId],[StatusTwoId]) VALUES ('Task A',1,1)
    GO

    When I created the edmx I renamed Navigation Properties from Task.Status to Task.StatusOne and from Task.Status1 to Task.StatusTwo.

    So, I retrieve the Task (TaskId=1) with this code (button1_Click):

    using (var ctx = new SampleDBEntities())
                {
                    ctx.Tasks.MergeOption = System.Data.Objects.MergeOption.NoTracking;
                    _internalTask = (from t in ctx.Tasks.Include("StatusOne")
                                                        .Include("StatusTwo")
                                     where t.TaskId == 1
                                     select t).SingleOrDefault();
                }

    And updated _internalTask later with this code (button2_Click):

    using (var ctx = new SampleDBEntities())
                {
                    try
                    {
                        ctx.Tasks.Attach(_internalTask);
                        _internalTask.Name += "A";
                        ctx.SaveChanges();
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex.Message);
                    }
                }

    When include the line .Include("StatusTwo") , I get the error "An object with the same key already exists..." in ctx.Task.Attach(_internalTask);

    If I comment .Include("StatusTwo") update is completed sucessfully.

    Thanks again for your support.

    Best regards


    @ayus


    Tuesday, March 27, 2012 7:56 AM
  • Hi Alberto,

    The exception is thrown because _internalTask.StatusOne and _internalTask.StatusTwo are two different instances of the same entity. This happens because you're using MergeOption.NoTracking. If you use any other MergOption then the context will make sure that both Navigation Properties have the same instance assigned to them. I assume you're using NoTracking for performance, but I really can't see it making a difference when retrieving just 3 entities.

    If you'd like to keep NoTracking then you could simply set one or both Navigation Properties to null before attaching the object to the context for saving.

    Regards,

    Tyler

    • Marked as answer by Alberto Ayuso Tuesday, March 27, 2012 3:33 PM
    Tuesday, March 27, 2012 9:37 AM
  • Hi Alberto;

    As Tyler has stated in his post the issue is caused because you are using the MergeOption.NoTracking. This causes the state manager to not keep track of the entities in the ObjectContext and because of this when Entity Framework materializes entities from a query all entities are materialized even when they are the same object. When tracking is enabled as entities are added to the ObjectContext they are first checked to see if they already exist in the ObjectContext and if they do then the duplicate is thrown away and the one in the ObjectContext is used.

    Seeming that MergeOption.NoTracking is not really an issue in this case I would not use it and get the benefits of it.

        


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    • Marked as answer by Alberto Ayuso Tuesday, March 27, 2012 3:33 PM
    Tuesday, March 27, 2012 2:44 PM
  • Hi Fernando & Tyler:

    That's right! The problem is because MergeOption.NoTracking.

    I understand now the source of the problem.

    Thanks a lot!


    @ayus

    Tuesday, March 27, 2012 3:33 PM
  •  

    Not a problem Alberto, always glad to help.

    Have a great day.

     


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Tuesday, March 27, 2012 3:45 PM