简体   繁体   中英

EF One-to-one optional relationship navigation properties not working

I have Payments and Reviews .

public class Payment {

        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int PaymentId { get; set; }

        [ForeignKey("Review")]
        public int? ReviewId { get; set; }
        public virtual Review Review { get; set; }

        // other properties
}

public class Review { 
        [Key]
        public int ReviewId { get; set; }

        [ForeignKey("PeerPayment")]
        public int? PeerPaymentId { get; set; }
        public virtual PeerPayment PeerPayment { get; set; }
}

I've mapped them as follows:

    public ReviewConfiguration() {
        // One-to-One optional:optional
        HasOptional(s => s.Payment).WithMany().HasForeignKey(x => x.PaymentId);
    }

    public PaymentConfiguration() {
        // One-to-One optional:optional
        HasOptional(s => s.Review).WithMany().HasForeignKey(s => s.ReviewId);
    }

My database FKs are created as nullable , which is great. However the following test fails:

    public async Task ReviewPeerPaymentWorks() {
        using (var db = new AppContext()) {
            var user1 = db.FindUser("M1");
            var user2 = db.FindUser("M2");
            var review = new Review() {
                FromUserId = user1.UserId,
                ToUserId = user2.UserId
            };
            db.Reviews.Add(review);
            var payment = new Payment() {
                FromUserId = user1.UserId,
                ToUserId = user2.UserId,
                ReviewId = review.ReviewId
            };
            db.Payments.Add(payment);
            db.SaveChanges();

            review = db.Reviews.First(s => s.ReviewId == review.ReviewId);
            payment = db.Payments.First(s => s.PaymentId == payment.PaymentId);
            Assert.AreEqual(review.PaymentId, payment.PaymentId); // fails - review.PaymentId is always null
            Assert.AreEqual(review.ReviewId, payment.ReviewId); // passes
            Assert.AreEqual(review.Payment.PaymentId, payment.Review.PaymentId); // fails - review.Payment is null, payment.Review.PaymentId is null
        }
    }

What am I doing wrong?

To be clear - I don't want to modify my test, unless someone thinks it's an invalid way to test the relationship for some reason.

One-to-One relation is a bit tricky in the EF. Especially in Code-First. In the DB-First I would make the UNIQUE constraint on the both FK properties to make sure that this relation appear only once per table (but please make sure that it's not One-to-One-or-Zero because UNIQUE constraint will allow you to have only one NULL FK value). In case of the NULLable FK it can be done via the 3rd relation table with the UNIQUE constraint over all columns in relation table.

Here is the URL which explains how EF maps the One-to-One relationship: http://www.entityframeworktutorial.net/entity-relationships.aspx
Here is the URL which explains how to configure One-to-One relationship: http://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx

Generally they suggest to use the same PK values for both tables:

"So, we need to configure above entities in such a way that EF creates Students and StudentAddresses table in the DB where it will make StudentId in Student table as PK and StudentAddressId column in StudentAddress table as PK and FK both ."

I have tried your example and you did two independent One-to-Many relations between Review and Payment. But without the Many navigation property. Look WithMany() :

.HasOptional(s => s.Review).WithMany().HasForeignKey(s => s.ReviewId); 

So it's real One-To-Many relation with no MANY (List) navigation properties. Look at the MSSQL diagram:

MSSQL图

And the reason why did you get the NULL values - because you have assigned Payment.ReviewId relation and didn't assign the Review.PaymentId relation. But you expected EF did it automatically. Since they are separate relations - EF left one of the as NULL because you didn't assign it

public async Task ReviewPeerPaymentWorks() {
    using (var db = new AppContext()) {
        var user1 = db.FindUser("M1");
        var user2 = db.FindUser("M2");
        var review = new Review() {
            FromUserId = user1.UserId,
            ToUserId = user2.UserId
        };
        db.Reviews.Add(review);
        var payment = new Payment() {
            FromUserId = user1.UserId,
            ToUserId = user2.UserId,
            ReviewId = review.ReviewId
        };

        db.Payments.Add(payment);
        db.SaveChanges();
        review.Payment = payment;
        db.SaveChanges();


        review = db.Reviews.First(s => s.ReviewId == review.ReviewId);
        payment = db.Payments.First(s => s.PaymentId == payment.PaymentId);
        Assert.AreEqual(review.PaymentId, payment.PaymentId); // fails - review.PaymentId is always null
        Assert.AreEqual(review.ReviewId, payment.ReviewId); // passes
        Assert.AreEqual(review.Payment.PaymentId, payment.Review.PaymentId); // fails - review.Payment is null, payment.Review.PaymentId is null
    }
}

I have faced this issue two times. Each time I have removed those two entities from Edmx and re-added. It solves my issue.

Hopefully it will work with you

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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