InvalidCastException:插入具有多个关系的实体时EF失败

时间:2013-08-12 08:31:27

标签: c# entity-framework .net

如果一个实体有多个关系并且我尝试同时插入数据,那么EF会抛出InvalidCastException。

举个例子,想象一下这些域类:

public class Person : Entity<Guid>
{
    public string Name { get; set; }
    public ICollection<Watch> Watches { get; set; }
    public ICollection<Shoe> Shoes { get; set; }
}

public class Shoe : Entity<Guid>
{
    public string Brand { get; set; }
}

public class Watch : Entity<Guid>
{
    public string Brand { get; set; }
}

用例#1(完美地工作):

using (var context = new MultipleRelationshipsContext())
{
    var watches =
        new List<Watch>() {
            new Watch { Brand = "Rolex" }
        };

    context.Set<Person>().Add(
        new Person
        {
            Name = "Warren Buffett",
            Watches = watches
        }
    );
}

用例#2(完美地工作):

using (var context = new MultipleRelationshipsContext())
{
    var shoes =
        new List<Shoe>() {
            new Shoe { Brand = "Cole Haan" }
        };

    context.Set<Person>().Add(
        new Person
        {
            Name = "Barack Obama",
            Shoes = shoes
        }
    );
}

用例#3(InvalidCastException):

using (var context = new MultipleRelationshipsContext())
{
    var watches =
        new List<Watch>() {
            new Watch { Brand = "Casio" }
        };

    var shoes =
        new List<Shoe>() {
            new Shoe { Brand = "New Balance" }
        };

    context.Set<Person>().Add(
        new Person
        {
            Name = "Steve Jobs",
            Watches = watches,
            Shoes = shoes
        }
    );
}

在第三种情况下,抛出InvalidCastException,表示EF无法从“EntityFrameworkMultipleRelationships.Entities.Watch”转换为“EntityFrameworkMultipleRelationships.Entities.Shoe”。

我是EF的新手,但我认为这里出现了一些错误。

我希望能提出任何可能的解决方案!

PD。:为了尽可能快地测试自己,请下载此VS2012解决方案:https://dl.dropboxusercontent.com/u/22887057/EntityFrameworkMultipleRelationships.zip。按照README.txt按照代码优先模式创建数据库。

更新

正如@Chris指出的那样,问题在于EF认为Shoe和Watch实体是相同的。这是由于实施的覆盖等于错误造成的。这实际上是问题的根源:

public abstract class Entity<T>
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("Id")]
    public T Id { get; set; }

    public override bool Equals(object obj)
    {
        Entity<T> entityOfT = obj as Entity<T>;
        if (entityOfT == null)
            return false;

        return object.Equals(this.Id, entityOfT.Id);
    }

    public override int GetHashCode()
    {
        return this.Id.GetHashCode();
    }
}

如果两个不同的实体类型(如WatchShoe)具有相同的Id,则EF会将它们视为等于。

添加运行时类型检查以覆盖Equals会考虑实体类型,因此可以解决此问题。

...
return this.GetType() == entityOfT.GetType() && object.Equals(this.Id, entityOfT.Id);
...

2 个答案:

答案 0 :(得分:1)

不确定为什么抛出该特定错误,但由于您的Entity.Id定义,EF似乎变得混乱。如果将PK字段移动到Shoe和Watch类定义,它就可以工作。此外,如果您将watch和shoe对象添加到各自的DbContext集,然后通过集合将它们添加到Person对象,它也可以。在任何情况下,以某种方式稍微更明确地解决问题。

答案 1 :(得分:1)

我不确定实体框架的内部结构(虽然我可能会尝试看一看),但我认为你的问题可能是两件事的组合:

1)实体框架自动生成存储实体的集合的方式。

2)你有两个具有相同Guid的子对象,即使它们的类型不同,也会返回Equals(..)为真。

如果您执行以下操作,您的代码将会运行:

定义Shoe / Watch(或两者),并将它们添加到相应的集合中:

context.Set<Shoe>().Add(aShoe);

或为WatchShoe

的Guid定义不同的值
Watch tWatch = new Watch { Brand = "Casio", Id = new System.Guid("00000000-0000-0000-0000-000000000001") };

如果您没有执行上述任何一项操作,并且按照第三个示例操作,则可以按照调试程序进行操作,并发现您将使用Equals来调用WatchShoe,结果是真的 - 我假设这是实体框架抛出异常的点。

希望能够了解更多关于EF内部人员的人能够指出为什么会出现这种情况。