LINQ 快速开发设计最佳实践

LINQ 快速开发设计最佳实践(一)

一.摘要

会用LINQ的人很多, 但是用过LINQ做企业应用的人却不多.所以能搜索到的资料很有限.不才的我恰巧在新的项目中使用了LINQ,结果遇到了重重困难.在此将我经历了 LINQ的磨难后,总结的经验和方法分享给大家.

第一篇文章将我总结出来的LINQ开发的流程, 后面的文章将对其中的很多细节和难点一一讲解.一家之言仅供参考,如有不对的地方请大家指正.

二.前言

先说几句 题外话.有关项目的实际效果, 大家可以去新上线没多久的项目: dianping.elong.com 上参观一下. 我的痛苦经历也就是在实现此频道的过程中.其实从纯技术上说, 这套系统有很多闪光点:

  • 领域通用的Tag标签系统和点 评系统,可以支持任意产品线和产品的点评和标签.
  • 支持任意语言扩展.所有的内容都是用语言表存储.
  • 动 态投票模型,主要是使用了数据库存储元数据的方式,实现了可以随时添加投票项.
  • 使用LINQ实现ORM
  • 使 用SOA思想将UI与业务逻辑通过服务切割开来.
  • 使用WCF作为SOA的实现
  • 支持动态更换推荐 指数计算的DLL,无需重启服务器
  • 在通用的Tag和点评系统上组装成艺龙酒店点评系统

看 起来忽悠人不?开发的时候我真的是希望将此项目作为一个典范或者说开发企业应用的模板, 来做一个可以让我骄傲的产品. 我是喜欢追求代码艺术的人, 然后很悲剧的是往往质量要为进度让路. 而且我不能让小组中的每个人都按照我想的方向去实现.最后终于完成了这款不完美的作品.亮点很多, 但是在基本的实现中却有很多的业务没有切割清楚, 各种设计准则和设计模式更是被一些人抛到脑后.来一张此项目的截图,限于公司保密原因不能提供给大家源代码:

image

这里面有一些很让我看着不舒服的项目是被别人 加进来的.尤其是Service层.本来在我创建项目的第一个版本的时候Host就是用来存放服务宿主的,无论是WebService还是WCF的IIS 宿主,都可以放在HOST中.但是随后被领导们添加进来了那几个名字超长的项目.

闲话不说,抱怨无罪也无用.下面来看看如果设计实现 一套系统,我认为的"敏捷"开发应该如何去做:

三.在PowerDesign中设计数据模型

在此我们跳过理解分 析需求,指定业务模型的过程.即使已有了业务模型也依然要有数据模型.所以下面假定我们已经开始了数据库设计.

我习惯使用PD设计数据 库模型.并在在这个阶段要将索引,外键,字段默认值,注释等统统设计进去.这里多花费的时间是完全可以值得的.

下面是两个表的截图:

image

四.在数据库中创建表,外键和索引

在PowerDesign中设计的数据模型可以选择对应的数据库类型,比如SqlServer2005. 并且在Preview选项卡中可以查看生成的SQL.这时上一步我们设计好的所有对象都派上了用场,甚至包括数据库中的字段注释.

比如 下图:

image

五.使用 Sqlmetal.EXE 生成Model层代码

入门的LINQ教程多举例使用可视化的"对象关系设计器(O/R设计器)"创建LINQ模型,但是实践证明此设 计器属于鸡肋.原因是默认生成的代码100%不符合需求, 比如属性没有默认值,没有任何注释,没有Detach方法等.

可视化设计使 用dbml文件, 保存后自动生成 [dbml名].design.cs后缀的代码文件, 生成的类都是部分类.同时允许我们手工添加 [dbml名].cs文件,这个不会在dbml修改时被重写,因为自动生成的类都是部分类,虽然可以在此文件中扩展类成员和方法.但是如 果添加属性默认值和注释的话是必须要修改.design.cs文件.

问题是dbml的修改是单向 的.在dbml中可视化修改会同时更新关联的.design.cs文件,但是如果手动修改了.cs文件代码,dbml不会更新. 而且在 dbml中的任何修改都会导致关联的.design.cs文件重写 , 如果我们手工给其加上注释,则只要在对象关系设计器中做了任何修改,就会被冲掉代码.手工添加注释可不是一件轻松的工作.

所以最佳建议 是: 第一次设计时可以使用"对象关系设计器(O/R设计器)"进行可视化设计.以后对LINQ模型的修改都是手动修改[dbml名].design.cs和 [dbml名].cs代码文件完成.如果新添加了表,可以使用SqlMetal.exe工具生成新的代码文件,然后在将文件中的新增的表的模型Copy到 项目中.

下面是一个最简单的SqlMetal命令工具,其中conn参数是数据库连接串,code是要生成的新的代码文件路径.

Sqlmetal   /conn:"uid=****;pwd=****;initial catalog=DataBaseName;data source=192.168.0.1;Connect Timeout=900;Connection Reset=FALSE"
   /code:"d:/DB.cs"
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

在生成的DB.cs中有整个数据库所有表的Model模型, 以及完整的DataContext.

自动生成的模型代码.有很多需要修改的点,这些细节我将在下一篇文章中详细讲解.

六.打造基础数据访问层与业务逻辑层对象

虽然再外层封装了服务,但是我认为内部的具体实现代码仍然少不了这两个层次.LINQ仅仅是帮我们完成了ORM的任务,让我们直接操作对象,而不用 关心如何操作数据库.

但是如何使用LINQ来创建这两个层次对象呢? 下面是我的总结:

1.将添加和更新操作放在每个表的实体模型中

因为LINQ会将数据库中的"键",转换成对象模型中的属性.比如表A,表B 和 A-B关系表(多对多),转换成的模型后,A对象有一个属性是EntitySet<A-B>, B同理.A-B对象则有两个属性分别是EntitySet<A>和EntitySet<B>.至于1对多的表的模型, 会在"多"的一方模型上创建EntityRef属性, 这个属性不是一个集合, 而是对"1"这一方对象的反向引用.

使用LINQ最容易导致的误区是对象职责拆分不清楚.因为DataContext包含了所有的数据库对象, 很可能我们在使用B对象的一个方法插入了一个A对象. 对于复杂的业务逻辑来说职责的拆分是必不可少的, 任何的设计模式以及原则其实本质上都是为实现SRP单一职责原则而努力的.比如我们希望在发表点评数据的时候做一些运算和统计工作.如果没有一个统一的点 评发表点, 源程序中到处散布着可以添加点评对象的地方, 那么完成这个工作就要花费大量的成本.

所以我构造对象的原则就是:

每个表都有一个Model类,一个数据访问层对象,一个业务逻辑层对象.每个表的添加和更新操作只能使用本表的相关对象完成. 即:A只负责A表的添加个更新操作,不能更新和操作B表.如果需要只能通过使用B对象的方式.

2.更新和插入操作一定要自顶向下.

已经将职责拆分好以后,我们还需要针对EntitySet和EntityRef对象的插入和更新指定顺序. 否则会产生循环更新.

原则:更新和插入操作要从 "一对多"中"一"的一方开始,在叶子节点或者多对多表中终结.

这两点也许太过抽象.下面提供一个数据访问层对象的模板类.对于其他的表只需要批量替换其中表名即可:

/****************************************************************************************************

* *
* * File Name : TagRefEntityInstanceDA.cs
* * Creator : ziqiu.zhang
* * Create Time : 2008-11-10
* * Functional Description : TagRefEntityInstance数据访问类
* * Remark :
* *
* * Copyright (c) eLong Corporation. All rights reserved.
* ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using Com.Elong.Model.Tag;
using Com.Elong.Model.Common;
using Com.Elong.Common.Comment;

namespace Com.Elong.DataAccess.Tag
{
/// <summary>
/// TagRefEntityInstance数据访问类
/// </summary>
public class TagRefEntityInstanceDA : IDisposable
{

#region ==================== Private Field ====================
private bool isDisposed = false ;
#endregion


#region ==================== Property =========================
private string m_ConnectionString;
/// <summary>
/// 数据库连接字符串
/// </summary>
public string ConnectionString
{
get { return m_ConnectionString; }
set { m_ConnectionString = value ; }
}
#endregion


#region ==================== Constructed Method ===============
/// <summary>
/// 禁止使用
/// </summary>
private TagRefEntityInstanceDA()
{ }

/// <summary>
/// 构造函数
/// </summary>
/// <param name="connectionString">数据库连接串</param>
public TagRefEntityInstanceDA(string connectionString)
{
m_ConnectionString = connectionString;
}
#endregion


#region ==================== Public Method ====================

#region ===== Select =====
/// <summary>
/// 得到Tag和实体的关系
/// </summary>
/// <param name="entityId">实体类型Id</param>
/// <param name="entityInstanceId">实体实例Id</param>
/// <param name="tagItemId">TagItemId</param>
/// <returns>TagRefEntityInstance列表</returns>
public List<TagRefEntityInstance> GetItemByEntity(int entityId, string entityInstanceId, int tagItemId)
{
List<TagRefEntityInstance> result = new List<TagRefEntityInstance>();
TagDataContext dc = new TagDataContext(ConnectionString);
dc.DeferredLoadingEnabled = true ;
var query =
from tagRefEntityInstance in dc.TagRefEntityInstance
where tagRefEntityInstance.EntityId == entityId
&& tagRefEntityInstance.EntityInstanceId == entityInstanceId
&& tagRefEntityInstance.TagItemId == tagItemId
select tagRefEntityInstance;
result = query.ToList();
return result;
}

/// <summary>
/// 根据主键得到对象
/// </summary>
/// <param name="pkid">主键</param>
/// <returns>对象</returns>
public TagRefEntityInstance GetItemByPkid(int pkid)
{
TagRefEntityInstance result = new TagRefEntityInstance();
TagDataContext dc = new TagDataContext(ConnectionString);
dc.DeferredLoadingEnabled = true ;
var query =
from tagRefEntityInstance in dc.TagRefEntityInstance
where tagRefEntityInstance.pkid == pkid
select tagRefEntityInstance;
result = query.SingleOrDefault();
return result;
}

/// <summary>
/// 根据主键列表得到对象集合
/// </summary>
/// <param name="pkid">主键列表</param>
/// <returns>对象集合</returns>
public List<TagRefEntityInstance> GetListByPkid(List<int > pkidList)
{
if (pkidList.Count > 2100)
{
throw new ParameterOverflowException("传入的pkid列表个数大于2100,超过了Sql允许的最大参数个数。" );
}
List<TagRefEntityInstance> result = new List<TagRefEntityInstance>();
TagDataContext dc = new TagDataContext(ConnectionString);
dc.DeferredLoadingEnabled = true ;
var query =
from tagRefEntityInstance in dc.TagRefEntityInstance
where pkidList.Contains( tagRefEntityInstance.pkid )
select tagRefEntityInstance;
result = query.ToList();
return result;
}

/// <summary>
/// 根据TagItemId,EntityId,获取EntityInstanceId集合:
/// </summary>
/// <param name="tagItemId">TagItemId</param>
/// <param name="entityId">实体分类Id</param>
/// <returns>EntityInstanceId列表</returns>
public List<string > GetEntityInstanceId(int tagItemId, int entityId)
{
List<string > result = new List<string >();

TagDataContext tagDataContext = new TagDataContext(m_ConnectionString);
tagDataContext.DeferredLoadingEnabled = false ;

//主查询语句
var query = from tagRegEntityInstance in tagDataContext.TagRefEntityInstance
where tagRegEntityInstance.TagItemId == tagItemId
&& tagRegEntityInstance.EntityId == entityId
&& tagRegEntityInstance.IsDeleted == 0
select tagRegEntityInstance.EntityInstanceId;

//执行查询,返回结果。
result = query.Distinct().ToList();
return result;
}
#endregion

#region ===== Insert =====
/// <summary>
/// 插入对象
/// </summary>
/// <param name="item">TagRefEntityInstance对象</param>
/// <returns>变更集</returns>
public ChangeSet Insert(TagRefEntityInstance item)
{
TagDataContext dc = new TagDataContext(ConnectionString);
dc.TagRefEntityInstance.InsertOnSubmit(item);
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}

/// <summary>
/// 插入集合
/// </summary>
/// <param name="item">TagRefEntityInstance集合</param>
/// <returns>变更集</returns>
public ChangeSet Insert(List<TagRefEntityInstance> itemList)
{
TagDataContext dc = new TagDataContext(ConnectionString);
dc.TagRefEntityInstance.InsertAllOnSubmit(itemList);
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}
#endregion

#region ===== InsertUpdate =====
/// <summary>
/// InsertUpdate对象
/// </summary>
/// <param name="item">TagRefEntityInstance对象</param>
/// <returns>变更集</returns>
public ChangeSet InsertUpdate(TagRefEntityInstance item)
{
TagDataContext dc = new TagDataContext(m_ConnectionString);

//TagRefEntityInstance数据完整性: 只存在一条 TagItemId, EntityId, EntityInstanceId 相同数据
var query =
from tagRefEntityInstance in dc.TagRefEntityInstance
where tagRefEntityInstance.TagItemId == item.TagItemId
&& tagRefEntityInstance.EntityId == item.EntityId
&& tagRefEntityInstance.EntityInstanceId == item.EntityInstanceId
select tagRefEntityInstance;

List<TagRefEntityInstance> itemList = query.ToList();
if (itemList != null && itemList.Count > 0)
{
item.pkid = itemList[0].pkid;
if (itemList.Count > 1)
{
//发现了多条重复的数据.保留第一条,删除其他的条目
itemList.RemoveAt(0);
this .PhysicsDelete(itemList);
WebLog.CommentLog.ErrorLogger.Error(string .Format(@"在TagRefEntityInstance表中发现错误数据.
TagItemId:{0},EntityId:{1},EntityInstanceId:{2} 保留Pkid:{3}, 删除其他数据."
,
item.TagItemId.ToString(),
item.EntityId.ToString(),
item.EntityInstanceId,
item.pkid.ToString()));

}
//更新对象
ChangeSet result = this .Update(item);
return result;
}
else
{
//插入对象
ChangeSet result = this .Insert(item);
return result;
}
}

/// <summary>
/// InsertUpdate对象集合
/// </summary>
/// <param name="itemList">TagRefEntityInstance集合</param>
/// <returns>变更集数组:索引0为插入操作变更集,索引1为更新操作变更集</returns>
public ChangeSet[] InsertUpdate(List<TagRefEntityInstance> itemList)
{
ChangeSet[] result = new ChangeSet[2];
List<int > entityIdList = new List<int >();
List<string > entityInstanceIdList = new List<string >();
List<int > tagItemIdList = new List<int >();

//将itemList中所有出现过的entityId,entityInstanceId和tagItemId保存在列表中
foreach (TagRefEntityInstance tempItem in itemList)
{
if (!entityIdList.Contains(tempItem.EntityId))
{
entityIdList.Add(tempItem.EntityId);
}
if (!entityInstanceIdList.Contains(tempItem.EntityInstanceId))
{
entityInstanceIdList.Add(tempItem.EntityInstanceId);
}
if (!tagItemIdList.Contains(tempItem.TagItemId))
{
tagItemIdList.Add(tempItem.TagItemId);
}
}

//选取所有可能存在于数据库中的集合.
TagDataContext dc = new TagDataContext(m_ConnectionString);
var query =
from tagRefEntityInstance in dc.TagRefEntityInstance
where entityIdList.Contains( tagRefEntityInstance.EntityId )
&& entityInstanceIdList.Contains(tagRefEntityInstance.EntityInstanceId)
&& tagItemIdList.Contains(tagRefEntityInstance.TagItemId)
select tagRefEntityInstance;

//从数据库集合中筛选出需要更新和插入的列表
List<TagRefEntityInstance> allList = query.ToList();
List<TagRefEntityInstance> insertList = new List<TagRefEntityInstance>();
List<TagRefEntityInstance> updateList = new List<TagRefEntityInstance>();
foreach (TagRefEntityInstance tempItem in itemList)
{
List<TagRefEntityInstance> tempItemList = allList.Where(c => c.EntityId == tempItem.EntityId && c.EntityInstanceId == tempItem.EntityInstanceId && c.TagItemId == tempItem.TagItemId).ToList();
if (tempItemList != null && tempItemList.Count > 0)
{
tempItem.pkid = tempItemList[0].pkid;
if (tempItemList.Count > 1)
{
tempItemList.RemoveAt(0);
this .PhysicsDelete(tempItemList);
WebLog.CommentLog.ErrorLogger.Error(string .Format(@"在TagRefEntityInstance表中发现错误数据.
TagItemId:{0},EntityId:{1},EntityInstanceId:{2} 保留Pkid:{3}, 删除其他数据."
,
tempItem.TagItemId.ToString(),
tempItem.EntityId.ToString(),
tempItem.EntityInstanceId,
tempItem.pkid.ToString()));
}
updateList.Add(tempItem);
}
else
{
insertList.Add(tempItem);
}
}

//执行操作
result[0] = this .Insert(insertList);
result[1] = this .Update(updateList);
return result;

}
#endregion

#region ===== Update =====
/// <summary>
/// 更新对象
/// </summary>
/// <param name="item">TagRefEntityInstance对象</param>
/// <returns>变更集</returns>
public ChangeSet Update(TagRefEntityInstance item)
{
TagDataContext dc = new TagDataContext(ConnectionString);
item.Detach();
dc.TagRefEntityInstance.Attach(item, true );
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}

/// <summary>
/// 更新对象集合
/// </summary>
/// <param name="itemList">TagRefEntityInstance集合</param>
/// <returns>变更集</returns>
public ChangeSet Update(List<TagRefEntityInstance> itemList)
{
TagDataContext dc = new TagDataContext(ConnectionString);
foreach (TagRefEntityInstance tempItem in itemList)
{
tempItem.Detach();
}
dc.TagRefEntityInstance.AttachAll(itemList, true );
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}
#endregion

#region ===== Delete =====
/// <summary>
/// 物理删除对象
/// </summary>
/// <param name="item">TagRefEntityInstance对象</param>
/// <returns>变更集</returns>
public ChangeSet PhysicsDelete(TagRefEntityInstance item)
{
TagDataContext dc = new TagDataContext(m_ConnectionString);
item.Detach();
dc.TagRefEntityInstance.Attach(item);
dc.TagRefEntityInstance.DeleteOnSubmit(item);
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}

/// <summary>
/// 物理删除对象集合
/// </summary>
/// <param name="itemList">TagRefEntityInstance集合</param>
/// <returns>变更集</returns>
public ChangeSet PhysicsDelete(List<TagRefEntityInstance> itemList)
{
TagDataContext dc = new TagDataContext(m_ConnectionString);
foreach (TagRefEntityInstance tempItem in itemList)
{
tempItem.Detach();
}
dc.TagRefEntityInstance.AttachAll(itemList);
dc.TagRefEntityInstance.DeleteAllOnSubmit(itemList);
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}

#endregion

#endregion


#region IDisposable 成员

public void Dispose()
{
Dispose(true );
GC.SuppressFinalize(this );
}

public virtual void Dispose(bool disposing)
{
if (!this .isDisposed)
{
if (disposing)
{
//释放非托管资源
}

//释放托管资源
m_ConnectionString = null ;
isDisposed = true ;
}
}

~TagRefEntityInstanceDA()
{
Dispose(false );
}

#endregion
}
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

七.实现具体业务逻辑

现在我们的业务逻辑层对象已经使用LINQ实现了基础的CURD操作, 接下来就需要在业务逻辑类中完成和功能的开发了. 在此我们可以根据项目考虑是否可以使用服务的方式组织调用业务逻辑对象, 对于我的项目我就是抽象出来了服务层. 有对内的服务也有对外暴露的服务.

八.开发UI层(Web层)

通过服务将业务逻辑与页面展示彻底分开, 不同于以前使用业务逻辑层, 使用WCF构建的服务层我们可以方便的实现分布式调用.可以将提供服务的宿主和Web项目放在不同的服务器里.

九.常见问题与总结

在PD中添加的字段注释,并不能反映在自动生成的代码中.我认为这是LINQ的Bug, 因为在使用LINQ之前, 我使用自己的"伪ORM框架",说"伪"是因为我还是通过写SQL语句实现的在数据访问层将数据转化成对象, 从而在业务逻辑层操作的都是对象. 但是数据访问层,业务逻辑层,和Model层我使用自己开发的CodeSmith模板, 全部自动生成, 并且能够将PD中生成的注释全部带到源程序中. 不需要修改一行代码就能实现某一个对象的基础CURD操作以及"按照对象任意属性组合条件查询". 将数据库中的注释带到代码中,这种连我都能做到的事情为何微软做不到呢? 在PD生成的SQL中是带有字段注释的.

 

 

LINQ 快速开发设计最佳实践(二) 构建Model模型

一.摘要

第一篇文章我简要介绍了项目的设计框架和LINQ实现思想. 本篇文章将是最实际和具有技巧性的地方, 就是如何创建LINQ TO SQL 的模型对象.

二.前言

1.LINQ与LINQ TO SQL

姜敏同学提醒我要注意LINQ TO SQL和LINQ的不同.的确这两者就类似C#和.NET. 老赵曾写文章特别强调过两者的不同.这里再简单提一下.

LINQ是Language-Integrated Query的缩写, 翻译后是"集成语言查询", 我将LINQ看做是一种查询框架, 拥有自己的特定语法. 只要对象实现了LINQ框架所需要的接口, 就可以使用LINQ语法进行查询.比如:

var query =
from tagCategory in dc.TagCategory
where tagCategory.pkid == tagCategoryId
select tagCategory
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

LINQ TO SQL 是一个LINQ Provider, 能够实现轻量级的ORM, ORM的作用是将数据库中的表和Model模型做映射, 比如Java中的Hiberate(.NET中也有NHiberate). LINQ TO SQL让我们可以使用LINQ的语言操作Model对象, 完成对数据库的CRUD操作.

为何我们可以使用LINQ语言查询一个Object? 因为有LINQ TO Object.

为何我们可以使用LINQ语言查询对象操作数据库? 因为我们有LINQ TO SQL.

2.LINQ TO SQL 的Model对象

传统的Model对象是一种信息模型, Model类一般不包含任何的方法, 只包含属性.要描述一个现实世界Student, 首先需要一个Student的Model类, 比如类名就叫做Student, 用来保存每一个Student特有的信息, 比如名字.  然后将其具有的行为在业务逻辑层描述, 比如在业务逻辑层创建一个StudentBL类, 为其添加一个Walk()方法.  现在比较流行使用业务模型设计系统, 业务模型会将信息和行为抽象成一个Student角色.但是我还很少使用.

使用LINQ TO SQL, 我们能将传统意义上的Model模型的某一个属性, 比如Student类的Name属性与数据库Student表的Name字段建立映射关系,  这就是ORM: 对象Object 关系Relational 映射Mapping 的含义. 有了ORM, 当我们修改Student的Name时,将自动修改数据库中的数据. 我们再也看不到SQL了, 系统中统统都是操作对象.

LINQ TO SQL 提供了可视化设计工具"O/R 设计器"和命令行工具SqlMetal.exe 为数据库中的表自动生成Model类代码. 但是默认生成的代码只能做做简单的Demo例子, 在实际应用中有很多问题和需要修改的地方.下面将我创建一个Model对象的的"最佳实践"拿出来和大家分享.

三. 使用工具建立Model类

在架构上我将Model类横向切割为一层, 纵向将每个业务线切割为一个项目.比如Tag系统的模型层为: Com.Elong.Model.Tag

每个项目首先都要为每个表创建Model类, 这一步我会使用O/R设计器的可视化设计.下面简单介绍一下步骤.

1.使用 O/R 设计器

在项目中添加新项是, 我们选择"LINQ TO SQL类",如下图:

image

我们会在项目中添加TagDataContext.dbml文件, 展开此文件的"+"会发现其包含两个子文件: TagDataContext.dbml.layout以及TagDataContext.design.cs

其中 TagDataContext.design.cs 中保存我们的DataContext类和所有表的Model类代码. TagDataContext.dbml.layout保存的是可视化设计的一些信息.

双击 TagDataContext.dbml 文件会进入可视化设计阶段. 使用O/R设计器十分简单, 在此不做介绍.想学习的可以参考MSDN或者一些前辈的系列教程,无非就是拖拖拽拽的工作.下面是一张O/R设计器截图:

image

提供下面几个技巧和经验:

1. 如果想自动创建数据库中两个表的关系, 需要将这两个表同时拖出.

2. 单击设计器空白处,查看属性面板, 有很多属性可以在这里修改:

image

最有用的上下文命名空间 和 实体命名空间. 上下文命名空间是DataContext类的命名空间, 我通常将其放置在DataAccess层中.实体命名空间是所有Model类所在的命名空间, 我将其放置在Model层中.

3. 任何在可视化设计中的修改, 都会造成 .design.cs 文件重写, 所以现阶段不要手工对此文件做修改. 因为一旦作了修改将不能再使用O/R 设计器.

4. O/R 设计器允许我们创建一个不会重写的类来扩成自动生成的代码,创建方法是在O/R设计器的空白处点右键, 在弹出菜单中选择"查看代码",如图:

image

会在.dbml文件中添加一个.cs文件:

image

请一定要创建这个文件,因为以后我会将Model类的方法创建在这个文件中.

四.使用SqlMetal.exe工具

上一章中已经提到过SqlMetal.exe,它是一个命令行工具, 需要从Visual Studio的命令行中使用.也会生成DataContext和Model类的代码.比如:

Sqlmetal   /conn:"uid=****;pwd=****;initial catalog=DataBaseName;data source=192.168.0.1;Connect Timeout=900;Connection Reset=FALSE"
   /code:"d:/DB.cs"
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

DB.CS中会有包含了是数据库所有表的DataContext以及每一张表的Model类.

一旦我们对可视化设计生成的.Design.cs文件作了修改, 比如添加了注释, 那么请不要再使用 O/R 设计器进行可视化的更改.正确的做法是手动的修改.Design.cs中的代码, 对于新加表等工作量比较大的修改, 可以先使用SqlMetal.exe生成表对应的Model类的初始代码, 将其Copy到.Design.cs中,然后再自行修改.

五.修改 TagDataContext.Design.cs 文件

在Design.cs中, 我们要做下面几项修改:

1.修改Model类名

修改Model类名称,以及Model类Table特性的值,去掉其中的"用户."前缀.添加注释.下面是一个例子.

修改前:

   [Table(Name = "dbo.T_Activity"
)]
public partial class T_Activity

修改后:

    /// <summary>

/// 活动表.保存各种活动信息
/// </summary>
[Table(Name = "T_Activity" )]
public partial class Activity

 

2. 添加所有属性的默认值和注释.

以ActivityName属性为例.

添加默认值:

private
 string
 _ActivityName = string
.Empty;
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

添加注释:

        /// <summary>

/// 活动名称
/// </summary>
[Column(Storage = "_ActivityName" , DbType = "NVarChar(200) NOT NULL" , CanBeNull = false )]
public string ActivityName
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

3.在主键列的Column特性上添加IsVersion属性,值为true

比如:

        /// <summary>

/// 自增主键
/// </summary>
[Column(Storage = "_pkid" , AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY" , IsPrimaryKey = true , IsDbGenerated = true , IsVersion = true )]
public int pkid
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

这一条相当重要.否则使用LINQ会遇到很多问题.当然也可以建立一个额外的TimeStamp列来用做IsVersion列,但是比较麻烦.

对于主键来说, AutoSync属性应该设置为 OnInsert, 实现的功能就是当我们将想要Insert一个对象时, 在插入前并没有对象的主键ID为null或默认值, 但是当调用DataContext完成Insert操作后, 此对象的主键ID属性会被自动被填充.

3.将使用数据库默认值的模型属性上,添加Column(IsDbGenerated=true)

比如:

[Column(Storage = "_CreatedTime"
, DbType = "DateTime NOT NULL"
, IsDbGenerated=true
)]
public System.DateTime CreatedTime
{...}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

CreatedTime是数据库中使用getDate()在创建时写入的,我们希望使用数据库中的值.而不是在程序中更新.注意使用LINQ更新的 是对象,如果使用默认的代码并且没有添加IsDbGenerated, 则数据库中的getDate()将失效.

4.修改DataContext类

如果我们添加了新的表, 除了修改.Design.cs中的Model模型类,还需要修改同样在此文件中的DataContext类.假设我们要添加一个Activity表,则需 要在DataContext类中增加下面代码:

        partial
 void
 InsertActivity(Activity instance);
partial void UpdateActivity(Activity instance);
partial void DeleteActivity(Activity instance);

public System.Data.Linq.Table<Activity> Activity
{
get
{
return this .GetTable<Activity>();
}
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

这三个分布方法是LINQ提供的机制,可以用来自定义增删改操作.Activity属性则是在LINQ语句中需要使用的数据库表对象.

5.为反向引用添加 XmlIgnore 特性标签

自顶而下的方向是指从1对多中的"1"一方作为顶端, 叶子节点是元子表 或 "多对多"表.

实际代码中, 就是将所有 EntityRef<T> 的字段所对应的属性上添加[XmlIgnore()]特性, 否则提供远程服务需要序列化对象对象时, 会提示存在循环引用.

六.修改 TagDataContext.cs 文件

在上面我们通过O/R 设计器创建的.cs文件中, 为每一个类添加一个Detach方法,下面是一个标准的Detach方法:

    [DataContract()]
public partial class TagCategory
{
public void Detach()
{
this .PropertyChanged = null ;
this .PropertyChanging = null ;
this ._TagCategoryLanguage = new EntitySet<TagCategoryLanguage>(new Action<TagCategoryLanguage>(this .attach_TagCategoryLanguage), new Action<TagCategoryLanguage>(this .detach_TagCategoryLanguage));
this ._TagCategoryRefCity = new EntitySet<TagCategoryRefCity>(new Action<TagCategoryRefCity>(this .attach_TagCategoryRefCity), new Action<TagCategoryRefCity>(this .detach_TagCategoryRefCity));
this ._TagItem = new EntitySet<TagItem>(new Action<TagItem>(this .attach_TagItem), new Action<TagItem>(this .detach_TagItem));
this ._TagCategory1 = new EntitySet<TagCategory>(new Action<TagCategory>(this .attach_TagCategory1), new Action<TagCategory>(this .detach_TagCategory1));
this ._T_TAG_TagCategory1 = default (EntityRef<TagCategory>);
}
}

一个partial类可以将类代码分布在多个文件中,所以我们可以在.cs文件中扩充Model类.

一个标准的Detach方法需要做如下几个事情:

1.如果有 PropertyChanged  和 PropertyChanging 成员, 则设置为null

其中只要是Model类实现了 INotifyPropertyChanging, INotifyPropertyChanged 这两个接口, 就会存在 PropertyChanged  和 PropertyChanging 这两个事件委托.默认生成的Model类实现了这两个接口. .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

2.将Model类所有的EntitySet和EntityRef字段设置为默认值.

这部分代码我们可以在自动生成的Model类的构造函数中找到. 需要做的就是Copy到此方法中.

在我的LINQ框架中, 每个Model类都要有Detach方法.

七.Detach的作用

Detach的主要作用是让对象脱离DataContext的跟踪, 从而实现一个方法:Update, 参数是Model对象模型.

一个对象Detach后, 我们即可调用自己的业务逻辑或数据访问层对象, 进行更新操作. 而不是每次都必须依赖DataContext.

如果依赖DataContext, 那么业务逻辑和职责就没办法拆分. 如果已经有了一个信息完整的实体对象, 在不调用Detach方法时会常常遇到无法Attach的问题. 每次更新都要先Select, 那显然是笨重的也是影响效率的.

也许至今还会有人此方法的正确性. 因为不了解LINQ底层的对象跟踪机制, 我也无法知道为何要这么做. 但是我花了好几天去寻找这个Detach方法, 因为中文没有相关资料, 而英文资料也是在试探了好几个解决方案后才在事件中验证了此方法的可行性.

有了Detach, 数据访问层的Update方法就可以这么写了:

        #region
 ===== Update =====
/// <summary>
/// 更新对象
/// </summary>
/// <param name="item">TagCategory对象</param>
/// <returns>变更集</returns>
public ChangeSet Update(TagCategory item)
{
TagDataContext dc = new TagDataContext(m_ConnectionString);
item.Detach();
dc.TagCategory.Attach( item, true );
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}

/// <summary>
/// 更新集合
/// </summary>
/// <param name="item">TagCategory集合</param>
/// <returns>变更集</returns>
public ChangeSet Update(List<TagCategory> itemList)
{
TagDataContext dc = new TagDataContext(m_ConnectionString);
foreach (TagCategory tempItem in itemList)
{
tempItem.Detach();
}
dc.TagCategory.AttachAll(itemList, true );
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}


#endregion
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

八.总结

也许有人又要批判我的LINQ架构的效率问题.  首先可以肯定我的Update方法会产生正确的操作, 比如肯定可以将一个对象完整的更新到数据库中.使用LINQ TO SQL 的跟踪和更新机制, 并不能提升多少效率, 反而会经常遇到问题, 而且没有一种能够将职责切分的方案.

至少我提供了一种方案.而且我还没找到第二种LINQ的框架设计.

 

 

 

LINQ 快速开发设计最佳实践(三) LINQ数据访问与业务逻辑层对象模板

一.摘要

LINQ在项目架构中的角色层次到底是什么? DataCotext对象应该放在哪里?层次间如何调用?在项目设计中我就不停的思考这些问题, 其实最后确定下来LINQ的结构体系还是在几个版本的尝试后,其间经过了几次论证和修改.作为本系列文章主题内容的收尾之作, 分享一下我的思考,以及我设计的LINQ业务逻辑对象与数据访问对象模板代码.

二.当三层架构遇上LINQ

传统的三 层架构我不需要多介绍: UI(Web)层, 业务逻辑层, 数据访问层. 其中可能还有模型层,公共层等辅助层.

数据访问层是和数据 持久化打交道的.打交道的对象不仅仅是数据库.数据库仅仅是数据持久化的一种方式而已. 当我们使用LINQ的时候, 往往使用LINQ TO SQL 来实现ORM,  上一章我技巧性的介绍了如何创建一个我认为标准的LINQ TO SQL的Model对象. 此后我们都是通过操作Model和DataContext对象实现对数据库的操作. 所以我曾想舍弃数据访问层, 有了LINQ TO SQL觉得不在需要这个层次了.LINQ的语法更接近业务对象查询的逻辑.感觉直接在业务逻辑层使用Model对象进行LINQ查询就可以了. 但是又考虑或者Model层也许应该改名叫数据访问层, 因为是它在和数据库打交道.又或者......为了广大程序员的精神安全, 在此省略万字.总之最后我的实践结论是:

1.Model层不可少, 用于存放我们创建的实现了ORM的Model对象.

2. 数据访问层不可少. DataContext对象应该放置在数据访问层. 每一个表至少都有一个数据访问层对象.每个表都只能负责自己的基础的CURD操作.其中逻辑的实现是使用LINQ语法操作Model对象实现.

3. 业务逻辑层中, 可以存在两个数据访问层对象(即数据库中的两个表)对应一个业务逻辑对象的情况,因为业务逻辑对象有时候需要进行业务抽象.比如标签和标签名字语言表, 完全可以抽象为一个"标签业务逻辑对象".

注: 这一条在我的项目中没有实现, 我的每一个数据库表都有一个业务逻辑对象.

4. 业务逻辑层对象之间不能相互调用.但是业务逻辑对象可以调用任何数据访问层对象.

5.数据访问层对象之间不能相互调用.

总 有人怀疑业务逻辑层和数据访问层存在的必要性, 觉得他们两者应该合并.但是我相信有经验的开发人员都很清楚, 这两者职责分明逻辑清楚,都是必不可少的. 比如要实现程序的多数据库支持就可以使用依赖注入的方式,创建多个数据库的数据访问层,然后在初始化时选择性加载.

大家可能还经常碰到这 种情况:业务逻辑层很多的方法都是单纯的返回数据访问层对象的方法数据, 只有一个"return"语句.我觉得这是一个颗粒度上面的问题.首先要明确数据访问层才是最细颗粒度的, 其实最细颗粒度的方法我们在程序中是很少直接使用的, 都是要增加很多的逻辑在里面. 所以在创建业务逻辑层对象的时候, 如果一个最基础的方法可能根本不会被UI层调用, 则没必要创建. 但是在业务逻辑层创建了所有的最细颗粒度的方法, 我觉得也不算错. 一般我将这两类方法使用过两个region折叠块分离, 也还算代码清晰.

三.实例简介

TagItem是我们常见 的"标签", 在博客园发表文章的时候也可以为一篇文章添加多个标签.假设数据库中有这么两个表, 一个是TagItem主表,另一个是语言表.对于现实世界一个"标签"表示的抽象, 可以有多种语言命名.

image

 

四.数据访问层模板.

下面是一个完整的TagItemLanguage表的的数据访问层对象代码. 对于TagItem表,我们只需要批量替换将"TagItemLanguage"替换成"TagItem"即可, 其实更科学的是使用模板生成工具比如CodeSmith自动生成数据访问类的代码.

 

/***************************************************************************************

* *
* * File Name : TagItemLanguageDA.cs
* * Creator : ziqiu.zhang
* * Create Time : 2008-11-10
* * Functional Description : TagItemLanguage数据访问类
* * Remark :
* *
* * Copyright (c) eLong Corporation. All rights reserved.
* ***************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using Com.Elong.Model.Tag;
using Com.Elong.Model.Common;
using Com.Elong.Common.Comment;

namespace Com.Elong.DataAccess.Tag
{
/// <summary>
/// TagItemLanguage数据访问类
/// </summary>
public class TagItemLanguageDA : IDisposable
{

#region ==================== Private Field ====================
private bool isDisposed = false ;
#endregion


#region ==================== Property =========================
private string m_ConnectionString;
/// <summary>
/// 数据库连接字符串
/// </summary>
public string ConnectionString
{
get { return m_ConnectionString; }
set { m_ConnectionString = value ; }
}
#endregion


#region ==================== Constructed Method ===============
/// <summary>
/// 禁止使用
/// </summary>
private TagItemLanguageDA()
{ }

/// <summary>
/// 构造函数
/// </summary>
/// <param name="connectionString">数据库连接串</param>
public TagItemLanguageDA(string connectionString)
{
m_ConnectionString = connectionString;
}
#endregion


#region ==================== Public Method ====================

#region ===== Select =====
/// <summary>
/// 根据主键得到对象
/// </summary>
/// <param name="pkid">主键</param>
/// <returns>对象</returns>
public TagItemLanguage GetItemByPkid(int pkid)
{
TagItemLanguage result = new TagItemLanguage();
TagDataContext dc = new TagDataContext(ConnectionString);
dc.DeferredLoadingEnabled = true ;
var query =
from tagItemLanguage in dc.TagItemLanguage
where tagItemLanguage.pkid == pkid
select tagItemLanguage;
result = query.SingleOrDefault();
return result;
}

/// <summary>
/// 根据主键列表得到对象集合
/// </summary>
/// <param name="pkid">主键列表</param>
/// <returns>对象集合</returns>
public List<TagItemLanguage> GetListByPkid(List<int > pkidList)
{
if (pkidList.Count > 2100)
{
throw new ParameterOverflowException("传入的pkid列表个数大于2100,超过了Sql允许的最大参数个数。" );
}
List<TagItemLanguage> result = new List<TagItemLanguage>();
TagDataContext dc = new TagDataContext(ConnectionString);
dc.DeferredLoadingEnabled = true ;
var query =
from tagItemLanguage in dc.TagItemLanguage
where pkidList.Contains(tagItemLanguage.pkid)
select tagItemLanguage;
result = query.ToList();
return result;
}
#endregion

#region ===== Insert =====
/// <summary>
/// 插入对象
/// </summary>
/// <param name="item">TagItemLanguage对象</param>
/// <returns>变更集</returns>
public ChangeSet Insert(TagItemLanguage item)
{
TagDataContext dc = new TagDataContext(ConnectionString);
dc.TagItemLanguage.InsertOnSubmit(item);
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}

/// <summary>
/// 插入集合
/// </summary>
/// <param name="item">TagItemLanguage集合</param>
/// <returns>变更集</returns>
public ChangeSet Insert(List<TagItemLanguage> itemList)
{
TagDataContext dc = new TagDataContext(ConnectionString);
dc.TagItemLanguage.InsertAllOnSubmit(itemList);
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}
#endregion

#region ===== InsertUpdate =====
/// <summary>
/// InsertUpdate对象
/// </summary>
/// <param name="item">TagItemLanguage对象</param>
/// <returns>变更集</returns>
public ChangeSet InsertUpdate(TagItemLanguage item)
{
TagDataContext dc = new TagDataContext(m_ConnectionString);
var query =
from tagItemLanguage in dc.TagItemLanguage
where tagItemLanguage.TagName == item.TagName
&& tagItemLanguage.TagItemId == item.TagItemId
&& tagItemLanguage.LanguageCode == item.LanguageCode
select tagItemLanguage;

List<TagItemLanguage> itemList = query.ToList();
if (itemList != null && itemList.Count > 0)
{
item.pkid = itemList[0].pkid;
if (itemList.Count > 1)
{
//发现了多条重复的数据.保留第一条,删除其他的条目
itemList.RemoveAt(0);
this .PhysicsDelete(itemList);
WebLog.CommentLog.ErrorLogger.Error(string .Format(@"在TagItemLanguage表发现错误数据.
TagName:{0}, TagItemId:{1}, LanguageCode:{2}, 保留pkid:{3},删除其他数据."
,
item.TagName,
item.TagItemId.ToString(),
item.LanguageCode,
item.pkid.ToString()));
}
//更新对象
ChangeSet result = this .Update(item);
return result;
}
else
{
//插入对象
ChangeSet result = this .Insert(item);
return result;
}
}

/// <summary>
/// InsertUpdate对象集合
/// </summary>
/// <param name="itemList">TagItemLanguage集合</param>
/// <returns>变更集数组:索引0为插入操作变更集,索引1为更新操作变更集</returns>
public ChangeSet[] InsertUpdate(List<TagItemLanguage> itemList)
{
ChangeSet[] result = new ChangeSet[2];
List<string > tagNameList = new List<string >();
List<string > languageCodeList = new List<string >();
List<int > tagItemIdList = new List<int >();

foreach (TagItemLanguage tempItem in itemList)
{
if (!tagNameList.Contains(tempItem.TagName))
{
tagNameList.Add(tempItem.TagName);
}
if (!languageCodeList.Contains(tempItem.LanguageCode))
{
languageCodeList.Add(tempItem.LanguageCode);
}
if (!tagItemIdList.Contains(tempItem.TagItemId))
{
tagItemIdList.Add(tempItem.TagItemId);
}
}

//选取所有可能存在于数据库中的集合.
TagDataContext dc = new TagDataContext(m_ConnectionString);
var query =
from tagItemLanguage in dc.TagItemLanguage
where tagNameList.Contains(tagItemLanguage.TagName)
&& languageCodeList.Contains(tagItemLanguage.LanguageCode)
&& tagItemIdList.Contains(tagItemLanguage.TagItemId)
select tagItemLanguage;

//从数据库集合中筛选出需要更新和插入的列表
List<TagItemLanguage> allList = query.ToList();
List<TagItemLanguage> insertList = new List<TagItemLanguage>();
List<TagItemLanguage> updateList = new List<TagItemLanguage>();
foreach (TagItemLanguage tempItem in itemList)
{
List<TagItemLanguage> itemItemList = allList.Where(c => c.LanguageCode == tempItem.LanguageCode && c.TagName == tempItem.TagName && c.TagItemId == tempItem.TagItemId).ToList();
if (itemItemList != null && itemItemList.Count > 0)
{
tempItem.pkid = itemItemList[0].pkid;
if (itemItemList.Count > 1)
{
//发现了多条重复的数据.保留第一条,删除其他的条目
itemItemList.RemoveAt(0);
this .PhysicsDelete(itemItemList);
WebLog.CommentLog.ErrorLogger.Error(string .Format(@"在TagItemLanguage表发现错误数据.
TagName:{0}, TagItemId:{1}, LanguageCode:{2}, 保留pkid:{3},删除其他数据."
,
tempItem.TagName,
tempItem.TagItemId.ToString(),
tempItem.LanguageCode,
tempItem.pkid.ToString()));
}
updateList.Add(tempItem);
}
else
{
insertList.Add(tempItem);
}
}

//执行操作
result[0] = this .Insert(insertList);
result[1] = this .Update(updateList);
return result;

}
#endregion

#region ===== Update =====
/// <summary>
/// 更新对象
/// </summary>
/// <param name="item">TagItemLanguage对象</param>
/// <returns>变更集</returns>
public ChangeSet Update(TagItemLanguage item)
{
TagDataContext dc = new TagDataContext(ConnectionString);
item.Detach();
dc.TagItemLanguage.Attach(item, true );
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}

/// <summary>
/// 更新对象集合
/// </summary>
/// <param name="itemList">TagItemLanguage集合</param>
/// <returns>变更集</returns>
public ChangeSet Update(List<TagItemLanguage> itemList)
{
TagDataContext dc = new TagDataContext(ConnectionString);
foreach (TagItemLanguage tempItem in itemList)
{
tempItem.Detach();
}
dc.TagItemLanguage.AttachAll(itemList, true );
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}
#endregion

#region ===== Delete =====
/// <summary>
/// 物理删除对象
/// </summary>
/// <param name="item">TagItemLanguage对象</param>
/// <returns>变更集</returns>
public ChangeSet PhysicsDelete(TagItemLanguage item)
{
TagDataContext dc = new TagDataContext(m_ConnectionString);
item.Detach();
dc.TagItemLanguage.Attach(item);
dc.TagItemLanguage.DeleteOnSubmit(item);
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}

/// <summary>
/// 物理删除对象集合
/// </summary>
/// <param name="itemList">TagItemLanguage集合</param>
/// <returns>变更集</returns>
public ChangeSet PhysicsDelete(List<TagItemLanguage> itemList)
{
TagDataContext dc = new TagDataContext(m_ConnectionString);
foreach (TagItemLanguage tempItem in itemList)
{
tempItem.Detach();
}
dc.TagItemLanguage.AttachAll(itemList);
dc.TagItemLanguage.DeleteAllOnSubmit(itemList);
ChangeSet result = dc.GetChangeSet();
dc.SubmitChanges();
return result;
}
#endregion

#endregion


#region IDisposable 成员

public void Dispose()
{
Dispose(true );
GC.SuppressFinalize(this );
}

public virtual void Dispose(bool disposing)
{
if (!this .isDisposed)
{
if (disposing)
{
//释放非托管资源
}

//释放托管资源
m_ConnectionString = null ;
isDisposed = true ;
}
}

~TagItemLanguageDA()
{
Dispose(false );
}

#endregion
}
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

这个标准的数据访问类中, 除了基本的Insert,Select,Update,Delete操作外,需要特别讲解的就是上面的InsertUpdate操作.在我们的数据库中有 一个IsDeleted标志位用来表示本条记录是否被逻辑删除.而当用户希望插入另外一条记录的时候,有时候在数据库中并不是Insert操作而仅仅是恢 复逻辑删除的数据.

如果每次Insert前,都要先Select看是否存在本条数据, 是十分笨重的.所以InsertUpdate提供了"逻辑插入"功能, 可以自己判断是需要Insert还是Update.

五.业务逻辑层模板

再来看看我们通用的业务逻辑层对象的代码. 则是一个最简单的业务逻辑层对象, 其中大部分都是单纯的对数据访问层方法的返回, 但是其中有的"逻辑删除"方法是自己特有的.

/***************************************************************************************
 * *

* * File Name : TagCategoryLanguageBL.cs
* * Creator : ziqiu.zhang
* * Create Time : 2008-11-10
* * Functional Description : TagCategoryLanguage业务逻辑类
* * Remark :
* *
* * Copyright (c) eLong Corporation. All rights reserved.
* ***************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Text;
using Com.Elong.DataAccess.Tag;
using Com.Elong.Model.Tag;
using Com.Elong.BusinessRules.Common;
using Com.Elong.Model.Common;


namespace Com.Elong.BusinessRules.Tag
{
/// <summary>
/// TagCategoryLanguage业务逻辑类
/// </summary>
public class TagCategoryLanguageBL : IDisposable
{

#region ==================== Private Field ====================
private bool isDisposed = false ;
private TagCategoryLanguageDA tagCategoryLanguageDA;
#endregion


#region ==================== Property =========================
private string m_ConnectionString;
/// <summary>
/// 数据库连接字符串
/// </summary>
public string ConnectionString
{
get { return m_ConnectionString; }
set { m_ConnectionString = value ; }
}
#endregion


#region ==================== Constructed Method ===============
/// <summary>
/// 禁止使用
/// </summary>
private TagCategoryLanguageBL()
{ }

/// <summary>
/// 构造函数
/// </summary>
/// <param name="connectionString">数据库连接串</param>
public TagCategoryLanguageBL(string connectionString)
{
m_ConnectionString = connectionString;
tagCategoryLanguageDA = new TagCategoryLanguageDA(connectionString);
}
#endregion


#region ==================== Basic Method ====================

#region ===== Select =====
/// <summary>
/// 根据主键得到对象
/// </summary>
/// <param name="pkid">主键</param>
/// <returns>对象</returns>
public TagCategoryLanguage GetItemByPkid(int pkid)
{
return tagCategoryLanguageDA.GetItemByPkid(pkid);
}

/// <summary>
/// 根据主键列表得到对象集合
/// </summary>
/// <param name="pkid">主键列表</param>
/// <returns>对象集合</returns>
public List<TagCategoryLanguage> GetListByPkid(List<int > pkidList)
{
return tagCategoryLanguageDA.GetListByPkid(pkidList);
}
#endregion

#region ===== Insert =====
/// <summary>
/// 插入对象
/// </summary>
/// <param name="item">TagCategoryLanguage对象</param>
/// <returns>变更集</returns>
public ChangeSet Insert(TagCategoryLanguage item)
{
return tagCategoryLanguageDA.Insert(item);
}

/// <summary>
/// 插入集合
/// </summary>
/// <param name="item">TagCategoryLanguage集合</param>
/// <returns>变更集</returns>
public ChangeSet Insert(List<TagCategoryLanguage> itemList)
{
return tagCategoryLanguageDA.Insert(itemList);
}
#endregion

#region ===== InsertUpdate =====
/// <summary>
/// InsertUpdate对象
/// </summary>
/// <param name="item">TagCategoryRefCity对象</param>
/// <returns>变更集</returns>
public ChangeSet InsertUpdate(TagCategoryLanguage item)
{
return tagCategoryLanguageDA.InsertUpdate(item);
}

/// <summary>
/// InsertUpdate集合
/// </summary>
/// <param name="itemList">TagCategoryRefCity集合</param>
/// <returns>变更集</returns>
public ChangeSet[] InsertUpdate(List<TagCategoryLanguage> itemList)
{
return tagCategoryLanguageDA.InsertUpdate(itemList);
}
#endregion

#region ===== Update =====
/// <summary>
/// 更新对象
/// </summary>
/// <param name="item">TagCategoryLanguage对象</param>
/// <returns>变更集</returns>
public ChangeSet Update(TagCategoryLanguage item)
{
return tagCategoryLanguageDA.Update(item);
}

/// <summary>
/// 更新集合
/// </summary>
/// <param name="item">agCategory集合</param>
/// <returns>变更集</returns>
public ChangeSet Update(List<TagCategoryLanguage> itemList)
{
return tagCategoryLanguageDA.Update(itemList);
}
#endregion

#region ===== Delete =====
/// <summary>
/// 物理删除对象
/// </summary>
/// <param name="item">TagCategoryLanguage对象</param>
/// <returns>变更集</returns>
public ChangeSet PhysicsDelete(TagCategoryLanguage item)
{
return tagCategoryLanguageDA.PhysicsDelete(item);
}

/// <summary>
/// 物理删除集合
/// </summary>
/// <param name="item">TagCategoryLanguage集合</param>
/// <returns>变更集</returns>
public ChangeSet PhysicsDelete(List<TagCategoryLanguage> itemList)
{
return tagCategoryLanguageDA.PhysicsDelete(itemList);
}

/// <summary>
/// 逻辑删除对象
/// </summary>
/// <param name="item">TagCategoryLanguage对象</param>
/// <returns>变更集</returns>
public ChangeSet LogicDelete(int pkid)
{
TagCategoryLanguage item = tagCategoryLanguageDA.GetItemByPkid(pkid);
if (item.IsDeleted != 1)
{
item.IsDeleted = 1;
}
ChangeSet result = tagCategoryLanguageDA.Update(item);
return result;
}

/// <summary>
/// 逻辑删除集合
/// </summary>
/// <param name="item">TagCategoryLanguage集合</param>
/// <returns>变更集</returns>
public ChangeSet LogicDelete(List<int > pkidList)
{
List<TagCategoryLanguage> itemList = tagCategoryLanguageDA.GetListByPkid(pkidList);
foreach (TagCategoryLanguage item in itemList)
{
item.IsDeleted = 1;
}
return tagCategoryLanguageDA.Update(itemList);
}
#endregion

#endregion

#region ==================== Extend Method ====================
#endregion

#region IDisposable 成员

public void Dispose()
{
Dispose(true );
GC.SuppressFinalize(this );
}

public virtual void Dispose(bool disposing)
{
if (!this .isDisposed)
{
if (disposing)
{
//释放非托管资源
tagCategoryLanguageDA.Dispose();
}

//释放托管资源
m_ConnectionString = null ;
isDisposed = true ;
}
}

~TagCategoryLanguageBL()
{
Dispose(false );
}

#endregion

}
}

六.如何组合应用

上面是TagItemLanguage表的BL(Business Logic)和DA(Data Access)对象, 同理TagItem表的TagItemBL和TagItemDA几乎是一模一样的代码.

一个TagItem对象带有一个TagItemLanguage对象集合的属性, 经常我们更新一个TagItem需要更新关联的TagItemLanguage对象, 这个时侯我们就可以在TagItemBL对象中创建一个方法, 先调用TagItemDA更新TagItem表, 再调用TagItemLanguageDA更新TagItemLanguage表.

七.经验和总结

使用LINQ第一个挑战是拆分业务逻辑和对象职责, 第二挑战就是性能. 既然将每个表的更新都放到了具体的对象去做, 那么很显然会造成大量的数据库访问请求. 比如一开始对于一个集合的逻辑更新, 如果循环每条记录去判断是要更新还是删除, 那就会造成多次数据库连接. 而我的做法是首先群穷举出所有的可能需要的数据条目, 都取出来后在程序中进行查询来判断哪些对象需要更新, 哪些需要添加, 最后执行一次批量更新和批量插入操作.一共访问三次数据库. 我虽然加重了传输数据量的传输但是已经大幅减少了数据库连接次数. 一般数据库和服务器的通信都是内网的, 所以牺牲数据量是值得的, 实践证明数据库连接才是最宝贵的资源.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值