开发者

DataContext.SubmitChanges attempts to remove wrong entity

The program below fails with an exception. It exercises what must be a pretty common use case, so it seems unlikely that this is a bug in the framework (LINQ to SQL isn't exactly brand new).

Can anyone suggest what I'm doing wrong?

I know that there's always the option to dump the current DataContext and continue with a new one but in my scenario that would be too wasteful (as that would force me to reload thousands of entities).

(I'm not using the LINQ to SQL designer because this code should at some point also run on 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开发者_如何学JAVA.
            //
            // 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();
        }
    }
}


Well, it turns out that LINQ to SQL doesn't particularly like it when you don't name your associations. To make the code above work correctly, a name needs to be added to all Association attributes, like so:

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

(Of course the name needs to be different for the association between Order and OrderDetail.)

In my book this is clearly a bug. Even without the names it is crystal-clear what pairs of AssociationAttributes form an association. The created DB confirms this, there LINQ to SQL was obviously able to determine the associations perfectly. However, the algorithm that determines the changeset seems to somehow confuse the two associations.

Finally, if LINQ to SQL requires the Name attribute to be set, it should communicate this by throwing an exception.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜