2

I'm following this question: EF Code First - 1-to-1 Optional Relationship

And I have my relationships set up as follows:

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

    public virtual Payment Payment { get; set; }
}

public class Payment {
    [Key]
    public int PaymentId { get; set; }

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

    public class ReviewConfiguration : EntityTypeConfiguration<Review>
{
    public ReviewConfiguration()
    {
        // One-to-One Optional
        HasOptional<Payment>(s => s.Payment).WithOptionalDependent(s => s.Review).Map(s => s.MapKey("PaymentId"));
    }
}

And I get ONE of the keys valid, but the other is never mapped as an optional FK:

enter image description here

What am I doing wrong?

I've seen some weird hacky solutions involving creating empty Lists etc - not what I'm looking for. I'm looking for a proper approach here - it has to exist... right?

Update

I'm using the above now - when I have the Payment on hand and need to access or delete the Review, I have to do another lookup on the [pseudo-FK] ReviewId, which totally sucks.

Community
  • 1
  • 1
RobVious
  • 12,685
  • 25
  • 99
  • 181
  • This can't be your model + mapping. It gives error *The navigation property 'Review' declared on type 'Payment' has been configured with conflicting foreign keys.* – Gert Arnold May 05 '16 at 23:21
  • @GertArnold that's exactly how I have it. Just double and triple-checked. Do you have PaymentId on Review? I don't. Thanks for trying this out by the way. Extremely frustrating stuff. – RobVious May 06 '16 at 00:42
  • Weird. Anyway. EF doesn't need the FK ReviewId. With the one optional FK PaymentId it knows enough to establish the association bidirectionally. – Gert Arnold May 06 '16 at 07:29
  • @GertArnold my current dilemma has shown me otherwise. I still have not managed to configure a fully bidirectional mutually optional relationship between two entities. – RobVious May 06 '16 at 07:31
  • To make sure we're on the same page: I only dropped `ReviewId` from the model, not `Review` (the property). – Gert Arnold May 06 '16 at 07:33
  • @GertArnold FK PaymentId doesn't exist above. I have one FK defined. Still no bidirectional. If you have something that works (and you've tested it), please feel free to post an answer. – RobVious May 06 '16 at 18:05

2 Answers2

7

In any one-to-one association, EF uses only one foreign key. When the association is required, the foreign key will also be the primary key of the dependent entity, as explained here.

When the association is optional, both entities should be able to exist independent of one another. So their primary keys can't be foreign keys, because PKs can't be optional. Here an additional nullable FK field is required to establish the optional association.

In your case, technically it doesn't really matter which entity has the FK field (logically, it may). I've used this model:

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

public class Payment
{
    [Key]
    public int PaymentId { get; set; }

    public Review Review { get; set; }
}

With this mapping:

public class ReviewConfiguration : EntityTypeConfiguration<Review>
{
    public ReviewConfiguration()
    {
        // One-to-One Optional
        HasOptional(s => s.Payment)
            .WithOptionalDependent(s => s.Review)
            .Map(s => s.MapKey("PaymentId"));
    }
}

(So, apart from Payment.ReviewId, this is identical to the model + mapping in your question).

Now I can do things like ...

db.Set<Review>().Add(new Review { Payment = new Payment() });
db.Set<Payment>().Add(new Payment { Review = new Review() });
db.SaveChanges();

... where db of course is a context. The content of both tables now is:

PaymentId
-----------
1
2

ReviewId    PaymentId
----------- -----------
1           1
2           2

And I can query the data bidirectionally like so:

var data = db.Set<Review>().Include(r => r.Payment).ToList();

or

var data = db.Set<Payment>().Include(r => r.Review).ToList();

But instead of ReviewConfiguration, I can also use...

public class PaymentConfiguration : EntityTypeConfiguration<Payment>
{
    public PaymentConfiguration()
    {
        // One-to-One Optional
        HasOptional(s => s.Review)
            .WithOptionalDependent(s => s.Payment)
            .Map(s => s.MapKey("ReviewId"));
    }
}

Now there will be an FK field ReviewId in table Payment, and the rest of the code works without changes.

Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • Thank you so much. My only concern is that db.SaveChanges() actually does not populate the nullable non-primary FK on the other end - but I need to test this out again. – RobVious May 06 '16 at 20:06
  • Don't worry, it does. EF first saves the principal entity and then uses its generated PK value to set the FK field in the dependent entity, all under the covers (and all in one transaction of course). – Gert Arnold May 06 '16 at 20:09
  • Thank you again guys. I know this took some time. – RobVious May 06 '16 at 20:13
1

You must have typos in your models (2 ReviewId fields in payment?). Also, if you're going the fluent route don't put any relationship attributes in there to confuse matters. IAC, try something like this:

public class Review {
    public int ReviewId { get; set; }

    public int? PaymentId { get; set; }
    public virtual Payment Payment { get; set; }
}

public class Payment {
    public int PaymentId { get; set; }

    public int? ReviewId { get; set; }
    public virtual Review Review { get; set; }
}

public class ReviewConfiguration : EntityTypeConfiguration<Review>
{
    public ReviewConfiguration()
    {
        HasOptional(r => r.Payment)
            .WithMany()
            .HasForeignKey(r => r.PaymentId);
    }
}

public class PaymentConfiguration : EntityTypeConfiguration<Payment>
{
    public PaymentConfiguration()
    {
        HasOptional(p => p.Review)
            .WithMany()
            .HasForeignKey(p => p.ReviewId);
    }
}
Steve Greene
  • 12,029
  • 1
  • 33
  • 54
  • Thanks for this. I butchered my models while copying/pasting - corrected. I'll try this and post back with results. Thanks Steve. – RobVious May 05 '16 at 21:21
  • I'm getting the following here: PaymentId: Name: Each property name in a type must be unique. Property name 'PaymentId' is already defined. ReviewId: Name: Each property name in a type must be unique. Property name 'ReviewId' is already defined. – RobVious May 05 '16 at 22:33
  • I've tried so many alterations to what you have here and nothing works. Downvoting for inaccuracy but will correct if we find something that works. – RobVious May 05 '16 at 22:42
  • Sorry, that was off the top of my head. I looked up my code and it actually looks like above. You have to use the WithMany() or make the primary key the foreignkey which I didn't like. I just tested it on my end and it works for me. – Steve Greene May 06 '16 at 14:00
  • This doesn't work. This gives me the reverse of my above dilemma - as if those boxes were swapped. – RobVious May 06 '16 at 18:03
  • Mine has 2 nullable FK. Do you have 'virtual' on both navigation properties unlike your pasted code? Don't worry, I won't down vote your inaccuracy :) – Steve Greene May 06 '16 at 19:44
  • Yeah I'm sure. And I'm not downvoting because I don't like it - just because other people looking for a solution will be misled and should know it doesn't quite work. Sorry if it came off the wrong way :( – RobVious May 06 '16 at 20:08
  • This actually looks like it should work, but it just doesn't. I'm trying to do the same thing--have As and Bs, where A and B can exist without each other, but may optionally have a relationship. Always results in an error "Unable to determine the principal end of an association between the types 'A' and 'B'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations." I don't know WHY there "needs" to be a principal relationship. –  Oct 26 '16 at 21:01
  • To relate one item to another, one of them needs to exist first. [Here is a good explanation](http://stackoverflow.com/questions/6531671/what-does-principal-end-of-an-association-means-in-11-relationship-in-entity-fr) – Steve Greene Oct 27 '16 at 01:20