Модель Entity Framework генерирует странное сообщение об ошибке - PullRequest
2 голосов
/ 01 ноября 2011

У меня есть база данных PostgreSql 9.04, которая содержит реализацию postgres базы данных ASPNet Membership Services.Эта реализация находится в той же базе данных, что и мои собственные таблицы, но в другой схеме, называемой «безопасность».

По деловым причинам я включил таблицы aspnet_Users, aspnet_Membership и aspnet_Profiles в мою модель сущности.Ниже приведен DDL для этих таблиц.

CREATE TABLE security.aspnet_users (
    userid UUID NOT NULL,
    applicationname VARCHAR(255) NOT NULL,
    username VARCHAR(255),
    lastactivitydate TIMESTAMP,
    isanonymous INT,
    CONSTRAINT aspnet_users_userid_pk PRIMARY KEY (userid),
    CONSTRAINT aspnet_users_username_pk UNIQUE (username, applicationname)
);

CREATE TABLE security.aspnet_membership (
    userid UUID NOT NULL,
    password VARCHAR(255),
    passwordsalt VARCHAR(255),
    passwordformat INT,
    email VARCHAR(255),
    passwordquestion VARCHAR(255),
    passwordanswer VARCHAR(255),
    comments VARCHAR(255),
    isapproved INT,
    islockedout INT,
    creationdate TIMESTAMP,
    lastlogindate TIMESTAMP,
    lastpasswordchangeddate TIMESTAMP,
    lastlockoutdate TIMESTAMP,
    failedpasswordattemptcount INT,
    failedpasswordattemptstart TIMESTAMP,
    failedpasswordanswercount INT,
    failedpasswordanswerstart TIMESTAMP,

    CONSTRAINT aspnet_membership_userid_pk PRIMARY KEY (userid),
    CONSTRAINT aspnet_membership_userid_ref FOREIGN KEY (userid) REFERENCES security.aspnet_users (userid)
);

CREATE TABLE security.aspnet_profiles (
    userid                      UUID,
    propertynames               BYTEA NOT NULL,
    propertyvaluesstring        BYTEA NOT NULL,
    propertyvaluesbinary        BYTEA NULL,
    lastupdateddate             TIMESTAMP NOT NULL,

    CONSTRAINT aspnet_profiles_userid_ref FOREIGN KEY (userid) REFERENCES security.aspnet_users (userid),
    CONSTRAINT aspnet_profiles_userid_key UNIQUE (userid)
);

Опять же, это единственные таблицы из этой схемы, которые есть в моей модели.

Теперь, когда я вставляю строку в эти таблицы,и я вызываю SaveChanges, я получаю следующее сообщение об ошибке:

Невозможно определить основной конец отношения 'Membership_Users'.Несколько добавленных объектов могут иметь один и тот же первичный ключ.

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

public static void SaveUserProfile( CarSystemEntities context, UserProfileData data ) {
    if ( context == null )
        throw new ArgumentNullException( "context", "You must pass a non-null CarSystemEntities instance." );

    if ( data == null )
        throw new ArgumentNullException( "data", "You must pass a non-null UserProfileData instance." );

    try {
        Profile profile = null;

        if ( !context.Profiles.Any( p => p.Userid == data.ID ) ) {
            profile = new CarSystem.Profile{
                LastUpdatedDate      = data.LastUpdatedDate.LocalDateTime,
                PropertyNames        = data.PropertyNames, 
                PropertyValuesBinary = data.PropertyValuesBinary, 
                PropertyValuesString = data.PropertyValuesString,
                Userid               = data.ID
            };

            context.Profiles.AddObject( profile );
        } else {
            profile = QueryUerProfiles( context ).Where( p => p.Userid == data.ID ).Single();

            if ( profile.LastUpdatedDate      != data.LastUpdatedDate )      profile.LastUpdatedDate      = data.LastUpdatedDate.LocalDateTime;
            if ( profile.PropertyNames        != data.PropertyNames )        profile.PropertyNames        = data.PropertyNames;
            if ( profile.PropertyValuesBinary != data.PropertyValuesBinary ) profile.PropertyValuesBinary = data.PropertyValuesBinary;
            if ( profile.PropertyValuesString != data.PropertyValuesString ) profile.PropertyValuesString = data.PropertyValuesString;
        }

    } catch ( Exception ex ) {
        throw new DataAccessException( DataAccessOperation.SaveProfile, FailureReason.DatabaseError, ex );
    }
}

В чем причина этой ошибки?Как это исправить?

Спасибо

Тони

PS

При дальнейшем исследовании проблема заключается не в вставке строки, а в попыткеобновить строку в той же транзакции, в которой была вставлена ​​строка.

По существу, существует очередь объектов для записи в базу данных.Каждый объект по очереди удаляется из очереди, и для сохранения его в нужной таблице вызывается метод, подобный приведенному выше.

При первом обращении к определенному идентификатору метод вставляет его.То есть проверка Any возвращает false.Код создает новый объект сущности и вызывает метод AddObject для набора сущностей, который соответствует таблице.Все идет нормально.

После этого предполагается, что проверка Any вернет true, указывая, что в ней уже есть строка с заданным идентификатором.Затем предполагается обновить строку.Но похоже, что проверка Any также возвращает false во второй раз, хотя я и вызвал AddObject для набора Entity, который соответствует таблице при первом вызове.

Есть идеи, что я делаю неправильно?

PPS

Я провел еще несколько тестов с открытым монитором Sql.Мы используем Devart dotConnect для библиотеки PostgreSql, и у них есть инструмент, который вы можете загрузить, который называется DbMonitor.Это позволяет вам отслеживать SQL, который испускается и выполняется Entity Framework.

Оказывается (как и следовало ожидать), что вызовы .Any выполняются как запросы к базе данных сразу, а всеиз вставок ставятся в очередь и применяются после вызова SaveChanges.Вызовы Any, похоже, не принимают никаких строк, ожидающих вставки в учет, только то, что возвращают вызовы базы данных.Поскольку вставляемые строки еще не вставлены, этот запрос всегда будет возвращать значение false, пока не будет вызван SaveChanges.

В результате моя логика в ее нынешнем виде не будет работать.Когда строка, ожидающая вставки, обновляется (то есть она появляется во второй раз в очереди), логика вставки запускается во второй раз.Иногда я получаю исключение, указывающее, что ограничение уникального или первичного ключа нарушается, в других случаях я получаю сообщение, о котором я первоначально разместил сообщение.

Мне нужно, чтобы все вставки и обновления выполнялись водна транзакция, и мне нужно, чтобы вставки происходили в базе данных во время вызова AddObject.Тем не менее, мне по-прежнему нужны все вставки и обновления для отката, если что-то идет не так, вставка или обновление одной строки, или если в коде C # возникает какая-то другая ошибка.

У кого-нибудь есть идеи, как мне это сделатьEntity Framework?

Ответы [ 2 ]

0 голосов
/ 02 ноября 2011

Мне удалось решить эту проблему так, как я хочу, чтобы она работала.

Сначала причина всех моих проблем заключалась в том, что структура сущностей ждет, пока вы не вызовете SaveChanges, чтобы записать какие-либо изменения вбаза данных.Но когда вы выполняете вызов .Any (), SQL генерируется и выполняется немедленно, игнорируя любые изменения, ожидающие записи в базу данных.Так что мой код всегда думал, что ему нужно вставить новые строки, даже если одна была подлинной вставкой, а другая должна была быть обновлением.

Затем я попытался использовать объект TransactionScope, вызывая SaveChanges () после вставки или обновлениякаждый объект.Затем я бы вызвал метод Completed () экземпляра TransactionScope, чтобы «зафиксировать» изменения.Ну, низко и вот, это заканчивается тем, что работает точно так же, как ожидал вызова SaveChanges!Ничего не записывается в базу данных, пока вы не вызовете scope.Completed ()!

Решение, которое я нашел, работает, показано в следующем коде:

using ( CarSystemEntities context = new CarSystemEntities() ) { 
    if ( context.Connection.State != ConnectionState.Open ) { 
        context.Connection.Open(); 
    } 

    DbTransaction transaction = context.Connection.BeginTransaction(); 

    try { 
        <Data processing code here> 

        transaction.Commit(); 

    } catch ( Exception ex ) { 
        transaction.Rollback(); 

        <More exception code here as needed> 
    }             
}

В сочетании с вызовом SaveChanges после каждого контекста..AddObject или свойство update, все работает именно так, как мне нужно, чтобы оно работало.Операторы INSERT & Update генерируются и выполняются при вызове SaveChanges.Вызовы .Any также учитывают все строки, которые были вставлены в таблицу предыдущей итерацией.Все находится внутри одной реальной транзакции базы данных, которую можно зафиксировать или откатить целиком.

0 голосов
/ 01 ноября 2011

Не похоже, что первичный ключ устанавливается для объекта профиля, который вы добавляете в базу данных. Значением по умолчанию для Guid является Guid.Empty, поэтому первый из них сохраняется, но каждый последующий объект дает сбой.

код ниже устанавливает первичный ключ

if ( !context.Profiles.Any( p => p.Userid == data.ID ) ) {
            profile = new CarSystem.Profile{
                LastUpdatedDate      = data.LastUpdatedDate.LocalDateTime,
                PropertyNames        = data.PropertyNames, 
                PropertyValuesBinary = data.PropertyValuesBinary, 
                PropertyValuesString = data.PropertyValuesString,
                Userid               = data.ID

                //add the next line to ensure there is a pk field
                ID                   = Guid.NewGuid()
            };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...