Entity Framework - Вставка записей в дочернюю таблицу, когда родительская запись уже существует - PullRequest
9 голосов
/ 27 августа 2010

Добрый день-

Я занимаюсь разработкой приложения Silverlight с использованием LINQ to Entity Framework для уровня доступа к данным.Моя n-уровневая модель базы данных включает в себя около 30 или около того таблиц с множеством многоуровневых родительских и дочерних отношений.Недавно начал писать свои службы данных и модульные тесты и обнаружил проблему со вставкой новой записи в дочернюю таблицу, когда запись родительской таблицы уже определена.Вот сокращенная версия моих родительских и дочерних таблиц:

СТОРОНА РОДИТЕЛЯ:

Users ИД пользователя (ПК)Эл. адреспарольи т.д ...

ТАБЛИЦА ДЕТЕЙ:

Профили пользователей UserProfileID (PK)Различная информация профиля ...UserID (FK - Таблица пользователей)

Итак, на данный момент у меня уже есть Entity Framework для создания различных классов для ORM, и я ищу создание служб данных для операций CRUD намои сущности.Ниже приведен пример кода для моего метода для вставки нового пользователя (у которого нет ссылок на родительскую таблицу):

    public User CreateUser(User newUser)
    {
        using (var _context = new ProjectDatabase())
        {
            newUser.CreatedDate = DateTime.Now;
            newUser.ModifiedDate = DateTime.Now;
            _context.AddToUsers(newUser);
            _context.SaveChanges();
        }

        return newUser;
    }

Это работает просто отлично.Я написал модульные тесты для него, нет проблем.Теперь, с другой стороны, рассмотрим следующий метод обслуживания данных, который я написал для вставки нового объекта UserProfile в базу данных.Начнем с того, что у меня есть очень похожая служба данных:

    public UserProfile CreateProfile(UserProfile newUserProfile)
    {
        using (var _context = new ProjectDatabase())
        {
            newUserProfile.CreatedDate = DateTime.Now;
            newUserProfile.ModifiedDate = DateTime.Now;
            _context.AddToUserProfiles(newUserProfile);
            _context.SaveChanges();
        }

        return newUserProfile;
    }

Этот код, как и другие, компилируется без проблем.Тем не менее, мой модульный тест постоянно терпит неудачу.Вот код для моего модульного теста:

    [TestMethod]
    public void UserProfileCrudTest()
    {
        IProfileDataService _dataService = new ProfileDataService();

        // Pre-seeded data on a State for foreign key
        State _myState;
        using (var _context = new ProjectDatabase())
        {
            _myState = _context.States.First();
        }

        // Creating temporary User for association with new UserProfile
        User _myUser = new User()
                  {
                      Email = "test",
                      Password = "test",
                      IsActive = true,
                      NameFirst = "test",
                      NameLast = "test"
                  };

        _myUser = _dataService.CreateUser(_myUser);
        Assert.IsNotNull(_myUser);

        // Attempt to create new UserProfile, assigning foreign key
        _myUserProfile = new UserProfile()
                 { 
                     Address = "123 Main", 
                     City = "Anywhere", 
                     State = _myState, 
                     PostalCode = "63021", 
                     UserID = _myUser.UserID 
                 };

        // This call to CreateProfile throws an exception and the test fails!!
        _myUserProfile = _dataService.CreateProfile(_myUserProfile);
        Assert.IsNotNull(_myUserProfile);

        // Remaining test code... never gets this far... 
    }

Итак, в этот момент мой модульный тест выдает следующее исключение:

Метод теста MyProject.DataServices.Tests.SecurityTests.UserProfileCrudTest выбросил исключение: System.InvalidOperationException : Свойство EntityKey можно установить только в том случае, если текущее значение свойства равно нулю.

Как я понял из своих исследований, это как-то связано ссвойство навигации, которое связывает новый объект UserProfile с коллекцией User.UserProfiles в родительской таблице / объекте.Но я не уверен, какие шаги необходимы для решения проблемы.Нужно ли каким-то образом прикреплять родительский элемент к ObjectContext?Буду признателен за любую помощь!

Спасибо, Джефф С.

1 Ответ

7 голосов
/ 27 августа 2010

Вам не нужно делать все это. С EF 4.0 ваш код может быть таким простым:

public UserProfile Create(UserProfile userProfile) {
    using (var context = new ProjectDatabase()) {                     
        //add the new graph to the context:
        context.UserProfiles.AddObject(userProfile);
        context.SaveChanges();
    }
    return userProfile;
}

public void UserProfileCrudTest() {    
    User _myUser = new User() {
        Email = "test",
        ...
    };
    UserProfile _myUserProfile = new UserProfile() {
        Address = "123 Main",
        ...
    };

    // join the new UserProfile to the User
    _myUserProfile.User = _myUser;
    UserProfile userProfile = Create(_myUserProfile);    
}

Пояснение:

То, что вы сделали, хорошо имеет смысл в типичных сценариях доступа к данным, где вам нужно будет сначала вставить нового пользователя , получить его UserID , а затем использовать его для вставки новый UserProfile . Однако SaveChanges делает все это за вас, когда видит, что оба являются новыми и что они связаны. Он также использует сопоставления модели, чтобы выяснить, какие является зависимым объектом (в данном случае UserProfile) и нуждается во внешнем ключе (UserID). С этой информацией он выполняет вставки базы данных в правильном порядке.
Добавление нового UserProfile к существующему пользователю:
Если вы хотите добавить новый UserProfile к существующему пользователю, и у вас уже есть объект User, к которому вы хотите добавить профиль, вы можете просто связать их, написав:

userProfile.User = yourUserObject
// OR
yourUserObject.UserProfiles.add(userProfile)

и затем вызовите SavingChanges ().

Если у вас есть только UserID, вы можете установить его так же, как UserProfile.UserID = userID. Кстати, я знаю, что это то, что вы изначально пытались сделать, и у вас есть исключение, поэтому вот что вам нужно сделать:

1. Обновите вашу Модель из базы данных, чтобы убедиться, что ваша модель полностью синхронизирована с вашей БД.

2. Просмотрите ваш код и убедитесь, что вы устанавливаете допустимый UserID для объекта UserProfile (то, что действительно существует в User Table) - вы даже можете попытаться жестко его кодировать.

3. Запустите его снова, и если вы все еще получаете исключение, то, пожалуйста, опубликуйте трассировку стека, так как я запустил ваш код, а я не получил, поэтому ваше исключение может быть связано с совершенно другой проблемой.

...