Stefan
Stefan

Reputation: 12260

How to specify two navigation relations for ef/breeze ("Unable to determine a valid ordering")?

I reverence an entity "monitoring" in two ways:


public class Company
  {
    [Key]
    [DataMember]
    public virtual Guid CompanyId { get; set; }

    [DataMember]
    public virtual Guid? MonitoringId { get; set; }

    [DataMember]
    [ForeignKey("MonitoringId")]
    public virtual Monitoring ActiveMonitoring { get; set; }

    [DataMember]   
    public virtual ICollection<Monitoring> Monitorings { get; set; }  
  }

public class Monitoring
  {
    [Key]
    [DataMember]
    public virtual Guid MonitoringId { get; set; }

    [DataMember]
    public virtual Guid CompanyId { get; set; }

    [DataMember]
    [ForeignKey("CompanyId")]   
    public virtual Company Company { get; set; } 
 }

If I create a new Company and add a new Monitoring to the list of monitorings, I am able to save it with breeze. After saving the new Company, I am able to set the alredy added Monitoring as ActiveMonitoring and save the Company again.

However, If I try to add the Monitoring and set it as ActiveMonitioring and save the Company only once, I get following error:

Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.

Here is example JavaScript code for creating the new Company:

var company = unitofwork.companyRepository.create();
var monitoring = unitofwork.monitoringRepository.create();

//add monitoring to the list of monitorings
monitoring.company(company);
company.monitorings.push(monitoring);

//set monitoring as active monitoring
company.activeMonitoring(monitoring);

unitofwork.commit();

I use EntityFramework 6 and Breeze 1.5.4

I tried to use the annotation "InverseProperty" to make the relations more explicit but did not succeed. I also found some related stackoverflow questions but I could not find a solution for my specific issue:

My classes allow following case: a Monitoring could be part of a list of one Company and be set as active Monitoring for another Company. However, I do not want to allow that case: a Monitoring should only be allowed to be the active Monitoring if it is also part of the list of monitorings of the same Company. Is this the underlying cause of my issue?

Does the Monitoring need two references to the Company, e.g. CompanyForListReference and CompanyForActiveReference? Or is there a solution that works with only one CompanyId?

I also thought about not saving an instance as active Monitoring, but only the index of the active Monitoring in the list of monitorings, e.g. "IndexOfActiveMonitoring". However, if I would like to access the active Monitoring, I would need some extra code to look up the active Monitoring.

=> What is the recommended way to implement the two references from Company to Monitoring?

Upvotes: 0

Views: 119

Answers (2)

Iraj
Iraj

Reputation: 1522

I have changed your classes and made a sample code and the result was successful :

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace TestApp
{
  public class Company
  {
      [Key]
      public virtual Guid CompanyId { get; set; }

      public virtual Guid? MonitoringId { get; set; }

      [ForeignKey("MonitoringId")]
      [InverseProperty("Companies")]
      public virtual Monitoring ActiveMonitoring { get; set; }

      [InverseProperty("Company")]
      public virtual ICollection<Monitoring> Monitorings { get; set; }
  }

  public class Monitoring
  {
      [Key]
      public virtual Guid MonitoringId { get; set; }

      public virtual Guid CompanyId { get; set; }

      [ForeignKey("CompanyId")]
      [InverseProperty("Monitorings")]
      public virtual Company Company { get; set; }

      [InverseProperty("ActiveMonitoring")]
      public virtual ICollection<Company> Companies { get; set; }
  }

  public class DatabaseContext : DbContext
  {
      public DatabaseContext() : base("TestContext")
      {
      }

      public DbSet<Company> Companies { get; set; }

      public DbSet<Monitoring> Monitorings { get; set; }
  }

  class Program
  {
      static void Main(string[] args)
      {
          Company cmp = new Company();
          cmp.CompanyId = Guid.NewGuid();

          using (var db = new DatabaseContext())
          {
              db.Companies.Add(cmp);                

              db.Monitorings.Add(new Monitoring { CompanyId = cmp.CompanyId, MonitoringId = Guid.NewGuid() });
              db.Monitorings.Add(new Monitoring { CompanyId = cmp.CompanyId, MonitoringId = Guid.NewGuid() });

             var m1 = new Monitoring { CompanyId = cmp.CompanyId, MonitoringId = Guid.NewGuid() };
            db.Monitorings.Add(m1);

              db.Companies.Add(new Company { CompanyId = Guid.NewGuid() ,MonitoringId = m1.MonitoringId });


              db.SaveChanges();

          }

      }
  }
}

Upvotes: 0

Richard
Richard

Reputation: 30618

I can only speak for the Entity Framework part of this, but the overall problem is the same. This problem arises because there is a strange ordering of commands you would need to run in order to add the row - it is not possible to do this in one step. You need to insert the company row (and you can add the list of monitorings at the same time), then call savechanges. This generates the IDs for the entities, and you can then use these to update the related ID.

EF is not clever enough to know about doing multiple operations on a single entity, in these cases you need to use explicit transactions and call SaveChanges multiple times. With EF-only, this would be something similar to this:

using (var trans = new TransactionScope())
{
    Company company = new Company();   // ... populate
    company.Monitorings.Add(new Monitoring());
    company.Monitorings.Add(new Monitoring());

    context.Companies.Add(company);
    context.SaveChanges();

    company.ActiveMonitoring = company.Monitorings.First();
    context.SaveChanges();

    trans.Complete()
}

Upvotes: 1

Related Questions