db.SaveChanges() в цикле не сохраняет записи после ошибки
Я хочу импортировать данные из Excel в базу данных, используя EPPLUS. Отсюда я взял код: https://www.paragon-inc.com/resources/blogs-posts/easy_excel_interaction_pt6
using (var db = new DbEntities())
{
for (var row = 2; row <= lastRow; row++)
{
var newRecord = new DB_USER
{
ID = Int32.Parse(worksheet.Cells[idColumn + row].Value.ToString()),
FIRST_NAME = worksheet.Cells[firstNameColumn + row].Value.ToString(),
LAST_NAME = worksheet.Cells[lastNameColumn + row].Value.ToString(),
};
db.DB_USER.Add(newRecord);
try
{
db.SaveChanges();
totalImported++;
}
catch (Exception ex)
{
resultMessages.Add(string.Format("Error in line #{0}: {1}\n", row,
ex.Message));
}
}
}
Все работает нормально, если данные в Excel верны. Проблема в том, что любая запись содержит недопустимые данные. Например, у нас есть 3 записи в Excel:
- ID: 21 (ID не в базе)| ФИО: Джон | ПОСЛЕДНЕЕ ИМЯ: клетка
- ID: 1 (ID в базе) | ФИО: Мэй | ПОСЛЕДНЕЕ ИМЯ: синий
- ID: 25 (ID не в базе) | ФИО: Ник | ПОСЛЕДНЕЕ ИМЯ: Сири
А в базе уже есть запись с ID = 1
, 1-й и 3-й должны сохранить, а 2-й не должен. Проблема в том, что сохраняется только первая запись, остальные (2-я и 3-я) получат ошибку. Не знаю почему? Может потому что это одна транзакция или как? Это странно. Может кто-нибудь сказать мне, что я должен сделать, чтобы сохранить 1-ю и 3-ю запись? Не только 1-й в этом случае?
Ошибка:
ORA-00001: уникальный первичный ключ ограничения нарушен
что не имеет смысла в 3-й записи...
3 ответа
Почему бы вам не выполнить проверку в базе данных, чтобы увидеть, существует ли уже пользователь в базе данных с определенным идентификатором, и выполнить вставку, только если пользователь не существует:
bool exists = db.DB_USER.Where(u => u.ID == newRecord.ID).Any();
if(!exists)
{
//Do the insert
}
Вы можете оказаться в ситуации, когда нарушается другое ограничение, кроме PK. Это может значительно увеличить усилия по проверке всех данных перед добавлением/обновлением. Для этих сценариев, когда вы не можете просто проверить, существует ли уже запись, я понял, что после возникновения такой ошибки ядро EF продолжает выдавать ошибку, поскольку оно сохраняет состояние объекта как «Изменено».
Конкретно в вашем случае попробуйте следующее:
try
{
db.SaveChanges();
totalImported++;
}
catch (Exception ex)
{
resultMessages.Add(string.Format("Error in line #{0}: {1}\n", row,
ex.Message));
db.Entry(db.DB_USER).State = EntityState.Unchanged;
}
Вы не должны устанавливать ID вообще в коде. В базе данных установите идентификатор на автоинкремент. Конечно, он все еще присутствует в вашей модели, но не в вашей логике.
Таким образом, вам не нужно будет думать об обработке идентификатора, но вместо этого позвольте объектной структуре обрабатывать его
Во всяком случае, вот идеи о том, как изменить свой код:
using (var db = new DbEntities())
{
foreach (user in db)
{
var userExists = db.DB_USER.Where(u => u.ID == user.ID);
if(!userExists)
{
var newUser = new DB_USER
{
ID = Int32.Parse(worksheet.Cells[idColumn + row].Value.ToString()),
FIRST_NAME = worksheet.Cells[firstNameColumn + row].Value.ToString(),
LAST_NAME = worksheet.Cells[lastNameColumn + row].Value.ToString(),
};
try
{
db.DB_USER.Add(newUser);
db.SaveChanges();
totalImported++;
}
catch (Exception ex)
{
resultMessages.Add(string.Format("Error in line #{0}: {1}\n", row,
ex.Message));
}
}
}
}
Тем не менее, я предполагаю, что, поскольку ваш код в вопросе начинается с третьей строки (которая, казалось, имела идентификатор 21 до того, как вы его изменили, это могло бы заставить вас думать, что это первая строка, которая работала нормально.
Если вы обновляете свою базу данных, то, вероятно, уже есть запись в базе данных с идентификатором 21, и после того, как вы изменили ее на 25 и выполнили код, у вас, вероятно, теперь тоже есть код с 25, и вы снова получаете ту же ошибку.
Я добавил в код цикл foreach и проверил наличие записей, поэтому код должен избегать нарушения первичного ключа.
Я также перемещаю это внутри блока try-catch:
db.DB_USER.Add(newUser);