tags:

views:

1132

answers:

5

edit: in case anyone is wondering, the actionhandler invokes code that creates and disposes the same kind of datacontext, in case that might have anything to do with this behaviour. the code doesn't touch the MatchUpdateQueue table, but i figure i should mention it just in case.

double edit: everyone who answered was correct! i gave the answer to the respondent who suffered most of my questioning. fixing the problem allowed another problem (hidden within the handler) to pop up, which happened to throw exactly the same exception. whoops!

I'm having some issues with deleting items in LINQ. The DeleteOnSubmit call in the code below causes a LINQ Exception with the message "Cannot add an entity with a key that is already in use." I'm not sure what I'm doing wrong here, it is starting to drive me up the wall. The primary key is just an integer autoincrement column and I have no other problems until I try to remove an item from the database queue. Hopefully I'm doing something painfully retarded here that is easy to spot for anyone who isn't me!

static void Pacman()
{
    Queue<MatchUpdateQueue> waiting = new Queue<MatchUpdateQueue>();

    events.WriteEntry("matchqueue worker thread started");

    while (!stop)
    {
        if (waiting.Count == 0)
        {
            /* grab any new items available */
            aDataContext db = new aDataContext();
            List<MatchUpdateQueue> freshitems = db.MatchUpdateQueues.OrderBy(item => item.id).ToList();

            foreach (MatchUpdateQueue item in freshitems)
                waiting.Enqueue(item);
            db.Dispose();
        }
        else
        {
            /* grab & dispatch waiting item */
            MatchUpdateQueue item = waiting.Peek();
            try
            {
                int result = ActionHandler.Handle(item);
                if (result == -1)
                    events.WriteEntry("unknown command consumed : " + item.actiontype.ToString(), EventLogEntryType.Error);

                /* remove item from queue */
                waiting.Dequeue();

                /* remove item from database */
                aDataContext db = new aDataContext();
                db.MatchUpdateQueues.DeleteOnSubmit(db.MatchUpdateQueues.Single(i => i == item));
                db.SubmitChanges();
                db.Dispose();
            }
            catch (Exception ex)
            {
                events.WriteEntry("exception while handling item : " + ex.Message, EventLogEntryType.Error);
                stop = true;
            }
        }

        /* to avoid hammering database when there's nothing to do */
        if (waiting.Count == 0)
            Thread.Sleep(TimeSpan.FromSeconds(10));
    }

    events.WriteEntry("matchqueue worker thread halted");
}
A: 

Use:

aDataContext db = new aDataContext();
item = new MatchUpdateQueue { id=item.id }; // <- updated
db.MatchUpdateQueues.Attach(item);
db.MatchUpdateQueues.DeleteOnSubmit(item);
db.SubmitChanges();

Since you are using a new datacontext it doesn't know that the object is already in the db.

chris
I tried this, but it didn't work. Same exception comes up.
Like Quintin Robinson noted it's still attached to the other datacontext. You can create a new Item with only the itemid. If you use Quintins method you will have one more query to the database.
chris
i had tried something similar to this, but without the explicit attach. i have just tried writing exactly as you have posted, with update, and the same exception is thrown
That's very strange, I have basically the same code running on a production server without problems.
chris
+1  A: 

You could do something to the effect of

db.MatchUpdateQueues.DeleteOnSubmit(db.MatchUpdateQueues.Single(theItem => theItem == item));

Just a note as other answers hinted towards Attach.. you will not be able to use attach on a context other then the original context the item was received on unless the entity has been serialized.

Quintin Robinson
I used to havedb.MatchUpdateQueues.DeleteOnSubmit(db.MatchUpdateQueues.FirstOrDefault(x => x.id == item.id));but that threw up the same exception, so I reverted to the simplest code giving the same error.
If you attempt the code I've provided and it throws an exception can you post the exception and I will try and help you resolve it?
Quintin Robinson
the code: db.MatchUpdateQueues.DeleteOnSubmit(db.MatchUpdateQueues.Single(x => x == item));the exception:exception while handling item : Cannot add an entity with a key that is already in use.
Is the MatchUpdateQueue.id field the PK for the table?
Quintin Robinson
yes, i double checked to be certain :)
I have to say this is curious.. has the partial datacontext been extended, like is there anything in partial void OnCreated() ? Even that wouldn't make sense, but it is almost as though the object has been pulled and is trying to be re-attached.
Quintin Robinson
The only other thing I can think of is that the DBML might be wrong, is the id column in the DBML designer set to Auto Generated Value = True, Auto-Sync = OnInsert, Primary Key = True, Server Data Type = ... NOT NULL IDENTITY ?
Quintin Robinson
yep, all of those properties match
I'm out of immediate ideas, let me sit on it a while.
Quintin Robinson
no worries, i think i will go get a coffee :) maybe i'll abandon linq for this component
I'm glad you finally found your issue!
Quintin Robinson
i don't want to create a whole new question for this... the other problem was caused by trying to use Attach to update a table that had two primary key columns, one an integer, one a guid. had delete then insert to fix that. is that a regular problem for linq newbies that you might know the answer?
A: 

Try wrapping the entire inside of the while loop in a using statement for a single data context:

Queue<MatchUpdateQueue> waiting = new Queue<MatchUpdateQueue>();
events.WriteEntry("matchqueue worker thread started");
while (!stop)
{
    using (var db = new aDataContext())
    {
      if (waiting.Count == 0)
      {
        /* grab any new items available */
           List<MatchUpdateQueue> freshitems = db.MatchUpdateQueues
                                                 .OrderBy(item => item.id)
                                                 .ToList();
           foreach (MatchUpdateQueue item in freshitems)
                waiting.Enqueue(item);
      }
      ...
   }
}
tvanfosson
using a single datacontext for the entire thread still throws up the same exception ! it seems that i must be missing something out of the scope of the code i posted, since it isn't exhibiting sane behaviour
just to be explicit, the code is failing the very first time it tries to remove an item, and i meant 'loop' instead of thread :)
A: 

Remove the first db.Dispose() dispose. It can be the problem code because the entities keep a reference to their data context, so you don't want to dispose it while you are still be working with the instances. This won't affect connections, as they are open/closed only when doing operations that need them.

Also don't dispose the second data context like that, since an exception won't call that dispose code anyway. Use the using keyword, which will make sure to call dispose whether or not an exception occurs. Also grab the item from the db to delete it (to avoid having to serialize/deserialize/attach).

using (aDataContext db = new aDataContext())
{
   var dbItem = db.MatchUpdateQueues.Single(i => i.Id == item.Id);
   db.MatchUpdateQueues.DeleteOnSubmit(dbItem);
   db.SubmitChanges();
}
eglasius
removed first db.disposeused Single method to select item to deletedidn't use using keyword, after exception program exits :)same exception is thrown. had similar thoughts about references to dbcontexts, but it doesn't seem to be the problem
@glipquux updated the Single expression (was doing == at the class level), but I guess you tweaked it a bit when you tried it. I would look at any special configuration on the dbml. Try re-adding the db entities in the designer. Its a really weird scenario, are you positive it fails on DeleteOn?
eglasius
A: 

Hi everybody, try this if your TEntity's (here Area) Primary Key is of type Identity column; Just it, without any change in your SP or Model:

    public void InitForm()
    {
        'bnsEntity is a BindingSource and cachedAreas is a List<Area> created from dataContext.Areas.ToList()
        bnsEntity.DataSource = cachedAreas;
        'A nominal ID
        newID = cachedAreas.LastOrDefault().areaID + 1;
        'grdEntity is a GridView
        grdEntity.DataSource = bnsEntity;
    }

    private void tsbNew_Click(object sender, EventArgs e)
    {
        var newArea = new Area();
        newArea.areaID = newID++;
        dataContext.GetTable<Area>().InsertOnSubmit(newArea);
        bnsEntity.Add(newArea);
        grdEntity.MoveToNewRecord();
    }

'Bardaan

Bardaan