DataContext.SubmitChanges пытается удалить неправильный объект - PullRequest
2 голосов
/ 16 июля 2011

Программа, приведенная ниже, завершается с ошибкой.Он использует то, что должно быть довольно распространенным вариантом использования, поэтому маловероятно, что это ошибка в платформе (LINQ to SQL не совсем новый).

Может кто-нибудь подсказать, что я делаю неправильно?

Я знаю, что всегда есть возможность сбросить текущий 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 ]

2 голосов
/ 16 июля 2011

Что ж, получается, что LINQ to SQL не особенно нравится, когда вы не называете свои ассоциации. Чтобы приведенный выше код работал правильно, необходимо добавить имя ко всем атрибутам ассоциации, например:

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

(Конечно, имя должно отличаться для связи между Order и OrderDetail.)

В моей книге это явно ошибка. Даже без имен совершенно ясно, какие пары AssociationAttributes образуют ассоциацию. Созданная БД подтверждает это, и LINQ to SQL, очевидно, смог точно определить ассоциации. Однако алгоритм, который определяет набор изменений, кажется, каким-то образом смешивает две ассоциации.

Наконец, если LINQ to SQL требует установки атрибута Name, он должен сообщить об этом, выдав исключение.

0 голосов
/ 16 июля 2011

Оба ордера могут иметь один и тот же первичный ключ. Я не вижу в вашем коде атрибута AutoSync = System.Data.Linq.Mapping.AutoSync.OnInsert. Ключ из базы данных, возможно, не вернулся в первый заказ, когда он был отправлен. Добавление второго даст вам два заказа с одним и тем же ключом, что может испортить отношения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...