ADO.NET Entity Framework快速入门
作者:胡文俊
EntityFramework是一个ORM框架,是linq to sql 的升级版本,linq tosql 微软已经不再更新。与linq to sql最大的区别是 linq to sql 只支持sqlserver.而 EntityFramework支持各种数据库。
1. 模型构建
EF 的模型构建有三种方式:分别为 Database First(逆向工程) ,Model First (正向工程1),Code First. (正向工程2)
我们只学习Database First。如需研究自行学习。数据仍为 hibernate中的那个数据库
首先确认vs是否带有EntityFramework框架。
新建一个控制台工程。在解决方案——工程名右键
查看是否存在 ADO.NET 实体数据模型。如果有请略过。
如果没有,进行如下操作。
指令:Install-Package EntityFramework
1.模型构建
请确保数据库sqlserver上有salary数据库
新建一个控制台工程,右键-》添加-》新建项-》ADO.NET 实体数据模型
选择从数据库生成,点击下一步。新建连接
点击测试连接成功则点击确定。失败请自行检查。
修改圈圈内的名字。此名字为此数据库的上下文类或者叫实体容器。之后的数据库增删改查均需要此类。下一步。
勾选所需的表。EF会自动生成对应的模型类。
此时可看到映射已经完成。属性名或类名如需修改可以直接在此设计器中完成。如需修改类名修改完毕请记得重新生成项目。
切记不要直接在类文件中修改。否则vs将无法进行联动更新,而且更新模型修改将会被覆盖。
打开所有折叠。可看到Model1.tt下就是我们的实体类。而实体容器就为SalaryDbContext.
Model1.edmx在默认情况下打开是打开设计器。实际上它是一个xml文件。我们在此文件上右键-》打开方式 选择xml。
SSDL定义了数据库中表的相关字段类型
CSDL定义了实体类的相关属性类型
C/Smapping 显然就是定义了上面两个的映射关系了。
模型设计器中可以看到每个模型有属性和导航属性。属性是指数据库中对应的列名的实体属性。而导航属性是由外键生成的实体关联对象。我们将类名大写.
从此源码可以发现 SalaryDbContext中有着我们之前选择的三张表所对应的的实体类集合。
至此。模型的构建就讲述完毕。
2.增删改查样例
1)添加(观察区别)
public void addTeahcer(Teacherinfo teacher){
SalaryDbContext context = new SalaryDbContext();
context.teacherinfos.Add(teacher);
context.SaveChanges();
context.Dispose();
}
public void addFeature(Feature feature)
{
SalaryDbContext context = new SalaryDbContext();
context.features.Add(fea);
context.SaveChanges();
context.Dispose();
}
2)删除
public void delete(Teacherinfo teacher)
{
SalaryDbContext context = new SalaryDbContext();
teacher = context.teacherinfos.Find(teacher.tno);
context.teacherinfos.Remove(teacher);
context.SaveChanges();
}
或者可以这样
public void delete2(Teacherinfo teacher)
{
SalaryDbContext context = new SalaryDbContext();
context.Entry(teacher).State = EntityState.Deleted;//将对象标记为删除状态
context.SaveChanges();
}
3)更新
public void update(Teacherinfo teacher){
SalaryDbContext context = new SalaryDbContext();
//context.teacherinfos.Attach(teacher);
context.Entry(teacher).State = EntityState.Modified;//告诉EF它是一个修改对象
context.SaveChanges();
context.Dispose();
}
也可以这样更新
public void update2(Teacherinfo teacher)
{
SalaryDbContext context = new SalaryDbContext();
var t = context.teacherinfos.Find(teacher.tno);
t.title = "超级讲师";
context.SaveChanges();
}
4)查询
public Teacherinfo query(Object teacherid)
{
SalaryDbContext context = new SalaryDbContext();
Teacherinfo teacher = context.teacherinfos.Find(teacherid); //根据主键正在tercherinfos中找到对应的对象
context.Dispose();
return teacher;
}
或者可以这样 linq to entity
public Teacherinfo query1(Object teacherid)
{
String tno = (String)teacherid;
SalaryDbContext context = new SalaryDbContext();
Teacherinfo teacher = (from t in context.teacherinfos where t.tno==tno select t).Single() ;//根据主键正在tercherinfos中找到对应的对象
context.Dispose();
return teacher;
}
又或者结合lambda
public Teacherinfo query2(Object teacherid)
{
String tno = (String)teacherid;
SalaryDbContext context = new SalaryDbContext();
Teacherinfo teacher= context.teacherinfos.Where(t => t.tno == tno).Single();
context.Dispose();
return teacher;
}
返回对象集合
public List<Teacherinfo> queryAll()
{
SalaryDbContext context = new SalaryDbContext();
List<Teacherinfo> teachers = context.teacherinfos.ToList();
return teachers;
}
根据条件获取满足的对象集:
public List<Teacherinfo> querywith()
{
SalaryDbContext context = new SalaryDbContext();
var result = from teacher in context.teacherinfos where teacher.sex == "女" orderby teacher.teachername descending select teacher;
List<Teacherinfo> teachers = result.ToList();
return teachers;
}
1)分页查询;
public List<Teacherinfo> querywithpage()
{
SalaryDbContext context = new SalaryDbContext();
var result = (from teacher in context.teacherinfos orderby teacher.tno select teacher).Skip(0).Take(5); //表示从结果集的第0个对象开始,包括自己往后的五个对象一起返回
List<Teacherinfo> teachers = result.ToList();
return teachers;
}
2)联合查询
,如果我们需求为 给指定的教师编号 打印出 教师名称 教师的特征名 和教师该特征的特征值。此时我们需要查询三张表。而DbContext默认为开启懒加载。故在dispose方法调用完毕之后将无法在进行数据库的查询。如果急切的要求立即获取数据。则关闭懒加载。并将所有数据准备好。如果只是使用Teacherinfo teacher = (from t in context.teacherinfos.Include("teacherfeature") where t.tno == tnoselect t).Single();只是寻找了两张表 所以无法满足我们的需要。
public Teacherinfo query1(Object teacherid)
{
String tno = (String)teacherid;
SalaryDbContext context = new SalaryDbContext();
context.Configuration.LazyLoadingEnabled = false;//关闭懒加载
Teacherinfo teacher = (from t in context.teacherinfos
where t.tno == tno select t).Single();//根据主键正在tercherinfos中找到对应的对象
List<TeacherFeature> tfs =( from tf in context.teacherfeatures.Include("feature").Include("teacherinfo") where tf.tno == tno select tf).ToList();// include可以在在查询数据库时将导航属性一并加载
teacher.teacherfeature = tfs;
context.Dispose();
return teacher;
}
3).EF调用存储过程
1)假如我们需要调用一个存储过程获取teacherinfo表中的记录条数,存储过程代码如下:
create procgetCount as
begin
select COUNT(*) fromteacherinfo
end
在模型设计器的空白地方点击 从数据库更新模型
这里可以看到我们刚刚写的存储过程已经检测到了,点击完成。
此时在模型浏览器里面可以看到 此存储过程已经导入模型。
并且函数导入下也有了该存储过程的映射,我们双击此项。
,此时可以看到对应的映射了。函数导入名称就是我们在前面提到的上下文中直接调用的方法名称,我们可以修改名称。也可以修改返回值。由我们所写的存储过程可知返回的是一个int而已。故我们选择int32.。此时我们就可以调用一个方法了吗?像int32 context.getCount();这样。然而当我们直接这样做的时候会发现,并没有对应的方法。那是因为我们只是模型设计器修改了 。相关的映射配置文件并没有修改cs代码。故我们重新生成我们的工程。
此时我们可以看到我们要的方法了但是返回值并不是我们选择的Int32. 我们回过头发现原来他返回的就是一个集合。我们可以这样测试获取所需数据。
public int getCount()
{
SalaryDbContext context = new SalaryDbContext();
ObjectResult<int?> or = context.getCount();
int? count = or.First();//我们可以确定他只会返回一个整数,所以取第一个即可。
context.Dispose();
return (int)count;
}
2)例子2;清空三张表数据。我们只需要在选择返回值时选择无即可。
create proccleartables
as
begin
begin transaction
delete fromteacherfeature;
delete fromteacherinfo;
delete fromfeature;
commit transaction
if(@@ERROR<>0)
rollback transaction;
end
3)例子3:根据性格特征id查出特征值最大的老师姓名和特征值。
create procgetMaxdegreeTf(@featureidchar(2))
as
begin
select t.teachername,tf1.degreefrom teacherfeaturetf1
join teacherinfot on tf1.tno=t.tno
where tf1.degree=
(selectMAX(degree)from teacherfeaturetf2 where feature_id=@featureid);
end;
在本例子中我们的映射参数为featureid。然而返回值是两个值:teachername,和degree。并不属于以上的标量或者实体。这里会被识别为复杂类型。
public virtual ObjectResult<TeachernameWithDegree> getMaxdegreeTf(string featureid)
{
var featureidParameter = featureid != null ?
new ObjectParameter("featureid", featureid) :
new ObjectParameter("featureid", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<TeachernameWithDegree>("getMaxdegreeTf", featureidParameter);
}
如第一个例子所述。默认情况下复杂类型的名字为方法名。我们将复杂类型修改一下:TeachernameWithDegree。该方法则返回了这个复杂类型的集合。
测试代码:
//存储过程调用示例
public List<TeachernameWithDegree> getMaxDegreeTF(String featureid)
{
SalaryDbContext context = new SalaryDbContext();
ObjectResult<TeachernameWithDegree> or = context.getMaxdegreeTf(featureid);
List<TeachernameWithDegree> list = or.ToList();
context.Dispose();
return list;
}
2.Ef使用sql
//使用ddl/dml sql
public void execInsertSql(Feature feature)
{
SalaryDbContext context = new SalaryDbContext();
string sql = @"insert into feature values({0},{1})";
context.Database.ExecuteSqlCommand(sql,feature.feature_id,feature.feature_name);
}
//使用ddl/dml sql
public List<Teacherinfo> execSelectSql(string id)
{
SalaryDbContext context = new SalaryDbContext();
string sql = @"select * from teacherinfo where tno={0}";
string[] paras = {id };
List<Teacherinfo> teachers= context.Database.SqlQuery<Teacherinfo>(sql, paras).ToList();
context.Dispose();
return teachers;
}
2.Ef之事务支持
EF的事务需要如上的dll,我们先引入。
//事务
public void doTrasaction()
{
Feature feature = new Feature();
feature.feature_id = "12";
feature.feature_name = "无趣";//此时这个feature是可以正常插入的
AddFeature(feature);
feature = new Feature();
feature.feature_id = "01";//然而在这里 由于主键约束,这是插不进去的
feature.feature_name = "可笑";
AddFeature(feature);
}
public void AddFeature(Feature feature)
{
SalaryDbContext context = new SalaryDbContext();
context.features.Add(feature);
context.SaveChanges();
context.Dispose();
}
EF本身每一个上下文都已经具备一个隐藏内置事务对象,即同一个上下文下,如果出现错误,是可以自动回滚的。然而事务一般出现在业务逻辑层,而上下文出现在数据访问层。即,同一个业务处理模块,可能出现不同的上下文对象。因为我们的每个数据访问操作都会new一个上下文对象,在执行完毕会被释放。因此,各个数据访问操作相对独立。此时,我们在执行doTrasaction()方法时,将会第一条插入成功。而第二条插入失败,显然不符合事务的要求。
.net为我们提供了这样一种方式
public void doTrasaction()
{
using(TransactionScope scope = new TransactionScope())
{
Feature feature = new Feature();
feature.feature_id = "12";
feature.feature_name = "无趣";//此时这个feature是可以正常插入的
AddFeature(feature);
feature = new Feature();
feature.feature_id = "01";//然而在这里 由于主键约束,这是插不进去的
feature.feature_name = "可笑";
AddFeature(feature);
scope.Complete();
}
}
如上所做,将要执行的事务块放入using(TransactionScope scope = new TransactionScope())
{
//事务代码块
}
此时就已经具备了事务的能力。简单,方便。TransactionScope 在之前我们导入的System.Trasactions命名空间下。
小贴士:
Sqlserver 存储过程事务的支持
Create PROCEDURE demo
As
Begin
Begin Transaction
Insert Into Lock(LockTypeID) Values('A')--此语句将出错,LockTypeID为Int类型 ;
Update Lock Set LockTypeID = 1 Where LockID =32 ;
Commit Transaction
If(@@ERROR <> 0)
Rollback Transaction
End
GO
上面一个存储过程中黄色的部分为事务的支持方式。
至此我们的入门教程结束。
博客排版不怎么好 ,附上 学习工程demo,数据库和完整文档,写教程不易无耻要两分。O(∩_∩)O哈哈哈~点击打开链接