Каскад обновлений CTP5

Мне нужен шаблон добавления или обновления для CTP5. Предполагая модель:

public class User
{
    public int UserId { get; set; }
    public ICollection<Address> Addresses { get; set; }
}
public class Address
{
    public int AddressID { get; set; }
    public string Location { get; set; }
}

Если я добавлю нового пользователя, соответствующая таблица адресов также будет заполнена. Но если пользователь уже находится в БД, я получу исключение DbUpdateException. В этом случае я хочу, чтобы данные в базе данных обновлялись новыми данными. Как я могу это сделать?

данные в базе

Адрес таблицы

AddressID Местонахождение

1 ДжонПлейс

2 МэриПлейс

3 ДжиммиПлейс

Пользователь, которого я обновляю, имеет в коллекции Addresses 1 элемент с AddressId: 2, Location=GeorgePlace. Но для ID=2 в БД уже есть запись с location=MarryPlace. Я хочу, чтобы GeorgePlace перезаписал MarryPlace.

У меня не может быть адреса, который не назначен пользователю

Я создаю пользователя, как:

var user=new User();
user.Id=GetUserIDfromService();
foreach(var address in GetAddressesFromService(user.Id)){
    user.Addresses.Add(address);
}
context.Users.Add(user);
context.SaveChanges();//this may throw an exception, because there is already a user with this id.

person Ryan    schedule 18.02.2011    source источник
comment
@Ryan: Значит, у вас может быть адрес в базе данных, который не назначен ни одному пользователю? Можете ли вы также опубликовать свой код обновления?   -  person Ladislav Mrnka    schedule 18.02.2011
comment
Я делаю что-то, как предложил Якуб Конецки. Но я хотел бы иметь возможность сделать что-то вроде context.Users.Add(newUser); контекст.Сохранить изменения()   -  person Ryan    schedule 18.02.2011
comment
@Ryan: опубликуй свой код. Нам нужно знать, как вы получили/создали пользователя и объект адреса, который вы хотите обновить, а также ваш фактический метод обновления.   -  person Ladislav Mrnka    schedule 18.02.2011
comment
@Ryan: Что делает GetAddressFromService? Загружает ли он адреса из БД в другой экземпляр контекста?   -  person Ladislav Mrnka    schedule 18.02.2011
comment
нет, он получает данные из веб-сервиса   -  person Ryan    schedule 18.02.2011


Ответы (2)


Если вы хотите обновить существующего пользователя, не вставляйте его (не вызывайте Add()). Сначала загрузите пользователя по идентификатору, затем измените его свойства и вызовите SaveChanges() в контексте.

var db = new DatabaseContext();
var user = db.Users.Find(myUserId);
user.Name = "New Name";
db.SaveChanges();
person Jakub Konecki    schedule 18.02.2011
comment
да, но это потребует от меня сделать то же самое для каждого адреса, чем для пользователя. Некоторые адреса будут новыми, поэтому мне нужно их добавить, некоторые нужно будет обновить. - person Ryan; 18.02.2011

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

Код для маркировки адресов может выглядеть так:

var user = new User(); 
user.Id = GetUserIDfromService(); 
context.Users.Add(user); 
foreach(var address in GetAddressesFromService(user.Id))
{
  context.Addresses.Attach(address);
  // Let say that new address always have 0 Id      
  context.Entry(address).State = address.Id == 0 ? EntityState.Added : EntityState.Modified;      
  user.Addresses.Add(address); 
} 

context.SaveChanges();
person Ladislav Mrnka    schedule 18.02.2011
comment
Возможно, у меня уже есть адрес с идентификатором, который я добавляю. К сожалению, я не могу предположить, что новые адреса равны 0. Мое решение состоит в том, чтобы попытаться добавить пользователя как нового, чем перехватывать исключение DbUpdateException, если оно возникает, и в блоке перехвата я добавляю его как измененное. Что вы думаете? - person Ryan; 18.02.2011
comment
@Ryan: Это похоже на проблему, аналогичную этой: stackoverflow.com/questions/5024094/ Я лично против этих решений, потому что, если вы работаете с сущностями, не зная, являются ли они новыми или измененными что-то странное происходит в вашем приложении. В целом он будет работать, но будет обрабатывать сбои один за другим, поэтому я ожидаю, что для 20 адресов, которые должны быть изменены, вам придется ловить исключение 20 раз. - person Ladislav Mrnka; 18.02.2011
comment
Как вы предлагаете мне реализовать это как-то еще? - person Ryan; 18.02.2011
comment
@Ryan: Если вы не знаете, новый адрес или нет, попробуйте сначала загрузить его. - person Ladislav Mrnka; 18.02.2011
comment
Я думал об этом, но подумал, что обработка исключений будет проще. Если я сначала попытаюсь загрузить, чем добавить, у меня есть 2 операции Db для каждого адреса. Если я использую исключения, у меня есть 1Db op, если данные новые, и 2DbOp+1exception, если адрес уже есть. Спасибо за ваши ответы. Как вы думаете, я принял правильное решение, используя обработку исключений, или я должен сначала загрузить данные? - person Ryan; 18.02.2011
comment
@Ryan: вы можете использовать один запрос, который загрузит все возможные адреса, возвращаемые службой GetAddressesFrom. Linq to entity позволяет использовать эквивалент оператора IN в SQL (содержит). Таким образом, вы можете передать все идентификаторы адресов в один запрос. - person Ladislav Mrnka; 18.02.2011
comment
Это звучит как именно то, что мне нужно. Не могли бы вы привести пример? Большое спасибо! - person Ryan; 19.02.2011
comment
@Ryan: проверьте здесь: stackoverflow .com/questions/2714681/ Вы должны создать коллекцию идентификаторов адресов и использовать что-то вроде: from address in context.Addresses where ids.Contains(address.Id) select address - person Ladislav Mrnka; 22.02.2011