пятница, февраля 08, 2008

Entity Framework - Identity, Computed и ключевые столбцы

Таблицы БД могут содержать столбцы, которые изменяются в момент сохранения изменений на стороне БД. В SQL Server это столбцы IDENTITY и вычисляемые (computed) столбцы. Как с такими таблицами работает Entity Framework?

Рассмотрим фрагмент Storage Schema (это Xml описание схемы БД в составе EDM)



<EntityType Name="Invoice">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="Customer" Type="nvarchar" Nullable="false" MaxLength="50" />
<Property Name="Cost" Type="money" Nullable="false" />
<Property Name="Discount" Type="money" Nullable="false" />
<Property Name="Total" Type="money" StoreGeneratedPattern="Computed" />
</EntityType>



Это описание таблицы Invoice. Столбец “Id” объявлен identity, а столбец “Total” - это вычисляемый столбец ([Cost]-[Discount]). Мы видим, что визард при генерации EDM добавил в описания этих столбцов атрибут StoreGeneratedPattern. Именно этот атрибут сигнализирует EF о том, что значение данного столбца вычисляется в БД.
StoreGeneratedPattern имеет три значения: “None”, “Identity”, “Computed”. Значение “None” принято по умолчанию. “Computed” обозначает, что столбец содержит вычисляемое значение, и при вставке и обновлении EF вместо того, чтобы передавать значения для этого столбца в БД, получает его значение после обновления. “Identity” соответственно обозначает столбец Identity. По смыслу он практически идентичен значению “Computed”, но заставляет EF генерировать специальный SQL код для получения значения этого столбца после вставки:


select [Id] from [dbo].[Invoice]
where @@ROWCOUNT > 0 and [Id] = scope_identity()


Свойства классов, замапленные на вычисляемые столбцы хот и имеют аксессор set, никогда не сохраняют свои значения в БД. Что бы мы ни присваивали свойству Invoice.Total после сохранения изменений, там будет записано значение, прочитанное из БД.

В этом отношении свойства классов, которые входят в состав Entity Key - ключа сущности, отличаются еще более странным поведением. Логично было бы предположить что Invoice.Id будет вести себя подобно Invoice.Total. Поскольку это свойство замаплено на identity столбец, то какие бы значения мы ему не присваивали. После сохранения мы получим значение, установленное БД. Это так лишь отчасти:


01 using (TestEntities context = new TestEntities())
02 {
03 Invoice inv = new Invoice();
04 inv.Id = 1;
05 inv.Total = 10;
06 context.AddToInvoice(inv);
07 context.SaveChanges(true);
08 Console.WriteLine(inv.Id);
09 inv.Id = 1000;
10 context.SaveChanges(true);
11 Console.WriteLine(inv.Id);
12 }


В приведенном коде мы сперва создаем экземпляр Invoice, а затем присваиваем значения его свойствам Id и Total. Затем мы сохраняем изменения в БД. После сохранения, в строке 8 мы видим в свойстве Id значение назначенное БД. Однако, когда в строке 9 мы пытаемся изменить значение Id то получаем исключение:

"The property 'Id' is read-only because it is part of the entity's key."

Вот так. В строке 4 присваивание Id проходит нормально, а здесь сваливается с исключением. Изменять значения ключевых свойств (свойств, которые входят в состав ключа) можно только, если состояние объекта “Detached” или “Added”. Такое поведение появилось только в beta 3. В предыдущем CTP изменения значений ключевых свойств тупо сохранялись в БД или игнорировались для identity столбцов.

Комментариев нет: