Столбец идентичности в EF 4 - PullRequest
6 голосов
/ 04 сентября 2010

Я следую примеру структуры сущности:

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 и в модели вижу, что Person StoreGeneratedPattern установлен на Identity.

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

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

EDITŁ

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

var schoolContext = new SchoolEntities();

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

            schoolContext.SaveChanges();

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

Итаквсе еще ни с чем ...

Есть идеи?

Ответы [ 3 ]

7 голосов
/ 04 сентября 2010

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

Редактировать:

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

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

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

В заказе хранится сгенерированный Id в качестве ключа и CustomerName в качестве свойства.,В строке заказа хранится сгенерированный Id в качестве ключа, 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.
}

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

Если я просто обновлю свой код, чтобы использовать уникальный временный идентификатор (я знаю, как настроено хранилище), он будетРабота.Это IMO одна из причин, почему мне нужно предоставить временный идентификатор для методов Create.

6 голосов
/ 04 сентября 2010

Я не могу сказать вам, почему команда 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;
 }

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

2 голосов
/ 04 сентября 2010

Вы должны иметь возможность настроить шаблон t4, который генерирует ваши классы, чтобы удалить свойство из метода фабрики. См. пост в блоге Дэнни Симмонса для получения информации о том, как создать 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;
}
...