Link to home
Start Free TrialLog in
Avatar of ApexCo
ApexCoFlag for United States of America

asked on

Updating LINQ, DataContext Issue

I'm using a generic class (that I did not write) to work with LINQ. So far, it's really working out well, but I'm having an issue updating objects.

So here is the code for retrieving an object, and also for updating it.

The issue is I'm getting an error back saying "Cannot add an entity with a key that is already in use."

I am not sure how to fix this. I think the problem is because I have this class in a DataObjects Project as the middle tier and I can't seem to figure it out. The article I got the class from claims this works with disconnected records, but I don't know how to kill off the datacontext that is in use.
public static T SelectByPK<T>(String id) where T : class
        {
            try
            {

                DataClassesDataContext context = new DataClassesDataContext();
                    

                    // get the table by the type passed in
                    var table = context.GetTable<T>();

                    // get the meta model mappings (database domain objects)
                    MetaModel modelMap = table.Context.Mapping;

                    // get the data members for this type
                    ReadOnlyCollection<MetaDataMember> dataMembers = modelMap.GetMetaType(typeof(T)).DataMembers;

                    // find the primary key field name
                    // by checking for IsPrimaryKey
                    string pk = (dataMembers.Single<MetaDataMember>(m => m.IsPrimaryKey)).Name;

                    // return a single object where the id argument
                    // matches the primary key field value
                    return table.SingleOrDefault<T>(delegate(T t)
                    {
                        String memberId = t.GetType().GetProperty(pk).GetValue(t, null).ToString();
                        return memberId.ToString() == id.ToString();
                    });
                    
            }

            catch (Exception)
            {
                throw;
            }
        }





 public static void Update<T>(T item) where T : class
        {
            try
            {
                // create a new instance of the object
                Object newObj = Activator.CreateInstance(typeof(T), new object[0]);

                PropertyDescriptorCollection originalProps = TypeDescriptor.GetProperties(item);

                // set the new object to match the passed in object
                foreach (PropertyDescriptor currentProp in originalProps)
                {
                    if (currentProp.Attributes[typeof(System.Data.Linq.Mapping.ColumnAttribute)] != null)
                    {
                        object val = currentProp.GetValue(item);
                        currentProp.SetValue(newObj, val);
                    }
                }

                

                // attach the new object to a new data context and
                // call submit changes on the context
                using (DataClassesDataContext context = new DataClassesDataContext())
                {

                    var table = context.GetTable<T>();
                    table.Attach((T)newObj, true);
                    context.SubmitChanges();
                }
            }
            catch (Exception)
            {
                throw;
            }

        }

Open in new window

Avatar of ApexCo
ApexCo
Flag of United States of America image

ASKER

And here is my current implementation.



var p = Repository.SelectByPK<Property>(editId);

                    p.listingTitle = txtListingTitle.Text;
                    p.status = int.Parse(dropStatus.SelectedValue);
                    p.propertyStyle = int.Parse(dropListingType.SelectedValue);
                    p.listingType = int.Parse(dropListingType.SelectedValue);
                    p.description = EditDescription.Content;
                    p.lotSize = txtLotSize.Text;
                    p.squarefeet = txtSquareFeet.Text;
                    p.bathrooms = dropBathrom.SelectedValue;
                    p.bedrooms = int.Parse(dropBedroom.SelectedValue);
                    p.address1 = txtAddress.Text;
                    p.zipcode = txtZipCode.Text;
                    p.state = int.Parse(dropState.SelectedValue);
                    p.city = int.Parse(dropCity.SelectedValue);
                    p.price = txtPrice.Text;
                    p.yearBuild = int.Parse(txtYearBuilt.Text);
                    p.GarageType = int.Parse(dropGarage.SelectedValue);
                    p.basementType = int.Parse(dropBasement.SelectedValue);
                    p.lastUpdated = DateTime.Now;

                    Repository.Update<Property>(p);

                    Response.Redirect("propertylist.aspx", false);

Open in new window

Avatar of Bob Learned
I usually create a single class, with a DataContext at the root level, that is shared with every method, and property.  I don't usually have any problem with this configuration, and there is a lot of code there to sift through, in order to find a needle in a haystack...
Avatar of ApexCo

ASKER

Hi.

Well unfortunately all of the code for this is in a single class and the datacontext is at the root level along with it.

I think it's got something to do with the fact that it is in another project? But I can't seem to pin it down yet.


Thanks.
You have defined a DataContext in the SelectByPK method:

    DataClassesDataContext context = new DataClassesDataContext();

Update<T> method:

    using (DataClassesDataContext context = new DataClassesDataContext())

If you have a module-level DataContext, then you don't need to create local instances inside of the methods.  Just use the module-level instance.
Avatar of ApexCo

ASKER

Tried the module level context and still receive the same error.
When you come to the DataContext.SubmitChanges, what does the DataContext.GetChangeSet look like?
Avatar of ApexCo

ASKER

It looks exactly the same as the data I pulled back from the database, the original object and that are identical.

It seems to be ignoring any changes I have made to the fields on the add/edit page I have. If I change a textbox or dropdown value, stepping through the code shows them to be the same as their original value.


Avatar of ApexCo

ASKER

I misspoke.

I didn't do the GetChangeSet correctly, but after adding it in properly I can see I have one update. The problem is that it only updates the p.lastUpdated property, and I have no idea why.

I debug, look at the web page itself and I see the new values, but when I step through the code, the values I'm seeing are the same as the original. I went ahead and took out the class I was using and just did an attempt to directly update right from that section, and exact same results in both places.

If I change the zipcode (or anything), why is it not showing up in the code properly when I debug? Only date update applies??

DataClassesDataContext context = new DataClassesDataContext();

                    var p = context.Properties.First(x => x.propertyId == int.Parse(editId));

                    p.listingTitle = txtListingTitle.Text;
                    p.status = int.Parse(dropStatus.SelectedValue);
                    p.propertyStyle = int.Parse(dropListingType.SelectedValue);
                    p.listingType = int.Parse(dropListingType.SelectedValue);
                    p.description = EditDescription.Content;
                    p.lotSize = txtLotSize.Text;
                    p.squarefeet = txtSquareFeet.Text;
                    p.bathrooms = dropBathrom.SelectedValue;
                    p.bedrooms = int.Parse(dropBedroom.SelectedValue);
                    p.address1 = txtAddress.Text;
                    p.zipcode = txtZipCode.Text;
                    p.state = int.Parse(dropState.SelectedValue);
                    p.city = int.Parse(dropCity.SelectedValue);
                    p.price = txtPrice.Text;
                    p.yearBuild = int.Parse(txtYearBuilt.Text);
                    p.GarageType = int.Parse(dropGarage.SelectedValue);
                    p.basementType = int.Parse(dropBasement.SelectedValue);
                    p.pool = dropPool.SelectedValue;
                    p.lastUpdated = DateTime.Now;

                   //Repository.Update<Property>(p);
                    System.Data.Linq.ChangeSet cs = context.GetChangeSet();
                    context.SubmitChanges();

                    Response.Redirect("propertylist.aspx", false);

Open in new window

Hmmm...diving deeper into LINQ...please wait while pressure equalizes...

I know that I have had problems with updates when I discovered the primary key column wasn't identified correctly on the .dbml designer.
Screenshot.png
Avatar of ApexCo

ASKER

I checked that as well, and it looks correct (just like your example).

Let me ask a question. I'm returning the object using the following code, and then assigning the properties of the object to their appropriate items on the aspx page.

Is there any reason to think the fields are being locked for some reason? Am I going about that the wrong way?

public void LoadProperty()
        {

            Property pEdit = Repository.SelectByPK<Property>(editId);
            txtListingTitle.Text = pEdit.listingTitle;
            dropStatus.SelectedValue = Convert.ToString(pEdit.status);
            dropPropertyStyle.SelectedValue = Convert.ToString(pEdit.propertyStyle);
            dropListingType.SelectedValue = Convert.ToString(pEdit.listingType);
            EditDescription.Content = pEdit.description;
            txtLotSize.Text = pEdit.lotSize;
            txtSquareFeet.Text = pEdit.squarefeet;
            dropBathrom.SelectedValue = Convert.ToString(pEdit.bathrooms);
            dropBedroom.SelectedValue = Convert.ToString(pEdit.bedrooms);
            txtAddress.Text = pEdit.address1;
            txtZipCode.Text = pEdit.zipcode;
            dropStatus.SelectedValue = Convert.ToString(pEdit.state);
            dropCity.SelectedValue = Convert.ToString(pEdit.city);
            txtPrice.Text = pEdit.price;
            txtYearBuilt.Text = Convert.ToString(pEdit.yearBuild);
            dropGarage.SelectedValue = Convert.ToString(pEdit.GarageType);
            dropBasement.SelectedValue = Convert.ToString(pEdit.basementType);

        }

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Bob Learned
Bob Learned
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial