DataContext.SubmitChanges尝试删除错误的实体

时间:2011-07-15 22:08:02

标签: .net linq-to-sql

以下程序失败,但有例外。它运行必须是一个非常常见的用例,所以它似乎不太可能是框架中的错误(LINQ to SQL并不是全新的)。

有人能说出我做错了吗?

我知道总是可以选择转储当前的DataContext并继续使用新的DataContext,但在我的场景中这样做太浪费了(因为这会迫使我重新加载数千个实体)。

(我没有使用LINQ to SQL设计器,因为这段代码在某些时候也应该在WP7.1上运行)。

using System;
using System.Linq;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.IO;

sealed class Context : DataContext
{
    internal Context(string databaseFile) : base(databaseFile)
    {
        this.Customers = this.GetTable<Customer>();
        this.Orders = this.GetTable<Order>();
        this.OrderDetails = this.GetTable<OrderDetail>();
    }

    internal readonly Table<Customer> Customers;
    internal readonly Table<Order> Orders;
    internal readonly Table<OrderDetail> OrderDetails;
}

[Table]
sealed class Customer
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true)]
    private int Id { get; set; }

    [Column]
    private int Whatever { get; set; }

    private EntitySet<Order> orders;

    public Customer()
    {
        this.orders = new EntitySet<Order>(
            order => order.Associate(this), order => order.Associate(null));
    }

    [Association(Storage = "orders", OtherKey = "CustomerId")]
    internal EntitySet<Order> Orders { get { return this.orders; } }
}

[Table]
sealed class Order
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true)]
    private int Id { get; set; }

    [Column]
    private int CustomerId { get; set; }

    private EntityRef<Customer> customer;
    private readonly EntitySet<OrderDetail> orderDetails;

    public Order()
    {
        this.orderDetails = new EntitySet<OrderDetail>(
            detail => detail.Associate(this),
            detail => detail.Associate(null));
    }

    internal void Associate(Customer newCustomer) { this.customer.Entity = newCustomer; }

    [Association(Storage = "customer", ThisKey = "CustomerId", IsForeignKey = true)]
    internal Customer Customer { get { return this.customer.Entity; } }

    [Association(Storage = "orderDetails", OtherKey = "OrderId")]
    internal EntitySet<OrderDetail> OrderDetails { get { return this.orderDetails; } }
}

[Table]
sealed class OrderDetail
{
    [Column(IsPrimaryKey = true, IsDbGenerated = true)]
    private int Id { get; set; }

    [Column]
    private int OrderId { get; set; }

    private EntityRef<Order> order;

    internal void Associate(Order newOrder) { this.order.Entity = newOrder; }

    [Association(Storage = "order", ThisKey = "OrderId", IsForeignKey = true)]
    internal Order Order { get { return this.order.Entity; } }
}

class Program
{
    static void Main()
    {
        var exeDirectory = Path.GetDirectoryName(
            typeof(Program).Assembly.ManifestModule.FullyQualifiedName);
        var dataDirectory = Path.Combine(exeDirectory, Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(dataDirectory);
        var dataFile = Path.Combine(dataDirectory, "DB.sdf");

        using (var context = new Context(dataFile))
        {
            context.CreateDatabase();

            // Insert a Customer
            var customer = new Customer();
            context.Customers.InsertOnSubmit(customer);
            context.SubmitChanges();

            // Insert the first Order
            var order1 = new Order();
            customer.Orders.Add(order1);
            context.SubmitChanges();

            // Insert the first OrderDetail
            var detail1 = new OrderDetail();
            order1.OrderDetails.Add(detail1);
            context.SubmitChanges();

            // Insert the second OrderDetail
            order1.OrderDetails.Add(new OrderDetail());
            context.SubmitChanges();

            // Delete the first OrderDetail
            context.OrderDetails.DeleteOnSubmit(detail1);
            order1.OrderDetails.Remove(detail1);

            // Everything works as expected up to this point. For all the
            // changes above, context.GetChangeSet() has always been
            // showing the expected changes.

            // This succeeds. As expected, we now have a single Customer
            // with a single Order and a single OrderDetail in the database.
            context.SubmitChanges();

            // Add a second Order 
            var order2 = new Order();
            customer.Orders.Add(order2);

            // The following fails with an InvalidOperationException with
            // the message:
            //     An attempt was made to remove a relationship between a
            //     Customer and a Order. However, one of the relationship's
            //     foreign keys (Order.CustomerId) cannot be set to null.
            //
            // It is absolutely unclear why an attempt is made to
            // delete/remove an Order. In the code above we're only
            // ever deleting an OrderDetail, *not* an Order.
            context.SubmitChanges();
        }
    }
}

2 个答案:

答案 0 :(得分:2)

嗯,事实证明,当你没有命名你的关联时,LINQ to SQL并不特别喜欢它。要使上面的代码正常工作,需要将名称添加到所有Association属性,如下所示:

[Association(Name = "Customer_Order", Storage = "orders", OtherKey = "CustomerId")]
internal EntitySet<Order> Orders { get { return this.orders; } }

(当然,Order和OrderDetail之间的关联名称需要不同。)

在我的书中,这显然是一个错误。即使没有名称,它也很清楚什么是AssociationAttributes对形成了一个关联。创建的DB确认了这一点,LINQ to SQL显然能够完美地确定关联。但是,确定变更集的算法似乎在某种程度上混淆了这两种关联。

最后,如果LINQ to SQL要求设置Name属性,它应该通过抛出异常来进行通信。

答案 1 :(得分:0)

两个订单可能具有相同的主键。我没有在代码中看到AutoSync = System.Data.Linq.Mapping.AutoSync.OnInsert属性。数据库中的密钥在提交时可能没有恢复到第一个顺序。添加第二个将给你两个具有相同密钥的订单,这可能会破坏关系。