LINQ to SQL - DuplicateKeyException во время обновления - PullRequest
3 голосов
/ 30 июня 2010

Приведенный ниже код пытается обновить строку в базе данных, но вместо этого выдает исключение:

System.Data.Linq.DuplicateKeyException: невозможно добавить объект с ключом, который уже находится виспользуйте

Большинство примеров, которые я видел, запрашивают базу данных, чтобы получить экземпляр сущности, изменить некоторые свойства экземпляра и затем обновить его.Здесь я получаю объект из другого источника полностью (он анализируется из файла XML) и запрашиваю, есть ли уже строка для этих данных.Если есть, я устанавливаю первичный ключ и пытаюсь запустить обновление.Какой правильный способ сделать это?

Вот урезанная версия кода:

Customer customer = new Customer(); // Customer has a database generated
                                    // identity column called CustomerId

// populate customer object
customer.Name = "Mr. X";
customer.Email = "x@company.com";
// etc.

// is customer already in database?
// identify customer by email
var results = ctx.Where(c => c.Email == customer.Email); // ctx is a DataContext

if (results.Any())
{
   Customer existing = results.Single();

   // set primary key to match existing one
   customer.CustomerId = existing.CustomerId;

   // update database
   customerTable.Attach(customer);  // customerTable is a Table<Customer>
   ctx.SubmitChanges();
}

// otherwise do insert
// ...   

Ответы [ 5 ]

1 голос
/ 03 декабря 2013

Где-то в сети я нашел это решение:

static void CopyProperties<T>(ref T Target, T Source)
        {
            foreach (PropertyInfo PI in Target.GetType().GetProperties())
            {
                if (PI.CanWrite && PI.CanRead)
                {
                    PI.SetValue(Target, PI.GetValue(Source, null), null);
                }
            }
        }

....

static void Save(Test_TableData ChangedData)
        {
            using (DataClasses1DataContext D = new DataClasses1DataContext())
            {
                Test_TableData UpdateTarget = D.Test_TableDatas.SingleOrDefault(i => i.ID == ChangedData.ID);
                if (UpdateTarget != null)
                {
                    CopyProperties<Test_TableData>(ref UpdateTarget, ChangedData);
                }
                else
                {
                    D.Test_TableDatas.InsertOnSubmit(ChangedData);
                }
                D.SubmitChanges();
            }
    }
1 голос
/ 30 июня 2010

Я новичок в LINQ to SQL, поэтому, если кто-то умнее меня, увидит, что это неправильно, поправьте меня.Но я считаю, что ваша проблема заключается в том, что когда вы входите в оператор if, вы получаете сущность из результатов (через results.Single ()) и устанавливаете значение для нового объекта customer.Когда вы пытаетесь отправить объект customer в базу данных, первичный ключ уже существует, поэтому вы получаете сообщение об ошибке.

Вместо этого вы хотите обновить существующего клиента и отправить его обратно в базу данных.

1 голос
/ 30 июня 2010

Сделайте это изменение:

customerTable.Attach(customer, existing);

^ Я не уверен, почему вышесказанное не сработает. Второй аргумент - это «исходное состояние» объекта, возможно, из-за того, что это другая ссылка на другой экземпляр, L2S считает, что ему нужно вставить целый новый объект.

Я думаю, что было бы лучше сделать что-то вроде:

var customer = ctx.Where(...).SingleOrDefault();
if (customer == null)
{
  customer = new Customer()
  {
    Name = name,
    Email = email
  };
  customerTable.InsertOnSubmit(customer);
}
else
{
  customer.Name = name;
  customer.Email = email;
}

ctx.SubmitChanges();
0 голосов
/ 30 июня 2010

Видимо, это не новая проблема. Вот несколько публикаций, в которых обсуждается эта проблема:

http://www.west -wind.com / блог / сообщений / 134095.aspx

http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx

http://social.msdn.microsoft.com/forums/en-US/linqprojectgeneral/thread/3848c02c-464e-40ff-87b6-813bff7b1263/

Я заработал, создав новый DataContext и Table перед обновлением. Мой модифицированный код выглядит так:

Customer customer = new Customer(); // Customer has a database generated
                                    // identity column called CustomerId

// populate customer object
customer.Name = "Mr. X";
customer.Email = "x@company.com";
// etc.

// is customer already in database?
// identify customer by email
var results = ctx.Where(c => c.Email == customer.Email); // ctx is a DataContext

if (results.Any())
{
   Customer existing = results.Single();

   // set primary key to match existing one
   customer.CustomerId = existing.CustomerId;

   // **** CODE CHANGES HERE ****
   // create new DataContext and table to avoid DuplicateKeyException errors
   var ctx = new DataContext(customerTable.Context.Connection.ConnectionString);
   customerTable = ctx.GetTable<Customer>();

   // update database
   customerTable.Attach(customer);  // customerTable is a Table<Customer>

   // **** ANOTHER CODE CHANGE ****
   // without this line the data won't be updated with the new values
   ctx.Refresh(RefreshMode.KeepCurrentValues, customer);

   ctx.SubmitChanges();
}

// otherwise do insert
// ...  

Насколько я понимаю, это то, что DataContext может содержать только один экземпляр каждого уникального объекта. Попытка присоединить новый объект с тем же первичным ключом вызывает ошибку, поскольку теперь будет два экземпляра одного и того же объекта. Новый DataContext не знает о существующей сущности и поэтому не имеет проблем с присоединением новой.


ОБНОВЛЕНИЕ: похоже, на этот вопрос уже дан ответ .


ОБНОВЛЕНИЕ: не используйте мой пример кода как есть. Это вызвало у меня другие проблемы.

0 голосов
/ 30 июня 2010

То, как я это делаю, таково:

Я бы вытащил клиента, который уже существует, как вы, затем обновил бы элемент с соответствующим идентификатором, который вы используете, со значениями, которые вы извлекли из xml. Затем, когда вы вызовете метод datacontext.SubmitChanges(), он сделает обновление за вас.

Или вы можете использовать метод Attach, в вашем случае ваш код будет выглядеть примерно так:

customerTable.Attach(customer, existing);

Attach был создан именно для этого случая.

Редактировать

Почему бы вам не изменить порядок, в котором вы делаете вещи, вместо того, чтобы создать нового клиента, заполнить его, сделать что-то подобное

var results = ctx.Where(c => c.Email == customer.Email);

Customer customer = (results.Any ? results.Single : new Customer)

Затем запросите ваш xml, чтобы заполнить клиента, затем выполните вставку / обновление.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...