简体   繁体   English

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

[英]Entity framework model generating weird error message

I have a PostgreSql 9.04 database that contains a postgres implementation of the ASPNet Membership Services database. 我有一个PostgreSql 9.04数据库,其中包含ASPNet Membership Services数据库的postgres实现。 This implementation lives in the same database as my own tables, but in a different schema called "security". 此实现与我自己的表位于同一数据库中,但位于称为“安全”的不同架构中。

For business reasons, I have included the aspnet_Users, aspnet_Membership, and aspnet_Profiles table in my entity model. 由于业务原因,我在实体模型中包含了aspnet_Users,aspnet_Membership和aspnet_Profiles表。 Below is the DDL for these tables. 下面是这些表的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)
);

Again, these are the only tables from this schema that are in my model. 同样,这些是该模式中模型中仅有的表。

Now, when I insert a row into these tables, and I call SaveChanges, I get the following error message: 现在,当我在这些表中插入一行并调用SaveChanges时,出现以下错误消息:

Unable to determine the principal end of the 'Membership_Users' relationship. 无法确定“ Membership_Users”关系的主要结尾。 Multiple added entities may have the same primary key. 多个添加的实体可能具有相同的主键。

Below is a sample of code that I'm using to insert a new or update an existing row. 以下是我用于插入新行或更新现有行的代码示例。 This is representative of everything I'm doing regarding updating the database. 这代表了我正在执行的有关更新数据库的所有操作。

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 );
    }
}

What is the cause of this error? 此错误的原因是什么? How do I fix this? 我该如何解决?

Thanks 谢谢

Tony 托尼

PS 聚苯乙烯

Upon further investigation, the problem isn't when inserting a row, it's when trying to update a row in the same transaction as the one that inserted the row. 经过进一步研究,问题不在于插入行,而是在于尝试在与插入行的事务相同的事务中更新行。

Essentially, there's a queue of objects to be written to the database. 本质上,存在要写入数据库的对象队列。 Each object is removed from the queue in turn and a method like the one above is called to save it to the proper table. 依次从队列中删除每个对象,并调用类似于上述对象的方法将其保存到正确的表中。

The first time a specific ID is seen, the method inserts it. 第一次看到特定ID时,该方法将其插入。 That is, the Any check returns false. 也就是说,Any检查返回false。 The code creates the new entity object and calls the AddObject method for the entity set that corresponds to the table. 该代码创建新的实体对象,并为与该表相对应的实体集调用AddObject方法。 So far so good. 到现在为止还挺好。

After that, it is assumed that the Any check will return true, indicating that there is a row with the given ID in it already. 此后,假定Any检查将返回true,表示已经存在具有给定ID的行。 It is then supposed to update the row. 然后应该更新该行。 But it appears that the Any check is returning false the second time, too, even though I called AddObject on the Entity set that corresponds to the table on the first call. 但是,即使我在与第一次调用的表相对应的实体集上调用了AddObject,似乎Any检查也第二次返回false。

Any idea what I am doing wrong? 知道我在做什么错吗?

PPS PPS

I've done some more testing with a Sql monitor open. 我在打开Sql监视器的情况下进行了更多测试。 We are using the Devart dotConnect for PostgreSql library and they have a tool you can download called DbMonitor. 我们正在使用Devart dotConnect for PostgreSql库,他们有一个可以下载的名为DbMonitor的工具。 This allows you to track the SQL that is emitted & executed by the Entity Framework. 这使您可以跟踪由实体框架发出和执行的SQL。

It turns out (predictably, as it happens) that the calls to .Any are executed as queries against the database immediately, while all of the inserts are queued up and applied once you call SaveChanges. 事实证明,对.Any的调用会作为对数据库的查询立即执行(发生时是可预测的),而一旦您调用SaveChanges,所有插入都将排队并应用。 The Any calls do not seem to take any rows that are pending inserting into account, just what the database calls return. Any调用似乎并没有考虑任何等待插入的行,只是数据库调用返回的行。 As the rows to be inserted haven't been inserted yet, this query will always return false until SaveChanges is called. 由于尚未插入要插入的行,因此该查询将始终返回false,直到调用SaveChanges。

As a result, my logic as it stands won't work. 结果,我目前的逻辑将无法正常工作。 When a row that is pending insertion is updated (that is, it appears a second time in the queue), The insert logic would run a second time. 当等待插入的行被更新时(也就是说,它再次出现在队列中),插入逻辑将再次运行。 Sometimes I'd get an exception indicating a unique or primary key constraint was being violated, other times I'd get the message I originally posted about. 有时,我会收到一个异常,指示违反了唯一键或主键约束,而其他时候,我会得到最初发布的消息。

I need all of the inserts and updates to be done in a single transaction, and I need the inserts to happen in the database at the time of the call to AddObject. 我需要在单个事务中完成所有插入和更新,并且需要在调用AddObject时在数据库中进行插入。 Yet I still need all of the inserts & updates to rollback if something goes wrong inserting or updating a single row, or if some other error occurs in the C# code. 但是,如果在插入或更新单行时出错,或者C#代码中发生其他错误,我仍然需要所有插入和更新来回滚。

Anybody have any ideas how I can do this with the Entity Framework? 有人对我如何使用实体框架做到这一点有任何想法吗?

It doesn't look like the primary key is being set on the Profile object you are adding to the db. 似乎您要添加到数据库的Profile对象上没有设置主键。 The default value for a Guid is Guid.Empty, so the first one gets saved but each subsequent object fails. Guid的默认值为Guid.Empty,因此第一个被保存,但随后的每个对象均失败。

the code below sets the primary key 下面的代码设置主键

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()
            };

I've managed to resolve this issue the way I want it to work. 我设法解决了这个问题,希望它能起作用。

The cause of all of my problems at first was that the entity framework waits until you call SaveChanges to write any changes to the database. 起初我所有问题的原因是实体框架一直等到您调用SaveChanges将任何更改写入数据库。 But when you execute a .Any() call, the SQL is generated and executed immediately, ignoring any changes that are pending being written to the database. 但是,当您执行.Any()调用时,将立即生成并执行SQL,而忽略任何等待写入数据库的更改。 So my code always thought it had to insert new rows even though one was a genuine insert and the other was supposed to be an update. 所以我的代码始终认为,即使其中一个是真正的插入而另一个应该是更新,也必须插入新行。

Next I tried using a TransactionScope object, calling SaveChanges() after inserting or updating each object. 接下来,我尝试使用TransactionScope对象,在插入或更新每个对象后调用SaveChanges()。 Then I'd call the TransactionScope instance's Completed() method to "commit" the changes. 然后,我将调用TransactionScope实例的Completed()方法来“提交”更改。 Well, low & behold, this ends up working exactly the same was waiting to call SaveChanges! 好吧,低估了,这最终的工作原理与等待调用SaveChanges时完全相同! Nothing gets written to the database until you call scope.Completed()! 除非调用scope.Completed(),否则什么都不会写入数据库。

The solution I found that works is shown in the code below: 我发现可行的解决方案如下代码所示:

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> 
    }             
}

Combined with calling SaveChanges after each context..AddObject or property update, everything works exactly as I need it to work. 结合在每个context..AddObject或属性更新之后调用SaveChanges,一切都按我需要的方式工作。 The INSERT & Update statements are generated & executed when SaveChanges is called. 调用SaveChanges时,将生成并执行INSERT&Update语句。 The calls to .Any also take into account any rows that were inserted into the table by a previous iteration. 对.Any的调用还考虑了上一次迭代插入表中的任何行。 Everything is inside one real database transaction that can be committed or rolled back as a whole. 一切都在一个实际的数据库事务中,该事务可以作为整体提交或回滚。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM