Rapid
Rapid

Reputation: 11

Entity Framework Code first error with self referencing objects

I'm trying to create my first EF code first objects into database. Unfortunately when trying to save my objects into DB I get this exception:

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 the code I use to fill the DB:

using (var context = new MySample.Context())
{
    context.Database.CreateIfNotExists();

    CloudFile f = new CloudFile(, "file1");

    CloudDirectory d = new CloudDirectory(CloudObject.CloudContentType.Customer, "directory1");
    d.Nodes.Add(d);

    FileGroup g = new FileGroup();
    g.CloudObjects.Add(d);

    context.FileGroups.Add(g);
    context.SaveChanges();
}

Actually I would like to create an object of type FileGroup. This object contains either files (CloudFile) or directories (CloudDirectory). The directories can contain either other directories or files.

Here are the classes for the mentioned objects:

public class FileGroup {

    public FileGroup()
    {
        CloudObjects = new HashSet<CloudObject>();
    }

    public int FileGroupId { get; set; }

    public string Name { get; set; }

    public virtual ICollection<CloudObject> CloudObjects { get; set; }
}


public abstract class CloudObject {

    public CloudObject(string name)
    {
        Name = name;
    }

    public int CloudObjectId { get; set; }

    public string Name { get; set; }

    public abstract bool hasChildren { get; }

    public int? ParentId { get; set; }

    public virtual Node Parent { get; set; }

    public int? FileGroupId { get; set; }

    public virtual FileGroup FileGroup { get; set; }
}

public class CloudDirectory : CloudObject {

    public CloudDirectory(string name) :base(name)
    {
        Nodes = new HashSet<CloudObject>();
    }

    public override bool hasChildren
    {
        get
        {
            return Nodes.Any();
        }
    }

    public virtual ICollection<CloudObject> Nodes { get; set; }
}

public class CloudFile : CloudObject {

    public CloudFile(string name) : base(name)
    {
    }

    public string ETag { get; set; }

    public override bool hasChildren
    {
        get
        {
            return false;
        }
    }
}

Any idea what I need to change in my objects so that they can be stored successfully do DB?

Upvotes: 1

Views: 203

Answers (1)

Diana
Diana

Reputation: 2226

Several changes in your test code (the code comments explain them):

using (var context = new MySample.Context())
{
    context.Database.CreateIfNotExists();

    CloudFile f = new CloudFile("file1");

    CloudDirectory d = new CloudDirectory("directory1");

    //This seems to be wrong:
    //d.Nodes.Add(d);
    d.Nodes.Add(f); //I guess you don't want d to be its own parent, but want it to be f's parent

    FileGroup g = new FileGroup();
    g.CloudObjects.Add(d);
    //Another way to do it:
    //d.FileGroup = g;

    //You also want f to be in the FileGroup, you have to add it explicitly
    g.CloudObjects.Add(f);
    //Another way to do it: 
    //f.FileGroup = g;

    context.FileGroups.Add(g);
    context.SaveChanges();
}

As the relationships between the entities are a bit too complex for EF automatic conventions to deal with them, you should add explicit mappings for them in your DbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    ...
    modelBuilder.Entity<CloudObject>().HasOptional(x => x.Parent).WithMany(x => x.Nodes).HasForeignKey(x => x.ParentId);
    modelBuilder.Entity<CloudObject>().HasOptional(x => x.FileGroup).WithMany(x => x.CloudObjects).HasForeignKey(x => x.FileGroupId);
}

Note: I assumed here that FileGroup is not mandatory for a given CloudObject. If it is mandatory you should change the method HasOptional to HasRequired.

You should also change the type of this property:

public abstract class CloudObject
{
    ...
    //public virtual Node Parent { get; set; }
    public virtual CloudDirectory Parent { get; set; }
    ...
}

Your migration has to look somewhat like this:

CreateTable(
    "dbo.CloudObjects",
        c => new
            {
                CloudObjectId = c.Int(nullable: false, identity: true),
                Name = c.String(),
                ParentId = c.Int(),
                FileGroupId = c.Int(),
                ETag = c.String(),
                Discriminator = c.String(nullable: false, maxLength: 128),
            })
        .PrimaryKey(t => t.CloudObjectId)
        .ForeignKey("dbo.FileGroups", t => t.FileGroupId)
        .ForeignKey("dbo.CloudObjects", t => t.ParentId)
        .Index(t => t.ParentId)
        .Index(t => t.FileGroupId);

 CreateTable(
    "dbo.FileGroups",
        c => new
            {
                FileGroupId = c.Int(nullable: false, identity: true),
                Name = c.String(),
            })
        .PrimaryKey(t => t.FileGroupId);

Upvotes: 1

Related Questions