65.9K
CodeProject 正在变化。 阅读更多。
Home

LINQ to SQL - 分离实体

starIconstarIconstarIconstarIconstarIcon

5.00/5 (9投票s)

2009年7月12日

CPOL

7分钟阅读

viewsIcon

104687

downloadIcon

1039

使用 LINQ to SQL 时轻松分离实体。

引言

如何使用 LINQ to SQL 的独立实体?使用 LINQ to SQL 的每个开发人员都会沮丧地问这个问题。分离在哪里?如何将这些实体用于服务、JSON、封装和多个数据上下文?在构建一个健壮的框架时,这些都是常见的问题。默认情况下,LINQ to SQL 不喜欢释放其实体,并且认为实体不应该与上下文分离。 PLINQO 可以非常轻松地分离 LINQ to SQL 实体。我们将通过展示如何手动添加分离功能来使用 LINQ to SQL,来引导您了解 PLINQO 中是如何实现的。

设置

我们将使用 Petshop 数据库来演示分离 LINQ to SQL 实体。每个表都已添加了一个类型为 `timestamp` 的 `RowVersion` 列。LINQ to SQL 使用 `RowVersion` 列执行乐观并发检查,并且在没有 `RowVersion` 列的情况下,或者在 DBML 中未将 `UpdateCheck` 设置为 `Never`(默认值为 `Always`)的情况下,不会允许您将修改后的实体附加到数据上下文。

从 DataContext 分离

为了确保实体完全分离,我们需要检查每个子实体、子实体列表以及所有延迟加载的属性。我们将逐步介绍在 `Product` 实体上实现分离的过程。下面是 DBML 设计器中 `Product` 实体及其所有依赖项的视图。

`Product` 实体包含了我们在分离实体时遇到的所有不同场景。`Category` 是一个子实体,`Item` 是一个子实体列表,我们将 `Descn` 配置为延迟加载。

要分离 `Product` 实体,我们需要做的第一件事是创建一个 `Product` 部分类。要为 `Product` 实体创建部分类,请在 LINQ to SQL 设计器中右键单击 `Product` 实体,选择“查看代码”,就会为 `Product` 实体创建一个部分类。我们将把以下方法添加到 `Product` 部分类中。

partial class Product
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;

        PropertyChanging = null;
        PropertyChanged = null;
    }
}

首先,会进行一个检查以验证实体是否已附加到上下文。这可能被认为有点hack,但 LINQ to SQL 实体通过其 `PropertyChanged` 和 `PropertyChanging` 事件处理程序参与 `DataContext` 的更改通知。LINQ to SQL 的 `DataContext` 使用 `INotifyPropertyChanging` 和 `INotifyPropertyChanged` 接口来跟踪对象。这意味着 `PropertyChanged` 和 `PropertyChanging` 事件管理对 `DataContext` 的附加。对该事件是否被处理的检查可以让我知道实体是否已附加到 `datacontext`。

if (null == PropertyChanging)
    return;

如果实体未附加到 `datacontext`,则无需执行任何操作。此外,此检查消除了循环引用导致堆栈溢出问题的可能性。如果实体已附加到 `datacontext`,则会移除 `PropertyChanging` 和 `PropertyChanged` 事件的处理程序。

PropertyChanging = null;
PropertyChanged = null;

现在事件处理程序已被移除,实体的更改将不再被跟踪。但是,我们还没有完成分离 `Product` 实体。我们必须分离其所有子实体、列表以及延迟加载的属性。所以,我们现在就来做。

为了实现所有子实体、列表和延迟加载属性的分离,我们必须创建一个名为 `LinqEntityBase` 的 `abstract` 基类,所有实体都将继承自该基类。

public abstract partial class LinqEntityBase
{
    public abstract void Detach();
}

由于我们将实现一些需要利用每个实体 `Detach()` 方法的重用性的基方法,因此在 `LinqEntityBase` 类中将需要一个 `abstract detach` 方法。我们已经看到了 Petshop `Product` 实体的 `Detach()` 方法。Petshop.dbml 中的其他每个实体都需要一个特定于该实体的分离方法。因此,`Product` 现在将继承 `LinqEntityBase`。

partial class Product : LinqEntityBase

所以,现在让我们看看我们需要做什么来分离子实体。我们将向 `LinqEntityBase` 类添加以下方法,该方法专门用于分离子实体。

protected static System.Data.Linq.EntityRef<TEntity> Detach<TEntity>
	(System.Data.Linq.EntityRef<TEntity> entity) 
    	where TEntity : LinqEntityBase
{
    if (!entity.HasLoadedOrAssignedValue || entity.Entity == null)
        return new System.Data.Linq.EntityRef<TEntity>();
        entity.Entity.Detach();
    return new System.Data.Linq.EntityRef<TEntity>(entity.Entity);
}

我们必须首先确定实体是否已加载。这里的技巧是不要触发任何实体加载。`HasLoadedOrAssignedValue` 方法告诉我们实体是否已加载,我们可以避免任何实体的延迟加载。一旦我们确定实体已加载,实体就会被分离并作为新 `EntityRef` 实例的目标返回。如果实体未加载,则该属性将设置为一个新的空 `EntityRef` 实例。

entity.Entity.Detach();

这一行调用了在这种情况下特定于 `Category` 实体的 `Detach` 实现。同样,每个实体都需要一个特定于该实体的 `Detach` 方法。我们已经在一个实体上实现了一个 `Detach()` 方法,其过程与我们为 `Product` 实体使用的过程类似。

partial class Category : LinqEntityBase
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;
               
        PropertyChanging = null;
        PropertyChanged = null;
        this._Products = Detach(this._Products, attach_Products, detach_Products);
    }
}

实体的所有子列表也必须分离。`Product` 实体上的 `ItemList` 属性是一个 `EntitySet`。`EntitySet` 中的每个 `ItemList` 都必须分离,并且需要以下方法来实现这一点。

protected static System.Data.Linq.EntitySet<TEntity> Detach<TEntity>
	(System.Data.Linq.EntitySet<TEntity> set, Action<TEntity> onAdd, 
	Action<TEntity> onRemove) 
    	where TEntity : LinqEntityBase
{
    if (set == null || !set.HasLoadedOrAssignedValues)
        return new System.Data.Linq.EntitySet<TEntity>(onAdd, onRemove);
            
    // copy list and detach all entities
    var list = set.ToList();
    list.ForEach(t => t.Detach());
        
    var newSet = new System.Data.Linq.EntitySet<TEntity>(onAdd, onRemove);
    newSet.Assign(list);
    return newSet;
}

正如我们之前提到的,`HasLoadedOrAssignedValue` 用于确定列表是否已加载,并避免延迟加载列表。`ItemList` 中的每个项都必须分离并复制到一个未附加到 `datacontext` 的新 `EntitySet` 中。

最后,任何延迟加载的属性都需要分离。通过更新 DBML,我已将 `Product` 实体的 `Descn` 属性配置为延迟加载。

任何延迟加载的属性也持有与 `datacontext` 的连接,必须进行分离,并使用我们将在基类中需要的第三个也是最后一个 `Detach` 方法。

protected static System.Data.Linq.Link<T> Detach<T>(System.Data.Linq.Link<T> value)
{
    if (!value.HasLoadedOrAssignedValue)
        return default(System.Data.Linq.Link<T>);
            
    return new System.Data.Linq.Link<T>(value.Value);
}

正如模式所示,检查对象是否已加载,如果未加载则返回默认实例,否则返回一个 `Link` 的新实例,该实例的值是对象的实例目标。

现在我们已经为分离子实体、子实体集和延迟加载属性添加了必要的基方法,我们可以通过为 `Category`、`ItemList` 和 `Desc` 属性添加 `Detach` 调用来完成 `Product Detach` 方法。以下是完整的 `Product Detach()` 方法。

partial class Product : LinqEntityBase
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;
            
        PropertyChanging = null;
        PropertyChanged = null;
            
        this._Category = Detach(this._Category);
        this._Items = Detach(this._Items, attach_Items, detach_Items);
        this._Descn = Detach(this._Descn);
    }
}

使用分离

利用分离和重新附加 Linq to SQL 实体的一种方法是使用存储库模式。我们已经设置了一个简单的 `OrderRepository` 来获取和保存订单。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Detach.Repositories
{
    public class OrderRepository
    {
        public static Order Get(int orderId)
        {
            Order order = null;
            using (var context = new PetshopDataContext())
            {
                order = context.Orders.FirstOrDefault(o => o.OrderId == orderId);
                order.Detach();
            }
            return order;
        }
        
        public static Order Save(Order order)
        {
            using (var context = new PetshopDataContext())
            {
                if (order.OrderId > 0)
                    context.Orders.Attach(order, true);
                else
                    context.Orders.InsertOnSubmit(order);
                    
                context.SubmitChanges();
                order.Detach();
            }
            return order;
        }
    }
}

如您所见,这些方法中的每一种都使用自己的 datacontext。无需将一个 `datacontext` 作为参数传递或将其保存在模块级变量中。实体完全是独立的,这意味着您可以自由地在任何地方使用实体,而无需担心 `datacontext`。分离能力使得存储库模式在 LINQ to SQL 中成为可能。下面的代码与存储库交互,不涉及 `datacontext` 或维护数据库连接。

Order order = new Order();
order.BillAddr1 = "0001 Cemetery Lane";
order.BillCity = "Westfield";
order.BillState = "NJ";
order.BillZip = "07090";
order.BillCountry = "US";
order.Courier = "DHL";
order.OrderDate = System.DateTime.Now;
order.TotalPrice = 0;
order.AuthorizationNumber = 1;
order.BillToFirstName = "Gomez";
order.BillToLastName = "Adams";
order.Locale = "blah";
order.ShipToFirstName = "Gomez";
order.ShipToLastName = "Adams";
order.ShipAddr1 = "0001 Cemetery Lane";
order.ShipCity = "Westfield";
order.ShipState = "NJ";
order.ShipZip = "07090";
order.ShipCountry = "NJ";
order.UserId = "gadams";
    
order = OrderRepository.Save(order);
    
order.UserId = "gadams2";
order = OrderRepository.Save(order);
    
order = OrderRepository.Get(2);
order.TotalPrice = 150;
order = OrderRepository.Save(order);

结论

如您所见,分离实体与 `datacontext` 之间存在一些工作。此外,随着对象图变得越来越复杂,确保实体完全分离可能很棘手。 PLINQO 分离在从 `datacontext` 分离时采取所有预防措施,并确保实体可以在独立模式下使用。能够将 LINQ to SQL 实体与 datacontext 分离使用,为封装和重用提供了许多机会。

PLINQO 为每个实体生成类似于我们为 `Product` 实体构建的分离方法。 PLINQO 找出所有需要分离的必要子对象、列表和延迟加载的属性,并确保在将实体与 `datacontext` 分离时执行这些实体的正确分离方法。这意味着您不必担心分离实体需要什么。 PLINQO 已经处理了。

那么,在使用 PLINQO 时如何分离实体?调用 `Detach` 方法。完成!

历史

  • 2009年7月12日:初始帖子
© . All rights reserved.