2013-03-28 2 views
3

Я хочу использовать объекты EF DbContext/POCO в отдельном режиме, то есть извлекать иерархию объектов из своего бизнес-уровня, вносить некоторые изменения, а затем отправлять всю иерархию обратно в бизнес-уровня, чтобы сохранить базу данных. Каждый вызов BLL использует другой экземпляр DbContext. Чтобы проверить это, я написал код для имитации такой среды.Сложное время с объектами POCO с объектной структурой Entity Framework

Сначала я извлечь Customer плюс связанные Orders и OrderLines: -

Customer customer; 
using (var context = new TestContext()) 
{ 
    customer = context.Customers.Include("Orders.OrderLines").SingleOrDefault(o => o.Id == 1); 
} 

Следующая добавить новый Order с двумя OrderLines: -

var newOrder = new Order { OrderDate = DateTime.Now, OrderDescription = "Test" }; 
newOrder.OrderLines.Add(new OrderLine { ProductName = "foo", Order = newOrder, OrderId = newOrder.Id }); 
newOrder.OrderLines.Add(new OrderLine { ProductName = "bar", Order = newOrder, OrderId = newOrder.Id }); 
customer.Orders.Add(newOrder); 
newOrder.Customer = customer; 
newOrder.CustomerId = customer.Id; 

Наконец я сохраняются изменения (с помощью нового контекст): -

using (var context = new TestContext()) 
{ 
    context.Customers.Attach(customer); 

    context.SaveChanges(); 
} 

Я понимаю, что эта последняя часть неполна, поэтому, без сомнения, мне нужно будет изменить состояние новых объектов до вызова SaveChanges(). Добавить или присоединить клиента? Какие сущности я должен изменить?

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

Объект с тем же ключом уже существует в ObjectStateManager.

кажется, проистекает из явно не устанавливать идентификатор двух OrderLine лиц, так как по умолчанию 0. Я думал, что это было прекрасно, чтобы сделать это, как EF будет обрабатывать вещи автоматически. Я делаю что-то неправильно?

Кроме того, работая над этим «отстраненным» способом, кажется, требуется большая работа, необходимая для установления отношений. Я должен добавить новую структуру заказа в коллекцию customer.Orders, установить свойство Customer нового заказа и его CustomerId собственности. Это правильный подход или есть более простой способ?

Будет ли я лучше смотреть на объекты самоконтроля? Я где-то читал, что они устарели или, по крайней мере, обескуражены в пользу POCOs.

ответ

3

Вы в основном есть 2 варианта:

A) оптимистичным.

Вы можете пройти довольно близко к тому, как вы это делаете сейчас, и просто приложить все как Модифицированные и надеющиеся. Код, который вы ищете вместо .Attach():

context.Entry(customer).State = EntityState.Modified; 

Определенно не интуитивно понятно. Этот странный смотрящий вызов приписывает выделенный (или вновь построенный вами) объект, как модифицированный. Источник: http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx

Если вы не уверены, был ли добавлен или изменен Вы можете использовать пример последнего сегмента объекта:

context.Entry(customer).State = customer.Id == 0 ? 
           EntityState.Added : 
           EntityState.Modified; 

Вы должны принимать эти действия на все объекты, которые добавлены/изменены , поэтому, если этот объект является сложным и имеет другие объекты, которые необходимо обновить в DB через отношения FK, вам также нужно установить их EntityState.

В зависимости от сценария вы можете сделать эти виды не-ухода пишет дешевле, используя различные вариации контекста:

public class MyDb : DbContext 
{ 
    . . . 
    public static MyDb CheapWrites() 
    { 
     var db = new MyDb(); 
     db.Configuration.AutoDetectChangesEnabled = false; 
     db.Configuration.ValidateOnSaveEnabled = false; 
     return db; 
    } 
} 

using(var db = MyDb.CheapWrites()) 
{ 
    db.Entry(customer).State = customer.Id == 0 ? 
     EntityState.Added : 
     EntityState.Modified; 
    db.SaveChanges(); 
} 

Вы в основном просто отключить некоторые дополнительные вызовы EF делает от вашего имени, что вы все равно игнорируете результаты.

B) Пессимистичный. Фактически вы можете запросить БД для проверки того, что данные не были изменены/добавлены с момента последнего получения, а затем обновите ее, если это безопасно.

var existing = db.Customers.Find(customer.Id); 
// Some logic here to decide whether updating is a good idea, like 
// verifying selected values haven't changed, then 

db.Entry(existing).CurrentValues.SetValues(customer); 
+0

Это что-то для меня, но я все еще не понимаю, какое исключение я получаю. Я не думал, что мне нужно назначать идентификаторы первичного ключа для новых объектов - я думал, что EF позаботился об этом для меня. Или это только в приложенном сценарии? –

+0

Ну, если вы посмотрите на документацию, связанную с ней, метод .Attach() предназначен для существующих объектов NotModified. Если ваши OrderLines являются новыми объектами, то они не соответствуют ни одному из этих критериев. Немодифицированные объекты имеют свой Ключ (обычно .Id), соблюдаемый на пути в базу данных; a 0 вызывает исключение. Таким образом, решение состоит в том, чтобы присоединить их, как указано выше, что заставляет его вставлять их без включения ключа, и, конечно, тогда db устанавливает для вас ключ (снова, .Id), потому что это столбец Identity. В основном .Attach() является довольно запутанным и, вероятно, его следует избегать. –