New Posts New Posts RSS Feed: Key is already present in this dictionary error
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Key is already present in this dictionary error

 Post Reply Post Reply
Author
jlozina View Drop Down
Newbie
Newbie


Joined: 20-Jul-2011
Posts: 9
Post Options Post Options   Quote jlozina Quote  Post ReplyReply Direct Link To This Post Topic: Key is already present in this dictionary error
    Posted: 01-Oct-2012 at 7:01am
Hi
 
I am using an int as the primary key and it is genrated by the database. When I create one record it works fine but when I try to create a second record with a unit of work I get the following error. This happens when I clear the unit of work or set it to null.
 
System.ArgumentException was unhandled by user code
  HResult=-2147024809
  Message=Key is already present in this dictionary
  Source=Cocktail
  StackTrace:
       at Cocktail.WeakRefDictionary`2.Add(TKey key, TValue value)
       at Cocktail.ObjectManager`2.Add(TKey key, T obj)
       at Services.Order.WizardUnitOfWorkManager.Services.IWizardUnitOfWorkManager<Services.IWizardUnitOfWork>.Add(Int32 , IWizardUnitOfWork )
In my enity class the Id is set up this way
[DataMember]

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]

[Required]

public int Id { get; set; }

I noticed that the sample app uses a Guid for the primary key and DatabaseGeenratedOption is none. Is it possible to use the UnitOfWork with an int as the primary key?
 
Regards,
Joe
 
Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 01-Oct-2012 at 1:03pm
You need a unique surrogate or business key if you want to manage multiple units of work as demonstrated in TempHire and still use DB generated keys.

The issue with DB generated keys is that entities get a temporary key until they are saved. This violates the fundamental principle that says the identity of an entity should never change. The temporary Ids are only unique withing a single EntityManager/Unit of Work. They typically start at -100 then -101 and so on. If you create a second EntityManager/UoW it also starts at -100. That's the issue you are having here. 

If you use DB generated keys, you'll be constantly figuring out how to reconcile things in the UI. You'll need something else in your entities that is unique and can be used as the key to identify the corresponding unit of work. You can't use the temporary Ids, because they are not guaranteed to be unique. 

We generally advise against DB generated keys. They do not agree with the rich client computing paradigm where entities are created on the client. The Ids should be generated where the entity is created and once generated it shouldn't change, so the entity can always be uniquely identified. There are two ways to do this. Use a central key service or use Guids.
Back to Top
jlozina View Drop Down
Newbie
Newbie


Joined: 20-Jul-2011
Posts: 9
Post Options Post Options   Quote jlozina Quote  Post ReplyReply Direct Link To This Post Posted: 02-Oct-2012 at 3:07am
If i used a Sequence(Sql Server 2012) as a central key service like

CREATE SEQUENCE dbo.OrderSequence

START WITH 1000

INCREMENT BY 1 ;

SELECT NEXT VALUE FOR dbo.OrderSequence;

how would I call it using code first?
 
Are there any other ways to use a central key service if I want to continue using an int as a surogate key?
Back to Top
smi-mark View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
Post Options Post Options   Quote smi-mark Quote  Post ReplyReply Direct Link To This Post Posted: 02-Oct-2012 at 8:57am
I'm looking at replacing our db generated keys with such a key service.

I was planning on making an IKeyService interface, with a fakestore implementation that would just increment, and a real implementation that would call a remote service method to get the next, or next range of keys.

If you used the table based id generator from previous versions of devforce, it would do something similar. However it would only update the key on save, as far as I remember.
Back to Top
smi-mark View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
Post Options Post Options   Quote smi-mark Quote  Post ReplyReply Direct Link To This Post Posted: 02-Oct-2012 at 5:34pm
 I just wrote a little service today to do this, for my project. So far I've only done a fake store implementation. For a real implementation you would simply use a remote service method, and then on the server side execute your query to get the next id.


    public interface IKeyService<T> : IHideObjectMembers
    {
        Task AllocateRange(int count);
        Task<T> GetNext();
    }



    public class FakeStoreIntegerKeyService : IKeyService<int>
    {
        private int _nextId;
        private readonly Queue<int> _availableKeys;

        public FakeStoreIntegerKeyService()
        {
            _nextId = 1;
            _availableKeys = new Queue<int>();
        }

        #region Implementation of IKeyService<int>

        public Task AllocateRange(int count)
        {
            for (var i = 0; i < count; i++)
            {
                _availableKeys.Enqueue(_nextId++);
            }
            return TaskFns.FromResult(true);
        }

        public Task<int> GetNext()
        {
           if (_availableKeys.Count == 0)
               AllocateRange(1);

            return TaskFns.FromResult(_availableKeys.Dequeue());
        }

        #endregion
    }


I have multiple models so I just made an interface for this specific model so I'm not sharing keys between different models. You could bypass this step.


    public interface IDemoKeyService : IKeyService<int>
    {

    }

  public class DemoFakeKeyService : FakeStoreIntegerKeyService, IDemoKeyService
    {

    }


I then subclassed Factory<T> to include my Key Service


public class DemoFactory<T> : Factory<T>
        where T : Entity
    {
        private readonly IDemoKeyService _keyService;

        public DemoFactory(IEntityManagerProvider<DemoEntities> entityManagerProvider, IDemoKeyService keyService)
            : base(entityManagerProvider)
        {
            _keyService = keyService;
        }

        public override async System.Threading.Tasks.Task<T> CreateAsync(System.Threading.CancellationToken cancellationToken)
        {
            var entity = await base.CreateAsync(cancellationToken);
            cancellationToken.ThrowIfCancellationRequested();
            var key = await _keyService.GetNext();
            cancellationToken.ThrowIfCancellationRequested();
            SetKey(entity, key);
            return await TaskFns.FromResult(entity);
        }

        private void SetKey(T entity, int key)
        {
            var keyProperty = entity.EntityAspect.EntityMetadata.KeyProperties.FirstOrDefault();
            entity.EntityAspect.SetValue(keyProperty, key);
        }
    }


In my sample project I have a Customer and a CustomerAddress table. Previously I could do Customer.AddAddress() but I wasn't sure the cleanest way to implement that anymore. So I simply made a new Factory class to handle this:


    public class CustomerFactory : ICustomerFactory
    {
        private readonly DemoFactory<Customer> _customerFactory;
        private readonly DemoFactory<CustomerAddress> _customerAddressFactory;

        public CustomerFactory(IEntityManagerProvider<DemoEntities> entityManagerProvider, IDemoKeyService keyService)
        {
            _customerFactory = new DemoFactory<Customer>(entityManagerProvider, keyService);
            _customerAddressFactory = new DemoFactory<CustomerAddress>(entityManagerProvider, keyService);
        }

        #region Implementation of ICustomerFactory

        public Task<Customer> CreateCustomer()
        {
            return _customerFactory.CreateAsync(CancellationToken.None);
        }

        public async Task<CustomerAddress> CreateAddress(int customerId)
        {
            var address = await _customerAddressFactory.CreateAsync();
            address.CustomerId = customerId;
            return await TaskFns.FromResult(address);
        }

        #endregion
    }


I then expose the ICustomerFactory on my UnitOfWork. I can then simply do:


 Address = await UnitOfWork.CustomerFactory.CreateAddress(Customer);


If Marcel has a better way of doing this, I'm all ears. Hopefully this will give you enough to get started though.





Edited by smi-mark - 03-Oct-2012 at 6:08am
Back to Top
mgood View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 18-Nov-2010
Location: Emeryville, CA
Posts: 583
Post Options Post Options   Quote mgood Quote  Post ReplyReply Direct Link To This Post Posted: 03-Oct-2012 at 5:46am
Mark,
That looks pretty good. The one thing I would change is to not pass the entire Customer entity to CreateAddress. That method doesn't need a whole customer, it only needs the id.
 
public async Task<CustomerAddress> CreateAddress(int customerId)
{
   var address = await _customerAddressFactory.CreateAsync();
   address.CustomerId = customerId;
   return await TaskFns.FromResult(address);
}
Back to Top
smi-mark View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
Post Options Post Options   Quote smi-mark Quote  Post ReplyReply Direct Link To This Post Posted: 03-Oct-2012 at 6:11am
Good point. I have changed that now.

Can you change the Factory class to also find internal methods? I've set InternalsVisibleTo to my DomainServices project, but of course Factory<> only looks for Public | Static right now. I will add it as a task on the tracker.
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down