如何在 Entity Framework 中 insert/update 主从?

How to insert/update master-detail in Entity Framework?

我正在尝试制作一个使用 Entity Framework 并在同一页面上执行插入和更新的主从 Web 表单。我是 EF 的新手,所以我一定在这里犯了很多错误。你能帮我指出在 EF 上执行 insert/update 的最佳做法是什么吗?我在这里做错了什么?

在此代码中,"New" 模式运行良好,但 "Edit" 模式出现此错误:"An entity object cannot be referenced by multiple instances of IEntityChangeTracker".

    OrdersEntities ordersEntities = new OrdersEntities();

    private Order myOrder
    {
        get { return (Order)Session["myOrder"]; }
        set { Session["myOrder"] = value; }
    }

    public DataTable dtOrderDetails
    {
        get { return (DataTable)ViewState["dtOrderDetails"]; }
        set { ViewState["dtOrderDetails"] = value; }
    }

    private string Mode
    {
        get { return (string)ViewState["mode"]; }
        set { ViewState["_modo"] = value; }
    }

    private void btnSaveOrder_Click(object sender, EventArgs e)
    {
        if (dtOrderDetails.Rows.Count > 0)
        {
            using (ordersEntities)
            {
                using (var contextTransaction = ordersEntities.Database.BeginTransaction())
                {
                    try
                    {
                        if (Mode == "New")
                        {
                            Order newOrder = new Order();
                            OrderDetails newOrderDetails;

                            int maxOrderNumber = ordersEntities.Order.Select(o => o.OrderNumber).DefaultIfEmpty(0).Max();
                            maxOrderNumber++;

                            newOrder.OrderNumber = maxOrderNumber;
                            newOrder.Date = DateTime.ParseExact(txtOrderDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture);
                            newOrder.CustomerID = Convert.ToInt32(ddlCustomer.SelectedValue);
                            newOrder.Status = 1;

                            ordersEntities.Orders.Add(newOrder);

                            foreach (DataRow dt in dtOrderDetails.Rows)
                            {
                                newOrderDetails = new OrderDetails();
                                newOrderDetails.OrderNumer = maxOrderNumber;
                                newOrderDetails.ProductId = Convert.ToInt32(dt["ProductId"]);
                                newOrderDetails.Quantity = Convert.ToInt32(dt["Quantity"]);

                                ordersEntities.OrderDetails.Add(newOrderDetails);
                            }

                            ordersEntities.SaveChanges();
                            contextTransaction.Commit();

                            myOrder = newOrder;
                        }

                        if (Mode == "Edit")
                        {
                            Order editedOrder = myOrder;
                            OrderDetails editedOrderDetails;

                            editedOrder.Date = DateTime.ParseExact(txtOrderDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture);
                            editedOrder.CustomerID = Convert.ToInt32(ddlCustomer.SelectedValue);

                            ordersEntities.Order.Attach(editedOrder);
                            ordersEntities.Entry(editedOrder).State = System.Data.Entity.EntityState.Modified;

                            editedOrder.OrderDetails.Clear();

                            foreach (DataRow dt in dtOrderDetails.Rows)
                            {
                                editedOrderDetails = new OrderDetails();
                                editedOrderDetails.OrderNumer = editedOrder.OrderNumber;
                                editedOrderDetails.ProductId = Convert.ToInt32(dt["ProductId"]);
                                editedOrderDetails.Quantity = Convert.ToInt32(dt["Quantity"]);

                                ordersEntities.OrderDetails.Add(editedOrderDetails);
                            }

                            ordersEntities.SaveChanges();
                            contextTransaction.Commit();
                        }
                    }
                    catch (Exception ex)
                    {
                        contextTransaction.Rollback();
                    }
                }
            }
        }
    }

这是你应该如何处理它。

如果您使用这个简单的接口将 DbContext 抽象出来,那将是最好的:

public interface IDataRepository : IDisposable
{
    IDbSet<Order> Orders { get; set; }

    void Save();
}

当然,你的实现IDataRepository是基于EntityFramework的。请注意,您的 web.config 文件中需要有一个 dataRepositoryConnection 连接字符串:

public class EfDataRepository : DbContext, IDataRepository
{
    public EfDataRepository() : base("dataRepositoryConnection")
    {
    }

    public IDbSet<Order> Orders { get; set; }

    public void Save()
    {
        this.SaveChanges();
    }
}

根据我的经验,您还需要一个 'factory',它会为您提供一个新的数据存储库实例。这允许您成为实例的 'owner',并且您可以安全地处置它。请注意,与 DataContext 的交互应该是最小的——你做你的 Unity of Work 并摆脱它。不要重复使用!您将在下面看到它作为示例。

public class DataRepositoryFactory<T> where T : IDataRepository
{
    private Type dataRepositoryImplementationType;

    public DataRepositoryFactory(T dataRepositoryImplementation)
    {
        if (dataRepositoryImplementation == null)
        {
            throw new ArgumentException("dataRepositoryImplementation");
        }

        this.dataRepositoryImplementationType = dataRepositoryImplementation.GetType();
    }

    public T Create()
    {
        return (T)Activator.CreateInstance(this.dataRepositoryImplementationType);
    }
}

在您的控制器(如果是 MVC 应用程序)或页面后端(表单)中,最好使用 Microsoft Unity 获取 DataRepositoryFactory 的实例。目前,手动构建也足够了。

IDataRepository dataRepository = new EfDataRepository();
var dataRepositoryFactory = new DataRepositoryFactory<IDataRepository>(dataRepository);

此外,您不需要所有这些 Transaction/Commit 内容。它对你来说应该是透明的。 EF 隐式支持它,您不必明确说明。

// See, now you are the 'owner' of the dataRepository
using (var dataRepository = this.dataRepositoryFactory.Create())
{
    if (Mode == "New")
    {
        Order newOrder = new Order();

        // This doesn't make sense. Either generate a random order number (e.g. a Guid), or just use the Order.Id as an order number, although I don't recommend it.
        int maxOrderNumber = dataRepository.Orders.Select(o => o.OrderNumber).DefaultIfEmpty(0).Max();
        maxOrderNumber++;

        newOrder.OrderNumber = maxOrderNumber;
        newOrder.Date = DateTime.ParseExact(txtOrderDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture);
        newOrder.CustomerID = Convert.ToInt32(ddlCustomer.SelectedValue);
        newOrder.Status = 1;

        dataRepository.Orders.Add(newOrder);

        foreach (DataRow dt in dtOrderDetails.Rows)
        {
            OrderDetails newOrderDetails = new OrderDetails();
            newOrderDetails.OrderNumer = maxOrderNumber;
            newOrderDetails.ProductId = Convert.ToInt32(dt["ProductId"]);
            newOrderDetails.Quantity = Convert.ToInt32(dt["Quantity"]);

            newOrder.OrderDetails.Add(newOrderDetails);
        }

        myOrder = newOrder;
    }

    if (Mode == "Edit")
    {
        Order editedOrder = dataRepository.Orders.FirstOrDefault(o => o.Id == myOrder.Id);

        editedOrder.Date = DateTime.ParseExact(txtOrderDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture);
        editedOrder.CustomerID = Convert.ToInt32(ddlCustomer.SelectedValue);
        editedOrder.OrderDetails.Clear();

        foreach (DataRow dt in dtOrderDetails.Rows)
        {
            OrderDetails editedOrderDetails = new OrderDetails();
            editedOrderDetails.OrderNumer = editedOrder.OrderNumber;
            editedOrderDetails.ProductId = Convert.ToInt32(dt["ProductId"]);
            editedOrderDetails.Quantity = Convert.ToInt32(dt["Quantity"]);

            editedOrder.OrderDetails.Add(editedOrderDetails);
        }
    }

    dataRepository.Save();
}

此外,我很确定您在 EF 代码优先方法中错误地设置了 OrderOrderDetails 类 之间的关系。

这是错误的:

OrderDetails newOrderDetails = new OrderDetails();
newOrderDetails.OrderNumer = maxOrderNumber;

如果你post他们在这里,我可以为你修复它们。