C# 一对一标识关系的实体框架代码首个Fluent API配置

C# 一对一标识关系的实体框架代码首个Fluent API配置,c#,entity-framework,entity-framework-6,code-first,ef-fluent-api,C#,Entity Framework,Entity Framework 6,Code First,Ef Fluent Api,我的班级结构如下: 如何配置Fluent API将标识关系放入Cards表中 我是说 卡片表主键:Id,CustomerId 卡片表FK:客户ID 我希望在将新卡分配给Customer.Card属性时删除上一张卡 所以我用这种方式定义了我的类: public class Customer { public int Id { get; private set; } public virtual Card Card { get; set; } } public abstrac

我的班级结构如下:

如何配置Fluent API将标识关系放入Cards表中

我是说

  • 卡片表主键:Id,CustomerId
  • 卡片表FK:客户ID
我希望在将新卡分配给Customer.Card属性时删除上一张卡

所以我用这种方式定义了我的类:

public class Customer
{
    public int Id { get; private set; }
    public virtual Card Card { get; set; }
}

public abstract class Card
{
    public int Id { get; private set; }
}

public class Visa : Card
{
}

public class Amex : Card
{
}
DbContext如下所示:

public class Context : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Card> Cards { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Customer>()
            .HasRequired(c => c.Card)
            .WithRequiredPrincipal()
            .Map(a => a.MapKey("CustomerId"))
            .WillCascadeOnDelete();

        modelBuilder.Entity<Card>();
    }
}
它根本不起作用。我在第二次保存时有此项,我不知道如何在此处指定标识关系:

未处理的异常: System.Data.Entity.Infrastructure.DbUpdateException:错误或 保存不公开外键的实体时发生 属性的关系。EntityEntries属性将 返回null,因为无法将单个实体标识为 异常的来源。可以在保存时处理例外离子 通过在实体类型中公开外键属性变得更容易。 有关详细信息,请参阅InnerException。-->System.Data.Entity.Core.U pdateException:来自“客户卡”关联的关系 处于“已删除”状态。给定多重性约束,一个 对应的“客户卡” _目标“”也必须处于“已删除”状态

更新一对多关系很容易实现。您可以在下面找到完整的示例:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var context = new Context();
        var customer = new Customer();
        context.Customers.Add(customer);
        customer.Cards.Add(new Visa());
        context.SaveChanges();

        customer.Cards[0] = new Amex();
        context.SaveChanges();

        Assert.AreEqual(1, context.Cards.Count());
    }
}

public class Customer
{
    public Customer()
    {
        Cards = new List<Card>();
    }

    public int Id { get; private set; }
    public virtual List<Card> Cards { get; set; }
}

public abstract class Card
{
    public int Id { get; private set; }
    public int CustomerId { get; private set; }
}

public class Visa : Card
{
}

public class Amex : Card
{
}

public class Context : DbContext
{
    static Context()
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
    }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<Card> Cards { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Customer>()
            .HasMany(c => c.Cards)
            .WithRequired()
            .HasForeignKey(c => c.CustomerId)
            .WillCascadeOnDelete();

        modelBuilder.Entity<Card>()
            .HasKey(c => new { c.Id, c.CustomerId });
    }
}
[TestClass]
公共类UnitTest1
{
[测试方法]
公共void TestMethod1()
{
var context=新上下文();
var customer=新客户();
context.Customers.Add(客户);
customer.Cards.Add(新的Visa());
SaveChanges();
customer.Cards[0]=新美国运通();
SaveChanges();
aresequal(1,context.Cards.Count());
}
}
公共类客户
{
公众客户()
{
卡片=新列表();
}
public int Id{get;private set;}
公共虚拟列表卡{get;set;}
}
公共抽象类卡片
{
public int Id{get;private set;}
public int CustomerId{get;private set;}
}
公共类签证:信用卡
{
}
美国运通公共类:信用卡
{
}
公共类上下文:DbContext
{
静态上下文()
{
SetInitializer(新的DropCreateDatabaseAlways());
}
公共数据库集客户{get;set;}
公共数据库集卡{get;set;}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
基于模型创建(modelBuilder);
modelBuilder.Entity()
.HasMany(c=>c.Cards)
.WithRequired()
.HasForeignKey(c=>c.CustomerId)
.WillCascadeOnDelete();
modelBuilder.Entity()
.HasKey(c=>new{c.Id,c.CustomerId});
}
}

实体框架实际上不允许这种操作。您不能仅仅通过尝试用另一个对象替换对象来从数据库中“删除”对象。即使使用级联删除,您仍然必须在实体框架中发出Delete命令,否则您的上下文中会出现孤立项。您可以尝试覆盖
SaveChanges()
方法来捕获此行为,但这不是一个容易的补丁

你最好的办法是检查一张卡是否存在,如果存在,在添加新卡之前移除它。这可以很容易地包装成一个可重复的函数调用,如下所示:

public void AddCard(Customer customer, Card card, Context context)
{
    if (customer.Card != null)
    {
        context.Cards.Remove(customer.Card);
    }
    customer.Card = card;
}
编辑

更清楚地说,Entity Framework不能在同一调用中批量删除关系对象和添加替换对象

这很好:

Customer.Card = null;
SaveChanges();
Customer.Card = new Amex();
SaveChanges();

注意对
SaveChanges()
的多次调用。前面提供的函数更像是一个包装函数,以避免额外的
SaveChanges()
调用。

EF实现一对一的方法是使依赖实体具有一个主键,该主键也是主体实体的外键。因此,依赖项的PK自然被约束为现有的主PK值

所以使用您的类,稍微修改一下:

public class Customer
{
    public int CustomerId { get; private set; }
    public virtual Card Card { get; set; }
}

public abstract class Card
{
    public int CustomerId { get; private set; }
}

public class Visa : Card { }

public class Amex : Card { }
以及映射:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>().HasRequired(c => c.Card)
                                   .WithRequiredPrincipal();
    modelBuilder.Entity<Card>().HasKey(c => c.CustomerId);
}
(为方便起见,添加了
名称
编号)

通常情况下,这是可以的。EF足够聪明,可以看到1:1的从属实体被替换,它只更新
编号
字段(有效地删除旧卡)

但是EF忽略了继承(我使用了默认值TPH)。当然,它还应该更新鉴别器字段,但它不会。最后你会得到一张
Amex
卡,如果你从数据库中重新取回物品,卡上的号码就是“Visa”

因此,遗憾的是,即使使用这种型号,您也必须先移除旧卡,然后添加新卡:

var cst = db.Customers.Include(c => c.Card).Single(c => c.CustomerId == 1);
db.Cards.Remove(cst.Card);
db.SaveChanges();

cst.Card = new Visa { Number = "Visa" };
db.SaveChanges();

这已经够笨拙的了,更不用说你还想把它包装在一个
TransactionScope

中。问题可能在于你的单元测试:
context.Customers.Add(new Customer())不添加与
var customer=new customer()相同的对象谢谢,我更新了问题。我仍然不知道如何在这里指定标识关系…我猜您的卡类中缺少CustomerId,请您展示一下您是如何看到的?我已经试过了…
public抽象类卡片{public int Id{get;private set;}}
看起来缺少
CustomerId
删除关系会删除依赖对象。在EntityCollection上调用Remove方法会标记要删除的关系和从属对象。是的,但您没有在代码示例中调用
Remove()
方法。如果将对象指定给
null
,它将在下一次
SaveChanges()
事件中被删除。但是,将对象指定给另一个对象是错误的,因为在
SaveChanges()
同步对象之前,该对象仍处于已删除状态。您的错误甚至验证了这一点,说>关系是
using (var db = new TempModelsContext())
{
    var cst = new Customer { Name = "Customer1", 
                             Card = new Amex { Number = "Amex" } };
    db.Customers.Add(cst);
    db.SaveChanges();
}

using (var db = new TempModelsContext())
{
    var cst = db.Customers.Include(c => c.Card).Single(c => c.CustomerId == 1);
    cst.Card = new Visa { Number = "Visa" };
    db.SaveChanges();
}
var cst = db.Customers.Include(c => c.Card).Single(c => c.CustomerId == 1);
db.Cards.Remove(cst.Card);
db.SaveChanges();

cst.Card = new Visa { Number = "Visa" };
db.SaveChanges();