0

I have two classes as such:

public class Event
{
    [Key]
    [Required]
    public long Id { get; set; }

    [InverseProperty("Event")          
    public virtual Record Record{ get; set; }
}


public class Record
{
    [Key]
    [Required]
    public int Id { get; set; }

    [ForeignKey("Event")]
    public long? EventId { get; set; }

    public virtual Event Event{ get; set; }
}

However I get the expected multiplicity errors with this setup. What I want to end up with is:

  • Event existing without a Record

  • Record existing without an Event

  • Event existing with a Record (and have the record store the Event Id)

Is this possible and do I have to use FluentApi as opposed to Data Annotations?

I do not want to add any fields or keys to the Event table.

TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
Seb
  • 410
  • 1
  • 6
  • 20

2 Answers2

1

In EF 6.x, you have to configure with Fluent API as follows:

 modelBuilder.Entity<Record>()
       .HasOptional(x => x.Event)
       .WithOptionalPrincipal(x => x.Record)
       .Map(a => a.MapKey("RecordId"));

In EF Core, Your current model set up is good enough! Don't need to do anything more.

TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
1

You were correct, giving only one of the two sides of the relation a foreign key. I first thought that both sides needed a nullable foreign key. However, if you would do that, then Event 1 could have foreign key to Record 2 with a foreign key to Event 3 with a foreign key to Record 4, etc.

Apparently in these situations, only one of the two sides should have a foreign key.

Looking at configure one-to-zero-or-one relation, I see the following is enough:

class Event
{
    public long Id { get; set; }
    public string Name { get; set; }

    // Every Event has zero or one Record
    public virtual Record Record { get; set; }
}
class Record
{
    public long Id { get; set; }
    public string Name { get; set; }

    // Every Record has zero or one Event
    public virtual Event Event { get; set; }
}

And the DbContext:

class OneToOneContext : DbContext
{
    public OneToOneContext(): base("OneToOneDemo"){}

    public DbSet<Event> Events { get; set; }
    public DbSet<Record> Records { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Record>()
            .HasOptional(record => record.Event)
            .WithOptionalDependent(ev => ev.Record)
            .WillCascadeOnDelete(false);
    }
}

The fluent API informs the model builder that every Record has zero or one Event, with zero or one Record. The Record is declared dependent. This ensures that Record will have the foreign in this relation.

Note that I didn't have to add any other Attributes. Because I followed the code first conventions entity framework was able to detect the primary keys and the relation between the tables.

I was able to add: Event without Record, Record without Event, Event with Record and Record with Event:

using (var dbContext = new OneToOneContext())
{
    dbContext.Events.Add(new Event { Name = "Event without Record"});
    dbContext.Records.Add(new Record { Name = "Record without Event" });

    dbContext.Events.Add(new Event
    {
        Name = "Event A with Record",
        Record = new Record { Name = "Record of Event A" },
    });

    dbContext.Records.Add(new Record
    {
        Name = "Record B with Event",
        Event = new Event { Name = "Event of Record B" },
    });

    dbContext.SaveChanges();
}
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • Hi there, this has mostly worked the way I want it to and will scaffold a migration and allow me to use navigation properties, however I will be saving these entities separately as we use service/repo patterns. For instance, the Event gets added before the Record, so at the point of saving the record I will only have the Event Id (due to the service/repo pattern obfuscating it) and ideally would just want to attach the event id to the record and save it. The alternative and long way around is I have to query the event table and pull that entity out and attach it. – Seb Jan 21 '19 at 15:53
  • @Sep, Have you tried my solution to overcome the problem you are facing? – TanvirArjel Jan 21 '19 at 16:19
  • @TanvirArjel Hi unfortunately when I do this it adds a column to the Event table as well and I need to avoid doing that as it's a large table already. – Seb Jan 21 '19 at 16:41
  • @Seb May be it would not be possible without adding the column to the event table in `Entity Framework 6.X` but you can do this in `EF Core` – TanvirArjel Jan 21 '19 at 16:43
  • @TanvirArjel I see, unfortunately we are not quite ready to move to EF Core just yet! – Seb Jan 21 '19 at 16:44
  • @Seb then nothing to do :'( but you have to follow the solution I have shown you. Good luck. – TanvirArjel Jan 21 '19 at 16:45