简体   繁体   English

实体框架代码First Fluent API配置,用于一对一的识别关系

[英]Entity Framework Code First Fluent API configuration for one to one identifying relationship

I have the following class structure: 我有以下类结构:

在此输入图像描述

How to configure Fluent API to put identifying relationship in Cards table? 如何配置Fluent API以将标识关系放入Cards表中?

I mean 我的意思是

  • Cards Table PK: Id, CustomerId 卡表PK:Id,CustomerId
  • Cards Table FK: CustomerId 卡表FK:CustomerId

I would like the previous Card be deleted when I assign a new one to Customer.Card property. 我想在将新的卡分配给Customer.Card属性时删除之前的卡。

So I defined my classes this way: 所以我用这种方式定义了我的类:

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 looks like this: 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>();
    }
}

Here is the test: 这是测试:

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

        customer.Card = new Amex();
        context.SaveChanges();

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

It does not work at all. 它根本不起作用。 I have this on second save and I do not know how to specify identifying relationship here: 我在第二次保存时有这个,我不知道如何在这里指定识别关系:

Unhandled Exception: System.Data.Entity.Infrastructure.DbUpdateException: An err or occurred while saving entities that do not expose foreign key properties for their relationships. 未处理的异常:System.Data.Entity.Infrastructure.DbUpdateException:保存不公开其关系的外键属性的实体时发生错误或发生。 The EntityEntries property will return null because a singl e entity cannot be identified as the source of the exception. EntityEntries属性将返回null,因为无法将单个实体标识为异常的来源。 Handling of except ions while saving can be made easier by exposing foreign key properties in your entity types. 通过在实体类型中公开外键属性,可以更轻松地在保存时处理除离子。 See the InnerException for details. 有关详细信息,请参阅InnerException。 ---> System.Data.Entity.Core.U pdateException: A relationship from the 'Customer_Card' AssociationSet is in the 'Deleted' state. ---> System.Data.Entity.Core.U pdateException:来自'Customer_Card'AssocSet的关系处于'已删除'状态。 Given multiplicity constraints, a corresponding 'Customer_Card _Target' must also in the 'Deleted' state. 给定多重约束,相应的'Customer_Card _Target'也必须处于'已删除'状态。

UPDATE It's easy to make it work for one-to-many relationships. 更新很容易让它适用于一对多的关系。 You can find a full example below: 您可以在下面找到完整示例:

[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 });
    }
}

The way EF implements one-to-one is to make the dependent entity have a primary key that's also a foreign key to the principle entity. EF实现一对一的方式是使从属实体具有主键,该主键也是主要实体的外键。 So the dependent's PK is naturally constrained to existing principle PK values. 因此,依赖的PK自然受限于现有的PK值。

So using your classes, slightly modified: 所以使用你的类,稍加修改:

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 { }

And the mapping: 和映射:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>().HasRequired(c => c.Card)
                                   .WithRequiredPrincipal();
    modelBuilder.Entity<Card>().HasKey(c => c.CustomerId);
}

So Card only has CustomerId as PK and FK, not two separate fields. 所以Card只有CustomerId作为PK FK,而不是两个单独的字段。

BUT

Trying this, I found out that there's a bug in EF (6.1.2). 试着这个,我发现EF(6.1.2)中有一个错误。 This is what I did: 这就是我做的:

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();
}

(Added Name and Number for convenience). (为方便起见,添加了NameNumber )。

Normally this would be OK. 通常情况下这没关系。 EF is smart enough to see that a 1:1 dependent entity is replaced and it just updates the Number field (effectively deleting the old card). EF很聪明,可以看到1:1依赖实体被替换,它只是更新Number字段(有效地删除了旧卡)。

But EF overlooks the inheritance (for which I used the default, TPH). EF忽略了继承 (我使用了默认的TPH)。 Of course it should also update the discriminator field, but it doesn't. 当然它也应该更新鉴别器字段,但事实并非如此。 You end up with an Amex card, having "Visa" as number if you re-fetch the items from the database. 如果您从数据库中重新获取项目,最终会得到一张Amex卡,并将“Visa”作为号码。

So, sadly, even with this model, you first have to remove the old card, and then add the new one: 所以,遗憾的是,即使使用此模型,您首先必须删除旧卡,然后添加新卡:

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();

This is clumsy enough, not to mention that you'd also want to wrap this in a TransactionScope . 这很笨拙,更不用说你也想在TransactionScope包装它。

Entity Framework doesn't really allow this sort of an operation. 实体框架实际上并不允许这种操作。 You can't "delete" an object from the database simply by trying to replace it with another object. 只需尝试将其替换为另一个对象,就无法从数据库中“删除”对象。 even with Cascade Delete, you still have to issue a delete command in Entity Framework, else you end up with an orphaned item in your context. 即使使用Cascade Delete,您仍然必须在Entity Framework中发出删除命令,否则您最终会在上下文中找到孤立项。 You can try to override the SaveChanges() method to trap this behavior, but it won't be an easy patch. 您可以尝试覆盖SaveChanges()方法来捕获此行为,但它不是一个简单的补丁。

your best bet would be to check if a card exists, and if so remove it before adding the new card. 您最好的选择是检查卡是否存在,如果是,请在添加新卡之前将其删除。 this can easily be wrapped up into a repeatable function call, like so: 这可以很容易地包含在一个可重复的函数调用中,如下所示:

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

Edit 编辑

To be more clear, Entity Framework cannot batch a deletion of a relational object and the addition of a replacement object into the same SaveChanges() call. 更清楚的是,Entity Framework无法批量删除关系对象以及将替换对象添加到同一个SaveChanges()调用中。

This works fine: 这很好用:

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

Note the multiple calls to SaveChanges() . 注意对SaveChanges()的多次调用。 The function provided earlier is more a wrapper function to avoid the extra SaveChanges() call. 前面提供的函数更像是一个包装函数,以避免额外的SaveChanges()调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 实体框架通过流畅的API继承了一对零的一对一关系配置 - Entity Framework inherited one to zero to one relationship configuration with fluent API 用于一对一实体关系的Entity Framework 6代码第一个流利的api配置 - Entity Framework 6 code first fluent api config for one to one entity relationship 与Entity Framework Fluent API的一对一关系 - One to one relationship with Entity Framework Fluent API 实体框架代码优先的一对一关系 - One to One relationship in Entity Framework Code First 使用 Entity Framework Fluent API 的一对一可选关系 - One to one optional relationship using Entity Framework Fluent API 使用Fluent API在实体框架中创建一对多关系 - Creating one to many relationship in Entity Framework using Fluent API 在实体框架6流畅的api中的视图上映射多对一关系 - Mapping a many to one relationship over a view in entity framework 6 fluent api 使用Entity Framework Code First和Fluent API配置许多一对一关系 - Configuring many One-To-One relationships with Entity Framework Code First and Fluent API 实体框架代码优先方法一对一流利的api映射 - Entity Framework code-first approach one-to-one fluent api mapping Entity Framework 5.0代码首先是一对一和一对多的关系 - Entity Framework 5.0 code first one to one and one to many relationship
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM