[讨论]使用实体框架时,对数据库是NEW还是单例比较好

yixian2007 2016-09-13 11:04:03
按理来说,我应该NEW,但实际操作过程中出现了以下问题,我就换成了单例模式,但单例模式也有问题。

下面详细说明为什么使用单例模式:
问题:业务类在使用时不得不单例,不然无法使用transactionscope。
举例如下:业务类A,需要做一个a操作,而这个操作包括了业务类B的一个b操作,然后再执行A中的c操作,需要transactionscope,此时,如果不单例数据库的话,业务类A实例化时,需要业务类A内部读取数据库数据并实例化,然后业务类B实例化时,需要业务类B内部读取数据库并实例化,那么这两个业务类是分别实例化的,自然也是分别两个数据库连接,此时执行业务类A的a操作,并NEW一个transactionscope,因为涉及到B中的b,所以会弹出错误,错误原因很简单,就是一个过程却是两个线程执行的,无法执行存储过程。

下面附上业务类A中的代码,和业务B的代码,请教一下,我这样实例化是否正确
业务类

public class ExamBatchInfo
{
DataBaseControl _DBControl = DataBaseControl.getSingleton();
public virtual IEnumerable<ExamRoomInfo> ExamRoomList
{
get
{
var query = from r in _DBControl.ExamRoom_GetList(YearTermID, CourID, BatchOrder).AsEnumerable()
select ExamRoomInfo.GetInfo(r);
return query;
}
}
#region 初始化与转换
private void InitClass(exam_Batch dbtable)
{
if (dbtable != null)
{
this.YearTermID = dbtable.YearTermID;
this.CourID = dbtable.CourID;
this.BatchOrder = dbtable.BatchOrder;
this.ExamWeek = dbtable.ExamWeek;
this.ExamDate = dbtable.ExamDate;
this.BatchName = dbtable.BatchName;
this.TeachList = dbtable.TeachList;
this.SelectedNum = dbtable.SelectedNum;
this.WeekSection = dbtable.WeekSection;
this.WeekTime = dbtable.WeekTime;
this.TimeExplain = dbtable.TimeExplain;
this.BatchType = dbtable.BatchType;
this.BatchFlag = dbtable.BatchFlag;
this.Inspector1 = dbtable.Inspector1;
this.Inspector1Name = dbtable.Inspector1Name;
this.Inspector2 = dbtable.Inspector2;
this.Inspector2Name = dbtable.Inspector2Name;
this.Inspector3 = dbtable.Inspector3;
this.Inspector3Name = dbtable.Inspector3Name;
this.Operator = dbtable.Operator;
this.LastDate = dbtable.LastDate;
}
}

public ExamBatchInfo()
{
this.YearTermID = string.Empty;
this.CourID = string.Empty;
this.BatchOrder = 0;
this.ExamWeek = 0;
this.ExamDate = null;
this.WeekSection = string.Empty;
this.WeekTime = 3;
this.TimeExplain = string.Empty;
this.BatchFlag = 0;
this.Inspector1 = string.Empty;
this.Inspector1Name = string.Empty;
this.Inspector2 = string.Empty;
this.Inspector2Name = string.Empty;
this.Inspector3 = string.Empty;
this.Inspector3Name = string.Empty;
this.Operator = string.Empty;
this.LastDate = DateTime.Now;
}

public ExamBatchInfo(exam_Batch dbtable)
{
InitClass(dbtable);
}
}
}

/// <summary>
/// 重置批次(包括删除考场、删除学生、任务还原为未安排状态、批次删除)
/// </summary>
public void Reset()
{
if (BatchOrder > 0)
{
using (System.Transactions.TransactionScope transactionScope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required))
{

#region 更新考试任务
foreach (var examTaskInfo in ExamTaskList)
{
examTaskInfo.ExamTask_BatchOrder = 0;
examTaskInfo.Update();
}
#endregion

#region 删除批次
exam_Batch batch = ConvertDBModel();
_DBControl.ExamBatch_Delete(batch);
#endregion

transactionScope.Complete();
}
}
else
throw new Exception("不存在批次,无法重置!");
}


从上述代码中可以看到,在 Reset()方法中,有两个操作,examTaskInfo.Update();是对另一个业务类进行的操作,_DBControl.ExamBatch_Delete(batch);是对本业务类进行操作。
下面是业务类B

public class ExamTaskInfo
{
DataBaseControl _DBControl = DataBaseControl.getSingleton();

#region 属性
public string ExamTask_YearTermID { get; set; }
public string ExamTask_CourID { get; set; }
public short ExamTask_CourOrder { get; set; }
public int ExamTask_BatchOrder { get; set; }
public string ExamTask_ArrangeType { get; set; }
public Enum_ExamTaskType ExamTask_ArrangeTypeEnum { get; set; }
public DateTime? ExamTask_GradeEndTime { get; set; }
public string ExamTask_Operator { get; set; }
public DateTime? ExamTask_LastDate { get; set; }
#endregion

#region 初始化
private void InitClass(exam_Task dbTable)
{
this.ExamTask_YearTermID = dbTable.YearTermID;
this.ExamTask_BatchOrder = dbTable.BatchOrder;
this.ExamTask_CourID = dbTable.CourID;
this.ExamTask_CourOrder = dbTable.CourOrder;
this.ExamTask_ArrangeType = dbTable.ArrangeType;
this.ExamTask_GradeEndTime = dbTable.GradeEndTime;
this.ExamTask_Operator = dbTable.Operator;
this.ExamTask_LastDate = dbTable.LastDate;
}
private exam_Task ConvertDBModel()
{
exam_Task task = new exam_Task();
task.YearTermID = ExamTask_YearTermID;
task.BatchOrder = this.ExamTask_BatchOrder;
task.CourID = ExamTask_CourID;
task.CourOrder = ExamTask_CourOrder;
task.ArrangeType = EnumUtil.GetEnumShowName(typeof(ClEas.Model.Exam.ExamTaskInfo.Enum_ExamTaskType), (int)ExamTask_ArrangeTypeEnum);
task.GradeEndTime = ExamTask_GradeEndTime;
task.Operator = ExamTask_Operator;
task.LastDate = ExamTask_LastDate;
return task;
}
public ExamTaskInfo()
{
this.ExamTask_YearTermID = string.Empty;
this.ExamTask_BatchOrder = 0;
this.ExamTask_CourID = string.Empty;
this.ExamTask_CourOrder = 0;
this.ExamTask_ArrangeTypeEnum = Enum_ExamTaskType.SchoolType;
this.ExamTask_GradeEndTime = null;
this.ExamTask_Operator = string.Empty;
this.ExamTask_LastDate = DateTime.Now;
}

public ExamTaskInfo(exam_Task dbTable)
{
if (dbTable != null)
{
InitClass(dbTable);
}
}

#endregion

#region 方法

public void Update()
{
exam_Task task = ConvertDBModel();
_DBControl.ExamTask_Update(task);
}
#endregion

}


总之,就是两个业务类在实例化的过程中,分别需要使用数据库,如果每次NEW则会出现使用存储过程时造成线程不一致,所以不得不使用单例模式解决问题。

但如果单例模式,则会出现下面的问题,不知道是否是我的问题,回滚时无法执行,导致上下文数据存在。
代码如下:

using (System.Transactions.TransactionScope transactionScope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required))
{
foreach (System.Data.DataRow dr in dt.Rows)
{
//这里将来切换为MODEL,不应该直接读取数据库
string studid = dr["学号"].ToString().Trim();
string name = dr["姓名"].ToString().Trim();
string grade = string.Empty;
if (dt.Columns.Contains("成绩"))
{
grade = dr["成绩"].ToString().Trim();
}

e_Student student = _DBControl.Student_GetInfo(studid);
if (student != null && student.StudName == name)
{
ECItemTempGradeInfo tempGrade = new ECItemTempGradeInfo();
tempGrade.ECGroupID = ECGroupID;
tempGrade.StudID = studid;
tempGrade.ECGrade = grade;
tempGrade.Operator = Operator;
tempGrade.Insert();
}
else
throw new Exception(studid + "学号与姓名无法对应!请检查是否有误!");
}
SelectedNum = _DBControl.ECItemGrade_GetList(ECGroupID).Count();
Update();
}

当这句执行时throw new Exception(studid + "学号与姓名无法对应!请检查是否有误!");,抛出错误,可以看到数据库确实没有变化 ,但是上下文却发生了变化,再次调用本函数时,就会发生主键冲突的错误。原因是因为tempGrade.Insert()时 使用AddObject,将该对象添加进了上下文。而回滚似乎是不回滚上下文的,这样就导致了数据库与上下文不一致的现象。


求教大神,该如何解决这个问题,到底是应该单例模式还是不应该单例,如果应该单例的话,如何解决上下文的问题?
...全文
332 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
你的 BLL 代码中必定会调用你的 DAL 概念中的对象和接口。既然你在设计业务逻辑时口口声声地不离“事务”概念,那么你的 BLL 必定要用它设计和实现,你必定要在 BLL 中声明某某几部操作是同一个“事务”对象关联而来的。而 static (甚至单例概念)反而不是正路。 至于说你认为“我所有的方法是不是就必须痛苦地修改一遍”,那是你 DAL 设计问题。不能放弃大原则,而保存你的 DAL。
xuzuning 2016-09-13
  • 打赏
  • 举报
回复
单例模式只在对象(方法)间隐式传递数据时使用 其实这与显式的将数据库对象作为参数传递是一样的 如果含有事务,那么事务的实现应在数据库对象中,而非使用者
  • 打赏
  • 举报
回复
在2003、2004年左右的时候,微软 asp.net 的旁门左道的“关系”项目组几个人,模仿 java 中开始流行的一个商店网站程序,而编写了 PetShop 示例程序,用来说明“asp.net 的性能并不比 jsp 差”。那是有明确的目的的,那时候就是模范 java 开源代码。 但是数据处理层框架(DAL)就应该如何设计吗?难道这样的 DAL 不令人感到“繁琐、痛苦”吗? 你显然还是在模仿过去的那种东西。许多asp.net 程序员是因为使用“生成器”所以才习惯于那些。那么就继续使用生成器,绑死在生成器上一辈子都不要重构,自己不要再来改写 DAL 层去设计什么“事务”啊。
  • 打赏
  • 举报
回复
引用 3 楼 yixian2007 的回复:
你的意思是我所有的方法都要传递一个“事务”对象吗?
你“所有的方法”,难道你的DAL就是只有“增删改”方法,那么你还不如直接用 ADO.NET 的抽象层。这就是典型地被“为了三层而三层”的做法绑架了结果。你直接使用 ADO.NET 或者 EF、ORM 等等,不要再写什么“DAL方法”,那对你来说没有意义,因为你认为“所有方法都要传递一个事务”,你把最底层的“增删改”方法弄到了高层来并且重复冗余复制了几百遍(原本通用地针对几百个Model只要使用一套DAL方法就够了,而你现在弄成几百套DAL方法),自然处处繁琐处处不敢抽象设计。
  • 打赏
  • 举报
回复
比如说(只是随便举例),你可以设计一个接口
public interface IMyDbTransaction
{
    MyDbConnection Connection{get;}
}

public interface IMyDbConnection
{
    IMyDbTransaction BeginTransaction();
}
前边的接口定义了一个“事务”,这个事务可以被从你的数据库连接而创建,也可以用来创建一个数据库连接。 实际上,假设你确定在6个月内并不会重构,那么你甚至根本不用自己定义接口,而是使用 ADO.NET 或者 EF 等等的现成的类型或者接口用在你的 BLL 层编程中,也是可以的。 程序代码的重构是永恒的,保持静止在一个“阶段”之内才是必须的。无论如何你定义了自己的抽象“事务”,早晚有一天都要重构。我可以一天之内修改500个重构时的核心架构的bug,而并不担心第二天修改好之后的系统运行结果不可靠,因为对我来说保证稳定性的关键不在于僵化教条的文档、而在于高强度的测试驱动开发工程方法。 基于此,你就会不会过度洁癖地要求程序设计必须考虑到6个月以后的架构演进细节,只要保持眼前的框架的不重复过去的失误,就可以了。
yixian2007 2016-09-13
  • 打赏
  • 举报
回复

public IQueryable<exam_Room> ExamRoom_GetList(TeachEasEntities db)
        {
            return db.exam_Room;
        }
比如这样的?
yixian2007 2016-09-13
  • 打赏
  • 举报
回复
引用 1 楼 sp1234 的回复:
[quote=引用 楼主 yixian2007 的回复:]如果不单例数据库的话,业务类A实例化时,需要业务类A内部读取数据库数据并实例化,然后业务类B实例化时,需要业务类B内部读取数据库并实例化,那么这两个业务类是分别实例化的,自然也是分别两个数据库连接,
这是你的DAL设计有问题。当你初始化两个实例之前,自然就先要创建一个“事务”对象,然后把这个对象传入随后的两个对象。 说到“业务类实例”,这就让人不得不提起“为了三层而三层”的相当垃圾的一些传统的做法的毒害来。你这里一方面在口口生生地说“两个业务类实例化”,而另一方面一是不是在业务逻辑上就已经在纠结关系数据库的 db transaction 概念呢?那种“为了三层而三层”的说法,很高大上地说什么“业务层不涉及数据库事务”,那么你能做到吗? 实际上,将“事务”概念或者接口放到业务逻辑层,是必须的,是绕不开的。那些认为业务层不调用DAL层对象做法,或者只会抄袭一些“生成器”所产生的不包含“事务类对象实例”的代码而不敢自己设计DAL层的做法,才会以为只能用static方法否则就没法承载事务了。[/quote] 说实话,这个我已经纠结了很久了,还是不明白大师们的意思。 你的意思是我所有的方法都要传递一个“事务”对象吗?
  • 打赏
  • 举报
回复
你的业务逻辑层必须有相当于你的关系数据库的事务的机制,不可能不涉及事务对象。 这个问题解决了,什么“单例”也就自动消失了。
  • 打赏
  • 举报
回复
引用 楼主 yixian2007 的回复:
如果不单例数据库的话,业务类A实例化时,需要业务类A内部读取数据库数据并实例化,然后业务类B实例化时,需要业务类B内部读取数据库并实例化,那么这两个业务类是分别实例化的,自然也是分别两个数据库连接,
这是你的DAL设计有问题。当你初始化两个实例之前,自然就先要创建一个“事务”对象,然后把这个对象传入随后的两个对象。 说到“业务类实例”,这就让人不得不提起“为了三层而三层”的相当垃圾的一些传统的做法的毒害来。你这里一方面在口口生生地说“两个业务类实例化”,而另一方面一是不是在业务逻辑上就已经在纠结关系数据库的 db transaction 概念呢?那种“为了三层而三层”的说法,很高大上地说什么“业务层不涉及数据库事务”,那么你能做到吗? 实际上,将“事务”概念或者接口放到业务逻辑层,是必须的,是绕不开的。那些认为业务层不调用DAL层对象做法,或者只会抄袭一些“生成器”所产生的不包含“事务类对象实例”的代码而不敢自己设计DAL层的做法,才会以为只能用static方法否则就没法承载事务了。
吉普赛的歌 2016-09-13
  • 打赏
  • 举报
回复
不用搞什么单例双例, 弄个一次只允许一个线程使用的存储过程即可。非常简单
yixian2007 2016-09-13
  • 打赏
  • 举报
回复
引用 9 楼 sp1234 的回复:
你的 BLL 代码中必定会调用你的 DAL 概念中的对象和接口。既然你在设计业务逻辑时口口声声地不离“事务”概念,那么你的 BLL 必定要用它设计和实现,你必定要在 BLL 中声明某某几部操作是同一个“事务”对象关联而来的。而 static (甚至单例概念)反而不是正路。 至于说你认为“我所有的方法是不是就必须痛苦地修改一遍”,那是你 DAL 设计问题。不能放弃大原则,而保存你的 DAL。
不明白。看不懂! 举一个实际的例子如何?

111,097

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧