ADO.NET Entity Framework 在哪些场景下使用?
orm一开始只是mapping,最基础的就是表与类的对应、column和属性的对应,这只是最基础的。在这个层次上,数据库对象通过mapping在面向对象语言层面,也就是业务层面被封装成了业务对象,然后允许以操作业务对象的方式对数据库进行操作。
但是,在很长时间里,orm的提升都被对象与关系间的“阻抗失配”困扰。一直以来很多orm的水平都只是维持在了用对象的方式进行crud而已,除了减少代码错误、提高简单查询的开发效率,在复杂查询、性能等等一些方面结果都还是要跨层回到底层的操作框架比如ado.net甚至存储过程去解决问题。
所以,在应用场景上来说简单查询的场景ef和其他orm都是能够胜任的。
从应用场景说起,这点在b/s和c/s里也会很明显。用户使用web的时候和使用桌面软件的最大体验不同是什么?——所见即所得。你在网页上操作了半天,一个关闭就全没了,还必须提交然后获得下一个页面才能把数据状态和ui更新;而同样在桌面上,你的操作比如画图,在操作的一瞬间结果就出来了。当然了,web 2.0技术就在解决这个问题。
同样在oo和rdbms中的问题也在这里。
从oo的角度上看,你运行下一段代码结果如何:
user.name = "indream luo";
在oo里,就是user对象的name属性被更新了。如果是一个桌面软件,那么用户的名称应该也更改了。
但是如果这个对象的数据是存在关系数据库中,或者任意数据库,那么结果都逃脱不出这个套路:
var object = db.get(id);
change(ref object);
db.update();
你需要把更新push过去,将操作和数据持久化。
在存储分层开始,推送更新就不可避免,哪怕在说面应用中,也是将对象的更新推送到了ui。orm站立在应用场景不一致中间所要扮演的角色,就是一个润滑剂的角色。
在我所能马上想起的特性,就是ef和nhibernate的缓存机制。ef是一级缓存,nh是二级缓存,手动的话似乎ef也可以做到二级。然后在.net下最重要的一点是有linq。linq在有合适的provider的情形下可以把oo的序列化操作转化成目标的序列化操作,在这里就是linq转sql,这样就省去了拼接sql、sql注入等很多麻烦。另外linq延迟加载的特性也很大地减少了用户控制sql执行的工作。
在操作同步的基础上,还有结构同步的问题。表结构和对象结构同步是使用orm一大工作内容。ef有默认的生成工具,db first、model first、code first三种模式提供选择,加上自动生成同步sql,选择性是现在最广的,nh也有一些相应的生成器,数据库优先方面小弟linq to sql的拖拽最惊艳。
在这个场景下,加上对相关工具的利用,ef等orm适用于序列操作、减少数据库操作管理和结构同步工作量,减少开发成本。
最后,不可回避的就是阻抗失配的问题。
对象关系模型和关系数据库模型在以前很大程度上不一致,这是在以前。现在orm要做的就是如何让两者更接近,让一边的特性能更顺滑地体现在另一边。
我 在早几个月写过一篇总结,关于最近一个项目ef使用的一些方法——《entity framework 与 面向对象》。太长就选重点来说明。
ef所做的涵盖:类型匹配、对象结构、数据源区分。
类型匹配方面,就是把oo类型和数据库类型进行匹配,这是orm的基础。基础类型中的整数、浮点、字符串、日期这些不在话下,ef比较有特点的可能是枚举类型(enum)、复杂类型(complex type)、地理位置的功能,实现方式也比较理想。
对象结构方面是ef让我最惊艳的地方。ef的model,也就是entity能实现集成关系,也可以通过此同步在表结构中;ef中通过对外键的控制,对引用和依赖关系实现得十分出色;支持虚类、对象层面的get/set、访问控制都很好用。
插一段,通过使用model first,我倒是发现数据库的设计更加接近于范式了。因为linq和对象结构方面带来的便利,我可以把表结构设计得更“合理”。比如如果要获取user的上司的上司,假设每个user都只有一个上司,那么用ef或者一些orm就是:
var bigboss = user.superior.superior;
如果要写sql,感觉有点烦,算代码量和可读性已经能看出区别了。
最后是数据源区分。有一个问题是一个对象,它的数据不一定完全源于数据库,或者一个数据库,这个例子我常用。
比如user有三个字段:firstname、lastname、fullname。可以知道fullname其实就是firstname和lastname的拼接,如果创建model/entity,一般:
复制代码
public partial class user
{
public string firstname { get; set; }
public string lastname { get; set; }
public string fullname
{
get
{
if (fullname == null)
{
this.fullname = string.format("{0} {1}", this.firstname, this.lastname);
}
return this.fullname;
}
} string fullname;
}
复制代码
而在数据库里,只需要存储firstname和lastname,fullname作为计算值就可以了,而且还是延迟加载的。
更甚者,我们还可以从上面的例子延伸,在entity中封装一些数据库操作:
复制代码
public partial class user
{
public user superior { get; set; }
public user bigboss
{
get
{
return this.superior.superior;
}
}
}
复制代码
此时在数据库中只存储一个superior关系即可,bigboss作为计算值就可以了。当然,你乐意还可以缓存和延迟加载,但ef已经处理了缓存了。
极端情况,确实是可以在表关系中“玩”不少面向对象的设计模式。
终上所述,ef适用于面向对象结构和特性优先性比较高的 场景。
那么相对地,也说说不适用的场景。
首先大家所诟病的是性能问题,这点希望不要抛开原理去说ef的性能。
ef由于其执行原理,性能损耗一般发生在:
linq也就是expression tree创建和转换成sql的过程
缓存比对的过程
特殊操作实现不合理
极限性能压力下的问题
性能泄露
1是不可避免的,2通过关闭比对或者缓存可以解决,3、4和5是主要问题。
特殊操作不合理举例来说,比如递归,获取一个树结构的一条索引链。如果是通过oo来做,那么就是要么要往返很多次数据库,要么要至少遍历一次对象表,要么就是要加一些特殊的“丑陋的”索引字段。
极限性能压力在包含上个问题的情况下扩展,比如sql server的存储过程执行的特殊操作是最快的,纯oo的方式肯定达不到。
这两项特殊项通过更原生的数据库方式去解决是最佳的解决方案。你可以和ef混用,也可以单独使用,但不要妄想着有银弹能同时解决所有问题。ef提供了sql的执行方式。
性能泄露不是一个专有名词,是我临时用的。意思是因为ef导致的不必要的性能浪费。特别是linq的延迟加载特性,许多不清楚linq特性的开发人员容易将linq序列无谓地实例化,浪费了资源。通常会是:
遍历查询全表数据, 然后再在oo层面进行筛选
无谓地执行实例化,进行查询,要么浪费缓存比对的资源,要么浪费查询资源
我只能说这是开发人员水平问题,虽然出现问题后很难定位,特别是一般情况下都会造成内存泄露。
最后最常见的还是回到数据模型同步的问题。当数据模型更改后,需要同步,这时候如果已经有业务数据了,是一件麻烦的事情。ef的migration我没用过,是一个解决方案但似乎不那么完美。在一些非orm应用的系统,sql集中管理架构下,在这个场景,可能会更容易进行维护。
上一篇: 教您如何解决交换机和路由器不通排障
下一篇: 说话要注意