前言:首先给大家说声"对不起",因为自从打算写这系列的文章以来,得到大家很多的支持,谢谢大家!最近因为公司的事和朋友找工作的事,没有怎么接着写了,也调了大家的胃口,还希望园子里的朋友原谅!
本篇主要是讲述数据层的开发,之前的一篇文章已经给出了很多的选中的方案,如SqlHelper,DataTable/DataSet,以及自定义实体。但是我们说过了,那些方案都有不尽人意的地方,所以我们就提出用Linq,找个方案就比之前好一些,但是也不是那么完美了。
本篇的话题如下:
Linq中自定义方法
提出解决方案
Linq中自定义方法
在此之前,我们先要讲清楚一个问题:在Linq中的自定义存储过程。
我们知道,当我们把数据库中的一张表拖放到Linq的ORM设计器上以后,ORM就自动生成了很多的方法,如Delete,Update,Insert方法,这些方法都是自动生成的,一般是没有什么问题的,但是一个项目的开发中,很多时候我们都要把这些方法进行定制。那么我们就要用自己的存储过程。
一般情况下,我们可以在我们的数据库中定义一些的存储过程,如下,我们在之前定义的ENTUserAccount表中添加一条数据:
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表中插入数据
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表中更新数据
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表中所有记录
CREATE PROCEDURE ENTUserAccountSelectAll
AS
SET NOCOUNT ON
SELECT ENTUserAccountId, WindowsAccountName, FirstName, LastName,
Email, IsActive, InsertDate, InsertENTUserAccountid,
UpdateDate, UpdateENTUserAccountId, Version
FROM ENTUserAccount
RETURN
通过ENTUserAccountId来查找记录
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
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文件夹中,而是直接加在外面的。
namespace V2.PaidTimeOffDAL
{
public partial class ENTUserAccount : IENTBaseEntity { }
}
大家可能会想:ENTUserAccount类是个partial类,那么就说明这个类之前已经在什么地方有了的?
不错!我们之前说要把ENTUserAccountById的存储过程拖到ENTUserAccount表上,其实我们就是想生成这个和数据表同名的ENTUserAccount类。
我们使得这个类实现这个接口,那么我们以后就可以"针对接口编程"。
其实我们知道我们的DAL数据层的功能很明了:和数据库直接打交道,实现增,删,改,查操作。
为了使得我们所有的数据操作统一,我们定义了一个基类,以后的所有数据库操作类都实现它。
我们在Framework文件夹中添加类:ENTBaseData.cs
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参数,如下:
这样是没有错,但是不是很好。如果我们的entUseAccount中如果有写字段没有赋值,那么我们插入就出问题。
所以我们的实现方法是:直接在不同的数据操作类中,针对特定的表写Insert和Update方法。
我们就来看看ENTUserAccount数据表的操作类的实现:
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方法的实现:
{
db.ENTUserAccountDelete(id);
}
我们之前说过,只是一个Template Method模式的使用,使用的意图是:把具体实现延迟到子类。可能在以后的项目中,我们的数据库要更改了,表也改了,那时,我们的 ENTUserAccount数据表可能改为User表,那么我们之前实现的代码不变,只要重写Delete方法就行了,如
{
db. User Delete(id);
}
下面,我们就来看看Insert方法:
其实实现也很简单,我们采用和SqlHelper相同的方式:第一个方法传入HRPaidTimeOffDataContext ,第二个方法传入connectionString
而且我们传入的参数是和ENTUserAccount表相对应的字段,这样就比只是传入一个ENTUserAccount类要好。
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方法实现如下;
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的实现的示例就到这里,当然,我们这里只有一个表,但是我们通过这个示例知道了我们之后实现方法,其他表的实现数据操作的方式一样的,随着深入的讲述,大家会看到的!
为了大家交流,已经创建企业项目开发团队,希望大家也以后会把有关企业开发的文章放入团队中,希望大家积极参加这个团队。而且我以后也会发表更多的项目示例,大家一起学习进步!