简体   繁体   中英

Relation in entity framework (code first)

I have 2 tables:

tblInvestment
{
    InvestmentId char(10), --pk
    PrimaryPerformanceId char(10)
}

tblPerformance
{
    PerformanceId char(10), --pk,
    InvestmentId char(10)
}

And I created 2 entity clasees for these two table:

[Table("tblInvestment")]
class Investment
{
    [Key]
    public string InvestmentId { get; set; }
    public string PrimaryPerformanceId { get; set; }

    [ForeignKey("PrimaryPerformanceId")]
    public virtual Performance PrimaryPerformance { get; set; }
    [ForeignKey("InvestmentId")]
    public virtual ICollection<Performance> Performances { get; set; }
}

[Table("tblPerformance")]
class Performance
{
    [Key]
    public string PerformanceId { get; set; }
    public string InvestmentId { get; set; }
    [ForeignKey("InvestmentId")]
    public virtual Investment Investment { get; set; }
}

When I create an new record for each table, and call the DbContext.SaveChanges function, I got an exception says: " Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values. " But if I remove the Investment.PrimaryPerformance property, I can save the records to database. Why?

using(MyContext db = new MyContext)
{
    var inv = db.Investments.Add(new Investment{
        InvestmentId = "1",
        PrimaryPerformance = new Performance{
            PerformanceId = "A",
            InvestmentId = "1"
        }
    };
    db.SaveChanges();
}

Here is the stack info:

System.Data.Mapping.Update.Internal.UpdateTranslator.DependencyOrderingError(IEnumerable`1 remainder) System.Data.Mapping.Update.Internal.UpdateTranslator.ProduceCommands() System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter) System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache) System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options) System.Data.Entity.Internal.InternalContext.SaveChanges() System.Data.Entity.Internal.InternalContext.SaveChanges() System.Data.Entity.Internal.LazyInternalContext.SaveChanges() System.Data.Entity.DbContext.SaveChanges()

I think the problem is in your model where you have a double foreign key of "Performance" ==>

[ForeignKey("PrimaryPerformanceId")]
public virtual Performance PrimaryPerformance { get; set; }
[ForeignKey("InvestmentId")]
public virtual ICollection<Performance> Performances { get; set; }

I think that you must remove one of them...

From MSDN:

If placed on a foreign key property, the name of the associated navigation property. If placed on a navigation property, the name of the associated foreign key(s).

You only need to associate the related fields within the same class (if they do not match the Naming conventions). You do so to tell Code First that they belong to the same Foreign Key relation. Code First is then clever enough to figure out the primary keys of the Relation by itself, since you have specified the Key attribute on the related classes.

So your model should look like this instead:

[Table("tblInvestment")]
class Investment
{
    [Key]
    public string InvestmentId { get; set; }

    [ForeignKey("PrimaryPerformance")]
    public string PrimaryPerformanceId { get; set; } // This is the foreign key property           
    public virtual Performance PrimaryPerformance { get; set; } // This is the navigation property

    public virtual ICollection<Performance> Performances { get; set; }
}

[Table("tblPerformance")]
class Performance
{
    [Key]
    public string PerformanceId { get; set; }

    public virtual Investment Investment { get; set; }
}

OK, first off, you need to correct your object model since it gives you an incorrect Db schema as the way it stands now. If you look into the resulting schema, you'll see that EF created an additional InvestmentId (with the name Investment_InvestmentId) on tblPerformance. All you need to do is to specify that your association between Investment and Performance classes represented by Investment.Performances and Performance.Investment properties is bidirectional.

If you prefer data annotations over the fluent API (as it looks) then you need to use the InverseProperty attribute like the following:

[Table("tblPerformance")]
class Performance
{
    [Key]
    public string PerformanceId { get; set; }
    public string InvestmentId { get; set; }

    [InverseProperty("Performances")]
    public virtual Investment Investment { get; set; }
}


Now, the exception that you are getting is very normal. Think about it, you're asking EF to add two objects for you in one transaction: an investment object who has a primary performance object so EF tries to insert the performance record first in order to get a PrimaryPerformanceId for the investment object (with InvestmentId=1), but then it notices that the performance record also needs the very same Investment object (with Id=1) as its Investment. Which one is going to go first? Impossible in one transaction.

Therefore, the only way to make it work is to use two transactions to add your objects:

using(Context db = new Context())
{
    var inv = db.Investments.Add(new Investment() { InvestmentId = "1"});
    db.SaveChanges();

    inv.PrimaryPerformance = new Performance()
    {
        PerformanceId = "A",
        InvestmentId = "1"
    };
    db.SaveChanges();
}

And this code works just fine.

Note: I've used the latest EF release to run this code (v4.3.1). So please update your binaries if you haven't already.

The exception is thrown by TryTopologicalSort function, I guess this function is used for working out the dependency between the records. It only supports one relationship between entities.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM