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

类型化数据集 LINQ 实体

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.71/5 (5投票s)

2008年4月21日

CPOL

5分钟阅读

viewsIcon

49253

downloadIcon

423

将类型化数据集转换为 LINQ 实体,以及将 LINQ 实体转换为类型化数据集。

引言

在我之前的一篇 博文 中,我讨论了 LINQ 实体如何不适合无法持续访问数据源的应用程序。我得出的结论是,如果有一种方法可以将 LINQ 实体和类型化数据集连接起来,那么 Web 应用程序和 N 层应用程序的领域就可以由相同的业务对象模型和基于 LINQ 的数据访问层来支持。

假设 - 先决条件

实体和数据表命名

在我继续之前,必须牢记一个基本假设。业务对象模型和类型化数据集必须由 Visual Studio 中的相应设计器创建,方法是将表拖入每个设计器。主要原因是,我开发的转换器假定 LINQ 中的相应实体和 DataSet 中的表具有相同的名称。

关系和外键约束

实体之间的每个关系名称必须与 DataSet 中表之间的关系名称相同。通过使用设计器,以上内容会自动(巧合的是)保留。

尚未测试循环关系和所有组合,因此我不知道我的代码是否支持它们。

从 LINQ 构建数据库

如果您希望从 LINQ 设计器构建数据库架构,请这样做,但在创建类型化数据集之前,必须创建数据库。要做到这一点,只需调用

LinqTestDataContext ltdc = new LinqTestDataContext(connectionString); 
if (!ltdc.DatabaseExists()) 
{ 
  ltdc.CreateDatabase(); 
}

其中 LinqTestDataContext 是设计器创建的 DataContext

列先决条件

每个实体必须有一个 version 属性。这是因为

Attach(entity,true) 

仅当存在此类属性时才有效。

用于测试的数据库架构

LINQ 架构命名为 LinqTest,其 DataSet 表示形式为 DsLinqTest

如下图所示,有一个 RootElement,其唯一键为 ID,版本属性为 TimeStamp,以及两个字符串属性。

RootElement 有一个 SubRootElement 实体的子关系,它们也有一个唯一的键 ID,版本属性 TimeStamp,一个字符串属性和一个指向其所属 RootElementRootID 外键。

即使上面图像中未显示,关系名称也是相同的。

每个业务对象都位于单独的程序集中。

DataSetEntityConvertion

这是执行 LINQ 业务对象与类型化数据集之间转换的程序集名称,假设满足上述先决条件。该程序集大量使用反射和泛型,因此对它们的理解至少应比较好。请记住,由于 DataSet 是类型化的,DataSet 中的每个类型都经过特殊命名,以便可以用来发现它所关联的实体。

ToDataRow

这是使用实体填充 DataSet 中相应表的*.* 的部分。入口点是 Entity2DataSet 类,其中 TEntity 是实体类型,TDataSetDataSet 类型。在我们的例子中,分别是 RootElementDsLinqTest

基本上,Entity2DataSet 类会发现与实体对应的表,然后调用 Entity2DataRow 类,后者还会接受发现的 DataTable 类型。

有一些辅助函数,通过反射,从实体填充行,并查找实体的子关系(如果存在)。如果存在,将再次调用 Entity2DataSet 类,但这次 TEntity 应该是 SubRootElement(在我们的例子中)。

转换的这一侧相当容易。

ToEntity

此案例涉及将整个 DataSet 转换为其实体。入口类是 DataSet2Entity,其中 TDataContext 是我们的 DataContext 类型,TDataSet 是源 Dataset 的类型。在我们的例子中,分别是 LinqTestBigDataContextDsLinqTest

DataSet2Entity 首先查找没有父级关系(parent relations)的表。对于这些表中的每一个,都使用 DataTable2Entity,其中 TDataTableTDataRow 分别是表及其行的类型。

DataTable2Entity 会发现必须为每一行创建的实体类型,并通过使用 DataRow2Entity 来实现,后者会知道它是否是子行。这一点至关重要,因为如果是子行,它必须添加到其父实体的相关 EntitySet 中,而不是添加到数据上下文的实体 Table 中。

这里的技巧是知道原始行是添加、修改、删除还是未更改,这通过 RowState 可以轻松实现。难点在于如何处理它。

Added

此案例很简单。只需构造实体并将其添加到表或实体集中,然后调用 InsertOnSubmit

修改或未修改

问题从这里开始。首先,我们必须获取我们将应用值的实体。根据行是子行还是非子行,必须构造一个谓词函数或表达式。这一部分是最困难的。

如果行未修改,则不会应用任何值。

Deleted

与修改类似,必须从数据上下文的实体表中检索实体,以便调用 DeleteOnSubmit

跟踪更改

当行被插入或修改时,各种列值需要由数据库的自动生成值更新。因此,对于每个实体,都会捕获 PropertyChanged。在那里,借助字典,将新值应用于原始行。这发生在数据上下文的 SubmitChanges 被使用之后。

DataRow2Entity 的其余部分会为每个数据关系查找行的子行,并调用其另一个泛型版本。

创建谓词函数和表达式

这是最难的部分,而且我仍然有一些地方不理解。

当尝试从数据上下文的表实体中获取实体时,一个简单的委托函数就足够了。经过多次尝试,我设法使创建完全动态,基于实体的(主)键。

这是通过这两个函数完成的

private System.Func<TEntity, bool> CreatePredicateFunction(TDataRow row) 
{ 
    return p => (IsEqual(p, row)); 
} 
private bool IsEqual(TEntity entity, TDataRow row) 
{ 
    for (int i = 0; i < Cache.EntityPrimaryKeys<TEntity>.Names.Count; i++) 
    {
        object columnValue = null; 
        if (row.RowState == DataRowState.Deleted) 
        { 
            columnValue = row[Cache.EntityPrimaryKeys<TEntity>.Names[i], 
                          DataRowVersion.Original]; 
        } 
        else 
        { 
            columnValue = row[Cache.EntityPrimaryKeys<TEntity>.Names[i]]; 
        } 
        if ((bool)Cache.EntityPrimaryKeys<TEntity>.EqualMethods[i].Invoke(
            this.entityType.GetProperty(Cache.EntityPrimaryKeys<TEntity>.Names[i]).GetValue(
            entity, null), new object[] { columnValue }) == false) 
        { 
            return false; 
        } 
    }
    return true; 
}

我很高兴能够将上述内容转换为 Expression<System.Func<TEntity, bool>>,但我发现在运行时会引发一个异常,告诉我 IsEqual 无法转换或其他信息。

我假设 Expression 比委托复杂得多。因此,为了使此方法有效,必须在 DataSet 的每个 DataRow 中提供一个 CreatePredicateExpression。我这样做了

public static class DsLinqTestPredicators 
{ 
    public static Expression<System.Func<RootElement, 
           bool>> CreatePredicateExpression(DsLinqTest.RootElementRow row)
    {
        int idValue = row.RowState == System.Data.DataRowState.Deleted ? (int)row["ID",
                      System.Data.DataRowVersion.Original] : row.ID;
        return (Expression<System.Func<RootElement, bool>>)(p => p.ID.Equals(idValue)); 
    }
    public static Expression<System.Func<SubRootElement, 
           bool>> CreatePredicateExpression(DsLinqTest.SubRootElementRow row) 
    {
        int idValue = row.RowState == System.Data.DataRowState.Deleted ? (int)row["ID",
                      System.Data.DataRowVersion.Original] : row.ID; 
        return (Expression<System.Func<SubRootElement, bool>>)(p => p.ID.Equals(idValue)); 
    }
}

关于转换器的最后的话

扩展方法被大量使用,以帮助使转换尽可能在编程上透明。

Using the Code

Extenders

public static classDsLinqTestExtenders{
    public static voidInsert(thisDsLinqTest extented, objectentity)
    {
        ((DataSet)extented).Insert(entity);
    }
    public static voidInsert(thisDsLinqTest extented, object[] entities)
    {
        ((DataSet)extented).Insert(entities);
    }
    public static voidToEntities(thisDsLinqTest extented, DataContext dataContext)
    {
        ((DataSet)extented).ToEntities(dataContext);
    }
}

Entity2Dataset

public DsLinqTest GetDsFromID(int id) 
{
    LinqTestDataContext ltdc = new LinqTestDataContext(connectionString);
    RootElement re = ltdc.RootElements.Single(p => p.ID.Equals(id)); 
    DsLinqTest ds = new DsLinqTest(); 
    ds.Insert(re); 
    ds.AcceptChanges(); 
    return ds;
}

DataSet2Entity

public void SaveGeneralDs(DsLinqTest dsLinqTest) 
{ 
    LinqTestDataContext ltdc = new LinqTestDataContext(connectionString); 
    dsLinqTest.ToEntities(ltdc);
    ltdc.SubmitChanges(); 
}
© . All rights reserved.