2010-09-04 3 views
6

Я следую по рамочному объекту, например:Идентичность колонок в EF 4

http://msdn.microsoft.com/en-us/library/bb399182.aspx

и у меня есть проблемы с Колонными удостоверениями.

Вот часть кода, создающего базу данных:

CREATE TABLE [dbo].[Person](
    [PersonID] [int] IDENTITY(1,1) NOT NULL, 
    [LastName] [nvarchar](50) NOT NULL, 
    [FirstName] [nvarchar](50) NOT NULL, 
    [HireDate] [datetime] NULL, 
    [EnrollmentDate] [datetime] NULL, 
CONSTRAINT [PK_School.Student] PRIMARY KEY CLUSTERED 
(
    [PersonID] ASC 
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY] 
) ON [PRIMARY] 
END 
GO 

В VS 2010 я строю EDMX-и на модели, которую я вижу, что лицо StoreGeneratedPattern установлен в Identity.

Но когда я хочу создать Person, путем: alt text

Почему я должен поместить идентификатор, если этот столбец автоинкремент?

EDITŁ

Я думал, что я нашел способ решить мою проблему:

var schoolContext = new SchoolEntities(); 

      schoolContext.AddToPeople(new Person() { LastName = "Gates", FirstName = "Bil" }); 

      schoolContext.SaveChanges(); 

, потому что он добавил Билл лицо, но ... потому что PersonID не обнуляемый, и вставить его с идентификатором 0. Когда я попытался добавить другому человеку так же, конечно, я получаю ошибку о первичном ключе :)

Так еще ни с чем ...

Есть идеи?

ответ

6

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

Если это действительно беспокоит вас, вы можете также извлечь выгоду из того факта, что сгенерированный объект класса Person определяется как частичного класса, так что вы можете продлить его легко. Создайте новый файл класса, например, PersonEx.cs и продлить этот частичный класс:

public partial class Person 
{ 
    public static Person CreatePerson(string lastName, string firstName) 
    { 
     return CreatePerson(-1, lastName, firstName); 
    } 
} 

Теперь вы можете легко создавать свои Person объекты без указания идентификатора, и добавить их к вашим данным:

using(SchoolEntities context = new SchoolEntities()) 
{ 
    Person newPerson = Person.CreatePerson("Gates", "Bill"); 
    context.AddToPeople(newPerson); 

    context.SaveChanges(); 

    int newID = newPerson.PersonID; 
} 

Это не идеальное решение - но это должен работать нормально (по крайней мере, для меня).

+0

Спасибо вам Марк! Это в следующий раз, когда вы спасете меня много времени. – user278618

+0

Хороший шаблон, но я думаю, вы были бы более безопасным, используя значение по умолчанию 0 для id .. -1 не сработало бы проверку значений по умолчанию, и можно предположить, что вы не будете создавать таблицу с идентификатором начиная с нуля. –

+0

@daveL: отрицательные числа работают просто отлично для меня .... –

7

ИМО Идентификатор необходим, даже если он сгенерирован. Предположим, что вы используете ассоциацию внешнего ключа (другое поведение, чем независимая ассоциация). Это означает, что связанные дочерние объекты используют первичный ключ родительского объекта для построения отношения. Теперь предположим, что вы добавляете несколько родительских объектов со связанными объектами в одну единицу работы. Вы должны указать уникальный (временный) идентификатор для каждого родительского объекта, иначе вы никогда не настроите граф объекта. Поэтому генератор кода делает это по умолчанию.

Edit:

Я удивлен, что я был downvoted для ответа на основе правильных фактов.Поэтому позвольте мне уточнить мой ответ:

В EF 4.0 есть два типа отношений. Независимая ассоциация и Ассоциация внешних ключей. Различие заключается в том, что более поздняя добавляет свойства внешнего ключа к объектам. Это позволяет работать с отношениями так же, как и в базе данных, - просто установив ключи. Вы можете прочитать об этих различиях в MSDN.

Теперь давайте предположим простой пример. У меня простая модель EDMX с MyContext. Модель состоит из двух объектов Order and OrderLine. Когда я добавлял таблицы Заказы и OrderLines к модели, я загубил Включить столбцы внешних ключей в модели, поэтому я использую ассоциации внешних ключей вместо независимых ассоциаций.

Заказ имеет идентификатор магазина в виде ключа и имя CustomerName как свойство. Строка заказа имеет идентификатор хранилища в качестве ключа, ProductTitle как свойство и OrderId как внешний ключ. Я хочу добавить два порядка в одной единице работы:

using (var context = new MyContext()) 
{ 
    var ox = Order.CreateOrder(0, "CustomerX"); 
    var oy = Order.CreateOrder(0, "CustomerY"); 

    // Building relationship in the same way as in database 
    var olx = OrderLine.CreateOrderLine(0, ox.Id, "ProductX"); 
    var oly = OrderLine.CreateOrderLine(0, oy.Id, "ProductY"); 

    context.Orders.AddObject(ox); 
    context.Orders.AddObject(oy); 
    context.OrderLines.AddObject(olx); 
    context.OrderLines.AddObject(oly); 
    context.SaveChanges(); // UpdateException: Unable determine principal end of Model.FK_OrderLine_Order relationship. Multiple added entities have the same primary key. 
} 

Ошибку я сделал в моем коде установка Id как новые заказы на 0. Даже если это временный идентификатор он все еще должен быть уникальными, если вы хотите использовать его для внешних ключей. Вероятно, вы можете спросить в тот момент, почему контекст не обрабатывает Id сам по себе? Просто потому, что он не может. Контекст знает, что идентификатор является временным и будет восстановлен в магазине, но контекст не знает подробностей о конфигурации магазина. Когда вы устанавливаете Identity в базе данных, вы также устанавливаете семя и приращение. Эти два значения не известны контексту, поэтому контекст не может получить действительный уникальный временный идентификатор, который еще не используется хранилищем. Предположим, что контекст неправильно создает некоторый временный идентификатор, и после этого вы загружаете объект, который уже использует эту проблему Id =.

Если я просто обновляю свой код, чтобы использовать уникальный временный идентификатор (я знаю, как настроен магазин), он будет работать. Это ИМО - одна из причин, по которой мне нужно предоставить временные методы Id для создания.

+0

Я не думаю, что будет проблема, если у вас есть несколько родительских объектов с ID = -1 с дочерними объектами. Он просто вставляет родительский элемент для родителя и использует '@@ IDENTITY', чтобы связать дочерние элементы. Свойство 'Id' не используется для создания графа объекта. –

+0

То, что вы описали, работает для независимой ассоциации. Я описал ассоциацию внешних ключей. –

2

Вы должны иметь возможность настроить шаблон t4, который генерирует ваши классы, чтобы удалить свойство из заводского метода. См. Danny Simmons' blog post для получения информации о том, как создать t4. Я не тестировал полученный заводский метод, но я не понимаю, почему это не сработает.

Затем измените этот метод:

bool IncludePropertyInFactoryMethod(StructuralType factoryType, EdmProperty edmProperty) 
{ 
    if (edmProperty.Nullable) 
    { 
     return false; 
    } 

    if (edmProperty.DefaultValue != null) 
    { 
     return false; 
    } 

    if ((Accessibility.ForReadOnlyProperty(edmProperty) != "public" && Accessibility.ForWriteOnlyProperty(edmProperty) != "public") || 
     (factoryType != edmProperty.DeclaringType && Accessibility.ForWriteOnlyProperty(edmProperty) == "private") 
     ) 
    { 
     // There is no public part to the property. 
     return false; 
    } 

    /********* 
    * This was added: 
    */ 

    var identity = edmProperty.TypeUsage.Facets 
     .Where(f => f.Name == "StoreGeneratedPattern").FirstOrDefault(); 

    if (identity != null && identity.Value.ToString() == "Identity") 
    { 
     // property is "Identity" generated, so skip from the factory method. 
     return false; 
    } 

    /********* 
    * end of the custom code 
    */ 

    return true; 
} 
+2

это будет работать для StorageModel. Если вы хотите использовать концептуальную модель, вам нужно будет использовать MetadataProperties. т.е.: property.MetadataProperties.TryGetValue (annotationNamespace + ": StoreGeneratedPattern", false, out storeGeneratedPatternProperty); –

+0

@SimonFrancesco, не могли бы вы подробнее рассказать о том, как это сделать, что такое annotationNamespace? – Shimmy

+0

@Shimmy. В вашем шаблоне T4 вы можете сделать что-то вроде: 'String annotationNamespace =" http://schemas.microsoft.com/ado/2009/02/edm/annotation "; \t \t \t MetadataProperty storeGeneratedPatternProperty = null; \t \t \t property.MetadataProperties.TryGetValue (annotationNamespace + ": StoreGeneratedPattern", false, out storeGeneratedPatternProperty); 'Если вам нужно более подробное объяснение, с удовольствием опубликуйте ответ :) –

Смежные вопросы