0

I have a requirement whereby I need to link 2 entities in the database (using Code First generation) but I'm a little unsure how to define the foreign keys/relationship so I can access each item from the other.

Basically, a Job can exist without any requirement for a Ticket. A Ticket can also exist without a Job, but ultimately a Job will be created for an existing Ticket.

I'm using Entity Framework 6

public class Job
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid JobId { get; set; }

    //Other Properties

    public virtual Ticket Ticket { get; set; }
}

public class Ticket
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid TicketId { get; set; }

    //Other Properties

    public virtual Job Job { get; set; }
}

Then, essentially what I want to be able to do is select a Job with it's Ticket (if it has one) and a Ticket with it's Job (if it has one).

i.e.

var tickets = context.Tickets
              .Include(ticket => ticket.Job);

and

var job = context.Jobs
              .Include(job => job.Ticket);

Do I put a nullable Foreign Key on both Tables?

public class Job
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid JobId { get; set; }

    //Other Properties

    public Guid? TicketId { get; set; )

    [ForeignKey("TicketId")]
    public virtual Ticket Ticket { get; set; }
}

public class Ticket
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid TicketId { get; set; }

    //Other Properties

    public Guid? JobId { get; set; )

    [ForeignKey("JobId ")]    
    public virtual Job Job { get; set; }
}

...or is there a better way to do this (ideally using Data Annotations)?

Thanks in advance for any guidance offered.

Edit: The following seems to work but of course doesn't guarantee a Unique Key on Job for TicketId and on TicketId for JobId: - Correction: This doesn't work for retrieving the Foreign Key properties - see below

public class Job
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid JobId { get; set; }

    //Other Properties

    [InverseProperty("Jobs")]
    public Guid? TicketId { get; set; )

    [ForeignKey("TicketId")]
    public virtual ICollection<Ticket> Tickets { get; set; }
}

public class Ticket
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid TicketId { get; set; }

    //Other Properties

    [InverseProperty("Tickets")]
    public Guid? JobId { get; set; )
  
    [ForeignKey("JobId ")]
    public virtual ICollection<Job> Jobs { get; set; }
}

EDIT: See Serge answer for .NET Core. I've tried the above and that doesn't retrieve the Child properties for each Entity. I.e. no Ticket when retrieving Jobs and no Job when retrieving Tickets.

Still stuck if anyone has any ideas!

Serge
  • 40,935
  • 4
  • 18
  • 45
ledragon
  • 299
  • 6
  • 17
  • I believe you're right. That's simply what you do. A job can have a ticket or not. That is - the ticketId for the job can be null or not null and the other way round. – Qudus Jul 10 '21 at 10:02
  • Hi there, actually I just tried that and got the following error: "Unable to determine the principal end of an association between the types 'Data.Models.Ticket' and 'Data.Models.Job'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations." – ledragon Jul 10 '21 at 10:42
  • Which EF version? In EF6 it's not that easy: https://stackoverflow.com/a/22237880/861716 – Gert Arnold Aug 05 '21 at 19:25
  • Hi Gert, Thanks for the reply. Yes not easy in EF6. I'll have a good read over your great answer. – ledragon Aug 23 '21 at 10:18

2 Answers2

2

You have to fix your tables. Change Guid to int if you want a key to be auto-incremented. Also notice Nullable TicketId

public class Job
{
      [Key]
     public int Id { get; set; }
      public string Name { get; set; }
    
     public int? TicketId {get; set;}

    [ForeignKey(nameof(TicketId ))]
    [InverseProperty("Job")]
    public virtual Ticket Ticket { get; set; }
}

public class Ticket
{
      [Key]
     public int Id { get; set; }
      public string Name { get; set; }
    //Other Properties

 
    [InverseProperty("Ticket")]
    public virtual Job Job { get; set; }
}

this model was succesfully tested in Vs 2019 and MS SQL Server using this code

            var ticket = new Ticket { Name = "Ticket1" };
            _context.Tickets.Add(ticket);
            _context.SaveChanges();

            var job = new Job { Name = "Job1", TicketId=1};
            _context.Jobs.Add(job);
            _context.SaveChanges();

            var job2 = new Job { Name = "Job2" };
            _context.Jobs.Add(job2);
            _context.SaveChanges();

            var jobs = _context.Jobs.Include(i => i.Ticket).ToList();
            var tickets = _context.Tickets.Include(i => i.Job).ToList();
Serge
  • 40,935
  • 4
  • 18
  • 45
  • thanks for the reply. I'm using Guids as Primary Keys but that's working fine as is. I;ve tried your suggestion but am getting the error: "Unable to determine the principal end of an association between the types 'Data.Models.Job' and 'Data.Models.Ticket'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations." when adding the migration. Any ideas? – ledragon Jul 10 '21 at 12:02
  • @ledragon try again. It is hard to config since you don't know what is the first , what is the second. I can' t save both foreign keys in tables, foreign key should be only one – Serge Jul 10 '21 at 12:21
  • Thanks for the amendment. unfortunately no joy yet. When I insert a new Ticket I get the following SQL Error: "The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Ticket_dbo.Job_TicketId". The conflict occurred in database "xxx", table "dbo.Job", column 'JobId'." Appreciate your continued help! – ledragon Jul 10 '21 at 12:36
  • @ledragon Pls post code how you insert the new thicket – Serge Jul 10 '21 at 12:41
  • just in the standard EF way. I create a Ticket, add to the context and call SaveChanges. There's nothing special about how the Ticket is created. The only thing that has worked so far is to create a foreign key on each Entity and have the relationship as a one to many or zero. i.e on Ticket have a property ICollection Jobs and on Job ICollection Tickets and know that there will only be unique TicketId in Jobs and unique JobId in Tickets. Maybe this is the way to go? – ledragon Jul 10 '21 at 13:11
  • I changed my code. Could you try again pls? – Serge Jul 10 '21 at 13:14
  • Unfortunately that returned the same error when generating the migration: "Unable to determine the principal end of an association between the types 'Data.Models.Job' and 'Data.Models.Ticket'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations." – ledragon Jul 10 '21 at 13:22
  • I've amended the original question to show using One To Many or Zero. Wondering of this might be the way to acheive it as a compromise – ledragon Jul 10 '21 at 13:23
  • @ledragon I have tested the model in VS 2019 . I created DB and insert 2 new records. Everything is working fine. – Serge Jul 10 '21 at 13:36
  • OK Great...I'll try again. Thanks muchly for taking the trouble to respond and test. I'll mark as answer when I've tested also. Have you managed to retrieve a Job with attached Ticket and a Ticket with attached Job? – ledragon Jul 10 '21 at 13:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/234720/discussion-between-ledragon-and-serge). – ledragon Jul 10 '21 at 13:41
  • @ledragon Yes, select is working fine too. – Serge Jul 10 '21 at 13:46
  • Still same error for me when creating the migration but I'll persevere as clearly it's possible. Thanks for your time @Serge. – ledragon Jul 10 '21 at 13:57
  • What .net version are you using? – Serge Jul 10 '21 at 13:58
  • .NET v4.7.2 EFv6 – ledragon Jul 10 '21 at 13:59
  • I am using net 5, but it will be working at any net core. I don't know about EF6. Sorry. Try to use public virtual ICollection Jobs { get; set; } – Serge Jul 10 '21 at 14:03
  • Yes I think I'll have to. Thanks for your time on this! Much appreciated. – ledragon Jul 10 '21 at 14:16
0
public class Job
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid JobId { get; set; }

    //Other Properties

    

    [ForeignKey("Ticket")]
    public int TicketId { get; set; }

    public virtual Ticket Ticket { get; set; }
    
    public virtual List<Job> Jobs { get; set; }

}

public class Ticket
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid TicketId { get; set; }

    //Other Properties

  

    [ForeignKey("Job")]   
    public int JobId { get; set; }


    public virtual Job Job { get; set; }
    
    public virtual List<Ticket> Tickets { get; set;}

}