实体框架模型生成奇怪的错误消息

时间:2011-10-31 23:01:58

标签: c# postgresql entity-framework-4

我有一个PostgreSql 9.04数据库,其中包含ASPNet Membership Services数据库的postgres实现。此实现与我自己的表存在于同一个数据库中,但位于名为“security”的不同模式中。

出于商业原因,我在实体模型中包含了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 );
    }
}

此错误的原因是什么?我该如何解决这个问题?

由于

P.S。

经过进一步调查,问题不在插入行时,而是在尝试更新与插入行的行相同的事务中的行时。

基本上,有一个对象队列要写入数据库。依次从队列中删除每个对象,并调用上面的方法将其保存到正确的表中。

第一次看到特定ID时,该方法会插入它。也就是说,Any检查返回false。该代码创建新的实体对象,并为与该表对应的实体集调用AddObject方法。到现在为止还挺好。

之后,假设Any检查将返回true,表示已存在具有给定ID的行。然后应该更新该行。但是看起来Any检查第二次也返回false,即使我在与第一次调用的表对应的Entity集上调用了AddObject。

知道我做错了吗?

PPS

我已经在打开Sql监视器的情况下进行了一些测试。我们正在使用Devart dotConnect for PostgreSql库,他们有一个名为DbMonitor的工具。这允许您跟踪发出的SQL和由实体框架执行。

事实证明(可以预料,当它发生时)对.Any的调用立即作为对数据库的查询执行,而所有插入在排队并在调用SaveChanges后应用。 Any调用似乎没有占用任何挂起插入的行,只是数据库调用返回的行。由于尚未插入要插入的行,因此在调用SaveChanges之前,此查询将始终返回false。

结果,我的逻辑不起作用。当更新待挂入的行时(即,它在队列中第二次出现),插入逻辑将再次运行。有时我会得到一个异常,表明违反了唯一或主要的约束,有时我会收到我最初发布的消息。

我需要在单个事务中完成所有插入和更新,并且我需要在调用AddObject时在数据库中进行插入。然而,我仍然需要所有插件和如果插入或更新单个行出错,或者C#代码中出现其他错误,则更新回滚。

任何人都有任何想法如何使用实体框架做到这一点?

2 个答案:

答案 0 :(得分:0)

在您要添加到db的Profile对象上,似乎没有设置主键。 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()
            };

答案 1 :(得分:0)

我已设法按照我希望的方式解决此问题。

一开始我遇到的所有问题的原因是实体框架一直等到你调用SaveChanges来写任何对数据库的更改。但是当您执行.Any()调用时,会立即生成并执行SQL,忽略正在等待写入数据库的任何更改。所以我的代码总是认为它必须插入新行,即使一个是真正的插入而另一个应该是更新。

接下来,我尝试使用TransactionScope对象,在插入或更新每个对象后调用SaveChanges()。然后我调用TransactionScope实例的Completed()方法来“提交”更改。嗯,低和&看,这最终工作完全一样等着叫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> 
    }             
}

结合在每个context..AddObject或属性更新之后调用SaveChanges,一切都完全按照我需要的方式工作。 INSERT&amp;生成更新语句&amp;调用SaveChanges时执行。对.Any的调用还会考虑前一次迭代中插入到表中的任何行。一切都在一个真正的数据库事务中,可以作为一个整体提交或回滚。