前言:首先给大家说声"对不起",因为自从打算写这系列的文章以来,得到大家很多的支持,谢谢大家!最近因为公司的事和朋友找工作的事,没有怎么接着写了,也调了大家的胃口,还希望园子里的朋友原谅!
本篇主要是讲述数据层的开发,之前的一篇文章已经给出了很多的选中的方案,如SqlHelper,DataTable/DataSet,以及自定义实体。但是我们说过了,那些方案都有不尽人意的地方,所以我们就提出用Linq,找个方案就比之前好一些,但是也不是那么完美了。
本篇的话题如下:
Linq中自定义方法
提出解决方案
Linq中自定义方法
在此之前,我们先要讲清楚一个问题:在Linq中的自定义存储过程。
我们知道,当我们把数据库中的一张表拖放到Linq的ORM设计器上以后,ORM就自动生成了很多的方法,如Delete,Update,Insert方法,这些方法都是自动生成的,一般是没有什么问题的,但是一个项目的开发中,很多时候我们都要把这些方法进行定制。那么我们就要用自己的存储过程。
一般情况下,我们可以在我们的数据库中定义一些的存储过程,如下,我们在之前定义的ENTUserAccount表中添加一条数据:
Code
CREATE PROCEDURE ENTUserAccountInsert
(
@WindowsAccountName varchar(50),
@FirstName varchar(50),
@LastName varchar(50),
@Email varchar(100),
@IsActive bit,
@InsertENTUserAccountId int
)
AS
SET NOCOUNT ON
INSERT INTO ENTUserAccount(WindowsAccountName, FirstName, LastName,
Email, IsActive, InsertENTUserAccountId, InsertDate,
UpdateENTUserAccountId, UpdateDate)
VALUES (@WindowsAccountName, @FirstName, @LastName, @Email,
@IsActive, @InsertENTUserAccountId, GetDate(),
@InsertENTUserAccountId, GetDate())
RETURN
我们接来进行下面的操作;
1.把这个存储过程拖放到ORM设计器的右边的方法面板中,这样存储过程就自动的生成一个方法。
2.在ORM中点击ENTUserAccount表,选择属性中的Insert,如图:(插图出现问题,大家原谅!!!)
3.在出现的面板中,我们选择Customer(自定义)。选择我们之前由存储过程实生成的的方法,确认就OK了。
如果我们在我们的以后的代码中调用HRPaidTimeOffDataContext.InsertENTUseAccount方法,那么此时我们就实际上调用我们自定义的方法了,之前自动生成的Insert方法就不调用了。
同理,Update,Delete方法的自定义也是一样的。
提出解决方案
约定:我们以后的存储过程的命名采用方式:表名+操作,如ENTUserAccountInsert。
首先我们创建一下存储过程:
ENTUserAccount表中插入数据
Code
CREATE PROCEDURE [dbo].[ENTUserAccountInsert]
(
@ENTUserAccountId int OUTPUT,
@WindowsAccountName varchar(50),
@FirstName varchar(50),
@LastName varchar(50),
@Email varchar(100),
@IsActive bit,
@InsertENTUserAccountId int
)
AS
SET NOCOUNT ON
INSERT INTO ENTUserAccount (WindowsAccountName, FirstName,
LastName, Email, IsActive, InsertDate,
InsertENTUserAccountId, UpdateDate,
UpdateENTUserAccountId)
VALUES (@WindowsAccountName, @FirstName, @LastName,
@Email, @IsActive, GetDate(),
@InsertENTUserAccountId, GetDate(),
@InsertENTUserAccountId)
SET @ENTUserAccountId = Scope_Identity()
RETURN
ENTUserAccount表中更新数据
Code
CREATE PROCEDURE [dbo].[ENTUserAccountUpdate]
(
@ENTUserAccountId int,
@WindowsAccountName varchar(50),
@FirstName varchar(50),
@LastName varchar(50),
@Email varchar(100),
@IsActive bit,
@UpdateENTUserAccountId int,
@Version timestamp
)
AS
SET NOCOUNT ON
UPDATE ENTUserAccount
SET WindowsAccountName = @WindowsAccountName
, FirstName = @FirstName
, LastName = @LastName
, Email = @Email
, IsActive = @IsActive
, UpdateDate = GetDate()
, UpdateENTUserAccountId = @UpdateENTUserAccountId
WHERE ENTUserAccountId = @ENTUserAccountId
AND Version = @Version
RETURN @@ROWCOUNT
ENTUserAccount表中删除数据
CREATE PROCEDURE [dbo].[ENTUserAccountDelete]
(
@ENTUserAccountId int
)
AS
SET NOCOUNT ON
DELETE FROM ENTUserAccount
WHERE ENTUserAccountId = @ENTUserAccountId
RETURN
获取ENTUserAccount表中所有记录
Code
CREATE PROCEDURE ENTUserAccountSelectAll
AS
SET NOCOUNT ON
SELECT ENTUserAccountId, WindowsAccountName, FirstName, LastName,
Email, IsActive, InsertDate, InsertENTUserAccountid,
UpdateDate, UpdateENTUserAccountId, Version
FROM ENTUserAccount
RETURN
通过ENTUserAccountId来查找记录
Code
CREATE PROCEDURE ENTUserAccountSelectById
(
@ENTUserAccountId int
)
AS
SET NOCOUNT ON
SELECT ENTUserAccountId, WindowsAccountName, FirstName, LastName,
Email, IsActive, InsertDate, InsertENTUserAccountid,
UpdateDate, UpdateENTUserAccountId, Version
FROM ENTUserAccount
WHERE ENTUserAccountId = @ENTUserAccountId
RETURN
以前的存储过程没有和大家以前做的项目一样,是很普通的一些存储过程。
写完上面的存储过程之后,大家把Insert,Update,Delete的存储过程拖到ORM的方法面板中生成方法,然后按照我们之前说的方法定制,把ENTUserAccount的Update,Insert等方法都配置成为我们自己写的方法。
注意:对于Select的存储过程,我们不应该把它们直接拖到方法面板中,我们而是把ENTUserAccountSelectAll,ENTUserAccountSelectById存储过程拖到到ORM设计器中的ENTUserAccount表上。因为只有这样,自定生成的代码才返回ENTUserAccount类的类型;如果是像之前那样拖到方法面板中,那么返回值就是ISingle<ENTUserAccount>,大家之后会看到原因的。
之前,我们就在V2.PaidTimeOffDAL中创建一个文件夹,命名为Framework。让后在这个文件夹下面添加一个类,命名为IENTBaseEntity.cs
Code
namespace V2.PaidTimeOffDAL.Framework
{
public interface IENTBaseEntity
{
DateTime InsertDate { get; set; }
int InsertENTUserAccountId { get; set; }
DateTime UpdateDate { get; set; }
int UpdateENTUserAccountId { get; set; }
Binary Version { get; set; }
}
}
因为Binary 类型是在System.Data.Linq下的,所以我们在类中添加这个命名空间的引用。
大家在想:为什么我们要定义这么一个接口?
原因有两点:
1.因为我们的这个项目中之后会有审计跟踪的功能,就是说数据库中记录的每次修改都会记录下是谁在什么时候修改的,而且我们的所有的表中都会有上面的5个字段。定义接口这样做为了使得每个类都必需定义必需有上面的5个字段。
2.定义接口,可以实现一些依赖倒置的原则,以后大家会看到效果的。
添加完上面的类之后,那么我们就再添加一个类:CustomizedEntities.cs ,这个类不在Framework文件夹中,而是直接加在外面的。
using V2.PaidTimeOffDAL.Framework;
namespace V2.PaidTimeOffDAL
{
public partial class ENTUserAccount : IENTBaseEntity { }
}
大家可能会想:ENTUserAccount类是个partial类,那么就说明这个类之前已经在什么地方有了的?
不错!我们之前说要把ENTUserAccountById的存储过程拖到ENTUserAccount表上,其实我们就是想生成这个和数据表同名的ENTUserAccount类。
我们使得这个类实现这个接口,那么我们以后就可以"针对接口编程"。
其实我们知道我们的DAL数据层的功能很明了:和数据库直接打交道,实现增,删,改,查操作。
为了使得我们所有的数据操作统一,我们定义了一个基类,以后的所有数据库操作类都实现它。
我们在Framework文件夹中添加类:ENTBaseData.cs
Code
public abstract class ENTBaseData<T> where T : IENTBaseEntity
{
public abstract List<T> Select();
public abstract T Select(int id);
public abstract void Delete(HRPaidTimeOffDataContext db, int id);
public void Delete(string connectionString, int id)
{
using (HRPaidTimeOffDataContext db = new
HRPaidTimeOffDataContext(connectionString))
{
Delete(db, id);
}
}
}
类的编写也不难理解:就是有写操作数据的方法。大家还要注意一点:Delete方法的实现,我们采用了Template Method设计模式,也就是常说的"钩子方法",实现的原因,现在讲述不是很好理解,没有一种自然的过程,我以后会说的。
这样我们就定义这样的一个基类,然后针对每个表都有数据操作,那么我们就让每个表的数据操作类都继承这个基类。
注意:大家在上面的基类中,没有看到Insert和Update方法,只是有原因的。如果我们在基类中写了Insert方法,就会传入一个T参数,如下:
public bool Insert(T entUserAccount)
这样是没有错,但是不是很好。如果我们的entUseAccount中如果有写字段没有赋值,那么我们插入就出问题。
所以我们的实现方法是:直接在不同的数据操作类中,针对特定的表写Insert和Update方法。
我们就来看看ENTUserAccount数据表的操作类的实现:
Code
public class ENTUserAccountData : ENTBaseData<ENTUserAccount>
{
public override List<ENTUserAccount> Select()
{
using (HRPaidTimeOffDataContext db = new HRPaidTimeOffDataContext(DBHelper.GetHRPaidTimeOffConnectionString()))
{
return db.ENTUserAccountSelectAll().ToList();
}
}
public override ENTUserAccount Select(int id)
{
using (HRPaidTimeOffDataContext db = new HRPaidTimeOffDataContext(DBHelper.GetHRPaidTimeOffConnectionString()))
{
return Select(db, id);
}
}
上面方法很简单:直接调用自动生成的方法。
至于DBHelper.GetHRPaidTimeOffConnectionString()方法,就是把数据库的链接字符串从web.config文件中读出。
下面我们就来看看Delete方法的实现:
public override void Delete(HRPaidTimeOffDataContext db, int id)
{
db.ENTUserAccountDelete(id);
}
我们之前说过,只是一个Template Method模式的使用,使用的意图是:把具体实现延迟到子类。可能在以后的项目中,我们的数据库要更改了,表也改了,那时,我们的 ENTUserAccount数据表可能改为User表,那么我们之前实现的代码不变,只要重写Delete方法就行了,如
public override void Delete(HRPaidTimeOffDataContext db, int id)
{
db. User Delete(id);
}
下面,我们就来看看Insert方法:
其实实现也很简单,我们采用和SqlHelper相同的方式:第一个方法传入HRPaidTimeOffDataContext ,第二个方法传入connectionString
而且我们传入的参数是和ENTUserAccount表相对应的字段,这样就比只是传入一个ENTUserAccount类要好。
Code
public int Insert(string connectionString, string windowsAccountName, string firstName,
string lastName, string email, bool isActive, int insertUserAccountId)
{
using (HRPaidTimeOffDataContext db = new HRPaidTimeOffDataContext(connectionString))
{
return Insert(db, windowsAccountName, firstName, lastName, email, isActive, insertUserAccountId);
}
}
public int Insert(HRPaidTimeOffDataContext db, string windowsAccountName, string firstName,
string lastName, string email, bool isActive, int insertUserAccountId)
{
Nullable<int> entUserAccountId = 0;
db.ENTUserAccountInsert(ref entUserAccountId, windowsAccountName, firstName, lastName,
email, isActive, insertUserAccountId);
return Convert.ToInt32(entUserAccountId);
}
同理,Update方法实现如下;
Code
public bool Update(string connectionString, int userAccountId, string windowsAccountName,
string firstName, string lastName, string email, bool isActive, int updateUserAccountId,
Binary version)
{
using (HRPaidTimeOffDataContext db = new HRPaidTimeOffDataContext(connectionString))
{
return Update(db, userAccountId, windowsAccountName, firstName, lastName, email,
isActive, updateUserAccountId, version);
}
}
public bool Update(HRPaidTimeOffDataContext db, int userAccountId, string windowsAccountName,
string firstName, string lastName, string email, bool isActive, int updateUserAccountId,
Binary version)
{
int rowsAffected = db.ENTUserAccountUpdate(userAccountId, windowsAccountName, firstName,
lastName, email, isActive, updateUserAccountId, version);
return rowsAffected == 1;
}
到这里,我们的DAL的实现的示例就到这里,当然,我们这里只有一个表,但是我们通过这个示例知道了我们之后实现方法,其他表的实现数据操作的方式一样的,随着深入的讲述,大家会看到的!
为了大家交流,已经创建企业项目开发团队,希望大家也以后会把有关企业开发的文章放入团队中,希望大家积极参加这个团队。而且我以后也会发表更多的项目示例,大家一起学习进步!