Entity Framework 4.1 DbContext使用记之三——如何玩转实体的属性值?

之前的两篇有关EF4.1的文章反响不错,感谢大家的支持!想体验EF4.1的新功能?RTW版本已经发布啦http://www.microsoft.com/downloads/en/details.aspx?FamilyID=b41c728e-9b4f-4331-a1a8-537d16c6acdf&displaylang=en   

Entity Framework 4.1 DbContext使用记之一——如何查找实体? DbSet.Find函数的使用与实现

Entity Framework 4.1 DbContext使用记之二——如何玩转本地实体? DbSet.Local属性的使用与实现

 

今天的主题是如何玩转EF4.1中实体的属性。实体的属性其实是我们使用EF来访问和修改实体的关键。在EF以前版本中,如果我们一般会直接访问对象的属性,比如得到PersonID大于100的所有Person实体的ID和Name:

using (var context = new MyContext())
{
    var people 
= context.People.Where(p => p.PersonID > 100);
    
for (var p in people)
    {
        Console.WriteLine(
"ID: {0}, Name:{1}", p.PersonID, p.Name);
    }

但是如果要得到ObjectContext所track的实体属性的OriginalValues(原始值)和CurrentValues(当前值),则不是很方便。

 

而在EF4.1中,由于我们可以使用DbContext.Entry()或DbContext.Entry<T>()来得到DbEntityEntry或DbEntityEntry<T>对象。通过DbEntityEntry (DbEntityEntry<T>)的OriginalValues和CurrentValues属性,我们可以方便地得到相应的属性集合(DbPropertyValues)。通过DbEntityEntry (DbEntityEntry<T>)的Property(Property<T>)方法得到DbPropertyEntry(DbPropertyEntry<T>)之后,我们也能通过相应的OriginalValue和CurrentValue属性得到单个属性的原始值和当前值。感觉有点绕?看看下面的这些例子应该就简单明了多了! 

using (var context = new MyDbContext())
{
    
// 有关Find方法,请看EF4.1系列博文一
    var person = context.People.Find(1);

    
// 得到Person.Name属性的当前值
    string currentName = context.Entry(person).Property(p => p.Name).CurrentValue;

    
// 设置Person.Name属性的当前值
    context.Entry(person).Property(p => p.Name).CurrentValue = "Michael";

    
// 通过string值"Name"来获得DbPropertyEntry,而非通过Lambda Expression
    object currentName2 = context.Entry(person).Property("Name").CurrentValue;
}

 

再来看看如何得到实体的所有属性的OriginalValue(原始值),CurrentValue(当前值)和DatabaseValue(数据库值)吧:

using (var context = new MyDbContext())
{
    var person 
= context.People.Find(1);

    
// 改变对应的实体的Name属性
    person.Name = "Michael";

    
// 改变对应属性的数据库值
    context.Database.ExecuteSqlCommand("update Person set Name = 'Lingzhi' where PersonID = 1");

    
// 输出对应实体所有属性的当前值,原始值和数据库值
    Console.WriteLine("Current values:");
    PrintValues(context.Entry(person).CurrentValues);

    Console.WriteLine(
"\nOriginal values:");
    PrintValues(context.Entry(person).OriginalValues);

    Console.WriteLine(
"\nDatabase values:");
    PrintValues(context.Entry(person).GetDatabaseValues());
}

这里用到的PrintValues函数实现如下:

public void PrintValues(DbPropertyValues values)
{
    
foreach (var propertyName in values.PropertyNames)
    {
        Console.WriteLine(
"Property {0} has value {1}",
                          propertyName, values[propertyName]);
    }
}

这里具体的输出大家就当小练习吧,呵呵。

 

下面再为大家介绍两个我个人认为比较cool的小功能:

1) 设置多层的复杂类型的属性。

例如,Person实体含有Address复杂类型属性(Complex - type),Address又含有City这一string类型属性,那么我们可以这样来获得City这一属性的当前值:

// 使用Lambda Expression
string city = context.Entry(person).Property(p => p.Address.City).CurrentValue;

// 使用string类型的属性表达
object city = context.Entry(person).Property("Address.City").CurrentValue;

这里层次的次数其实没有限制,可以一直点下去的。:)   EF的内部实现也是使用递归调用的方式,之后会有详细的介绍。 

 

2) 克隆含有实体属性当期值,原始值或数据库值的对象。

我们可以使用DbPropertyValues.ToObject()方法来克隆将DbPropertyValues中相应的属性和值变成对象。

using (var context = new MyDbContext())
{
    var person 
= context.People.Find(1);
    var clonedPerson 
= context.Entry(person).GetDatabaseValues().ToObject();

这里克隆到的对象不是实体类,也不被DbContext所跟踪。但这样的对象在处理数据库并发等问题时会很有用。


又到了探寻以上介绍的功能的内部实现的时候啦!我们还是照例使用.NET Reflector来查看EntityFramework.dll。 大家可以打开Reflector和我一起来做个简单的分析。


首先是从DbContext.Entry方法得到DbEntityEntry。Entry方法通过调用DbEntityEntry internal的构造函数DbEntityEntry(InternalEntityEntry internalEntityEntry)来创建一个DbEntityEntry对象。这里的InternalEntityEntry又是通过DbContext.InternalContext和先前传入Entry函数的实体对象来生成的。

public InternalEntityEntry(InternalContext internalContext, object entity)
{
    
this._internalContext = internalContext;
    
this._entity = entity;
    
    // ObjectContextTypeCache应该是EF内部保存的静态的类型缓存,用于查找实体类型
    this._entityType = ObjectContextTypeCache.GetObjectType(this._entity.GetType());
    
    // InternalContext.GetStateEntry内部则调用了ObjectStateManager.TryGetObjectStateEntry方法
    this._stateEntry = this._internalContext.GetStateEntry(entity);
    
if (this._stateEntry == null)
    {
        
this._internalContext.Set(this._entityType).InternalSet.Initialize();
    }
}

 

下面来看看DbEntityEntry.CurrentValues/OriginalValues。CurrentValues和OriginalValues属性都是DbPropertyValues类型的。DbPropertyValues可以被理解为对于一个实体或复杂类型所有属性信息的集合。我们通过PropertyNames属性拿到其所有属性的名字,通过GetValue或SetValues方法得到或设置属性值。还能通过我们之前讨论的ToObject方法来克隆一个有用与对应实体或复杂类型对象一样属性值的对象。演示一下如何使用DbPropertyValues.SetValuesF方法:

using (var context = new MyDbContext())
{
    var person 
= context.People.Find(1);
    var person1 
= new Person { PersonID = 1, Name = "Michael" };
    var person2 
= new Person { PersonID = 1, Name = "Lingzhi" };

    var entry 
= context.Entry(person);
    
    
// 这里直接将拥有相应属性值的实体对象直接赋给SetValues方法,直接对person实体的CurrentValues和OriginalValues进行赋值
    entry.CurrentValues.SetValues(person1);
    entry.OriginalValues.SetValues(person2);
}

 

这里SetValues内部首先调用了DbHelpers.GetPropertyGetters方法。DbHelpers是System.Data.Entity.Internal命名空间下Internal的类,包含了许多静态的辅助方法。这里的GetPropertyGetters顾名思义就是得到属性Getter方法delegate的集合,内部当然是通过PropertyInfo以及.NET Reflection来完成。有了这个Getter方法delegate的集合,我们就能方便地拿到传入SetValues方法的对象的所有属性值了,最后则按照所得到的属性值来进行赋值。

 

接着我们再来看看如何从DbEntityEntry.Property得到DbPropertyEntry。我们可以传递两种property的表达方式给DbEntityEntry.Property方法:1) Lambda Expression   2) string类型的property表示。先来说说1),在将Lambda Expression解析为对应property时,EF使用了辅助静态方法DbHelpers.ParsePropertySelector,ParsePropertySelector又调用了另一静态辅助方法DbHelpers.TryParsePath。具体算法在这里就不做深入解析,简单说这个TryParsePath方法就是递归地将Lambda Expression所表示的property层层深入地获取到,例如像这样的Lambda Expression:person => person.Address.City最后就变为"Address.City"。在解析完毕之后,ParsePropertySelector其实也是将Lambda Expression变成了string类型的property表示。自然,EF此时就调用了第二个接受string类型的property表示的DbEntityEntry.Property重载。DbEntityEntry.Property(string)在经过了一系列其他的内部调用之后,到了System.Data.Entity.Internal.InternalEntityEntry.Property(...):

private InternalPropertyEntry Property(InternalPropertyEntry parentProperty, string propertyName, IList<string> properties, Type requestedType, bool requireComplex)
{
    
bool flag = properties.Count > 1;
    Type type 
= flag ? typeof(object) : requestedType;
    Type declaringType 
= (parentProperty != null? parentProperty.EntryMetadata.ElementType : this.EntityType;
    PropertyEntryMetadata metadata 
= this.ValidateAndGetPropertyMetadata(properties[0], declaringType, type);
    
if ((metadata == null|| ((flag || requireComplex) && !metadata.IsComplex))
    {
        
if (flag)
        {
            
throw Error.DbEntityEntry_DottedPartNotComplex(properties[0], propertyName, declaringType.Name);
        }
        
throw (requireComplex ? Error.DbEntityEntry_NotAComplexProperty(properties[0], declaringType.Name) : Error.DbEntityEntry_NotAScalarProperty(properties[0], declaringType.Name));
    }
    InternalPropertyEntry entry 
= (InternalPropertyEntry) metadata.CreateMemberEntry(this, parentProperty);
    
if (!flag)
    {
        
return entry;
    }
    
return this.Property(entry, propertyName, properties.Skip<string>(1).ToList<string>(), requestedType, requireComplex);

从标黄的语句中不难发现,这个函数也是递归调用,并且将多层的property一一解析。 System.Data.Entity.Internal.InternalEntityEntry.Property函数返回InternalPropertyEntry类型的对象。这个对象又被返回给DbPropertyEntry.Create方法,Create方法又调用了InternalPropertyEntry.CreateDbMemberEntry:

 

public override DbMemberEntry<TEntity, TProperty> CreateDbMemberEntry<TEntity, TProperty>() where TEntity: class
{
    
if (!this.EntryMetadata.IsComplex)
    {
        
return new DbPropertyEntry<TEntity, TProperty>(this);
    }
    
return new DbComplexPropertyEntry<TEntity, TProperty>(this);
}

 

这里的逻辑很简单,将property分成一般的属性或复杂类型的属性,分别处理之。分析到这里,大家应该也发现了DbPropertyEntry中大部分信息都保存在其内部的InternalPropertyEntry对象里。这样的设计在解析EntityFramework.dll时,我们会经常看到。

 

呼!这篇文章不是一口气写完的了,这几天挺忙的,不过每天添几笔,可能思路有些混乱,大家见谅,哈哈!还是那句话,希望对您学习EF有点帮助吧!

 

PS1:这里为大家带来一个好消息:微软一站式实例代码库(Microsoft All-In-One Code Framework)即日起正式迁移至MSDN代码库了,新的平台会帮您更轻松地解决开发难题、节省更多时间、获得更友好的用户体验。本人作为这个项目的元老,见到我们已拥有600多个经典的代码实例,甚感欣慰啊!  更详细信息,请看http://msdn.microsoft.com/zh-cn/hh124104.aspx?ocid=ban-f-cn-loc-OC201104-MSDN

之后我将尽力为大家带来更多有关EF的代码实例以及相关的介绍!



PS2:同事开发了一个很cool的MSDN论坛桌面小工具,绝对给力!欢迎使用!(我也出了不少力啊


也欢迎到MSDN中文论坛ADO.NET与LINQ论坛来提问EF的问题啊,可以试试直接报我的名字Michael Sun,哈哈!

 

如需转发请注明原文出处,谢谢: http://www.cnblogs.com/LingzhiSun/archive/2011/04/13/EF41_WokingWithProperties.html

转载于:https://www.cnblogs.com/LingzhiSun/archive/2011/04/13/EF41_WokingWithProperties.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/462675.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[WorldWind学习]5.相机对象

首先查看WorldWindow的事件&#xff1a;OnMouseUp、OnMouseMove、HandleKeyDown&#xff0c;这几个方法中多次调用this.drawArgs.WorldCamera的各种属性实现了场景的控制&#xff0c;包括球的旋转、场景的放大缩小&#xff0c;上下移动。 1. 接下来查看CameraBase类RotationYaw…

MySQL中varchar(11)与int(11)的区别

结果&#xff1a; 对于varchar(11)&#xff1a;最多存储11个字符&#xff0c;超过则不存。 mysql> create table tt(c1 int primary key,c2 varchar(50))enginexxx; Query OK, 0 rows affected (0.15 sec)mysql> insert into tt values(1, aaaaaaaaaabbbbbbbbbbccccccc…

@Slf4j

注解Slf4j:&#xff08;当前日志为logback,其他日志框架不祥&#xff09; 直接使用log.xxxx("mothod is start") 例如&#xff1a;log.info("/returncode/add start"); 代替 如果不想每次都写 private final Logger logger LoggerFactory.getLog…

在VS2010开发的MVC3 应用程序中设定默认的浏览器

vs2010做mvc3 开发,用的是Razor的View,想修改默认浏览器,发现右键没有"浏览方式",把View改成.aspx的,也没有找到这个选项. 解决方法两种 (1)最简单的,建个Asp.net Web应用程序,在随便一个xxx.aspx页面,右键"浏览方式"即可.. (2)通过修改项目属性也可以,右键…

hdu 1161 Eddy's mistakes

http://acm.hdu.edu.cn/showproblem.php?pid1161 本题主要运用的就是大小写的转换&#xff1b; 我写的代码&#xff1a; #include<iostream>#include <string>#include <ctype.h>using namespace std;int main(int argc, char *argv[]){ string a; ch…

今年适合买房吗

本人是程序员&#xff0c;今天不聊程序相关的事情。不过今天有了点时间&#xff0c;考虑了下今年是否适合买房这件事。因为从中央到地方都在鼓励买房&#xff0c;每个人根据自己的实际情况决定是否要买房。 优点&#xff1a; (1)、房贷利率低&#xff0c;基本上是历史低点了 …

[18]Debian Linux Install GNU GCC Compiler and Development Environment

# apt-getinstall build-essential# gcc -v# make -v转载于:https://www.cnblogs.com/smartvessel/archive/2011/04/16/2018459.html

FireEye:2012年下半年高级威胁分析报告

最近&#xff0c;fireeye发布了2012年的高级威胁分析报告。根据对超过8900万获取的恶意代码事件进行分析&#xff0c;Fireeye认为&#xff1a; 1&#xff09;平均一个组织和单位每三分钟就会遭受一次恶意代码***&#xff0c;特指带有恶意附件、或者恶意WEB链接、或者CnC通讯的邮…

main()的参数argc与argv

C语言中的main()函数,一般会带有2个参数,例如int main (int argc, char* argv[]),这是一个典型的main函数的声明。 参数如下&#xff1a; argc: 整数, 为传给main()的命令行参数个数。 argv: 字符串数组。 在DOS 3.X 版本中, argv[0] 为程序运行的全路径…

数组中的forEach和map的区别

大多数情况下&#xff0c;我们都要对数组进行遍历&#xff0c;然后经常用到的两个方法就是forEach和map方法。先来说说它们的共同点 相同点 都是循环遍历数组中的每一项forEach和map方法里每次执行匿名函数都支持3个参数&#xff0c;参数分别是item&#xff08;当前每一项&…

1298 FORZA David Beckham

经典01背包问题&#xff0c;没有什么陷阱&#xff0c;唯一要求就是要优化空间复杂度&#xff01;下面是关于01背包的讲解&#xff1a; 01背包问题是最基础的背包问题&#xff0c;特点是&#xff1a;每种物品仅有一件&#xff0c;可以选择放或不放。用子问题定义状态&#xff1a…

android 获取lanucher 列表

引用&#xff1a;http://www.iteye.com/topic/696187 获取Launcher 启动列表 即 列出所有Launcher程序 通过PackageManager 来获取 [代码 步骤] 1. 定义内部类 LauncherItem 用于定义Application相关属性 比如&#xff1a;图标 名称 以及 ComponentName Java代码 public clas…

对int变量赋值的操作是原子的吗?

对于例子如下&#xff1a; int count 0; count; // 是原子操作吗? count; 是原子操作吗? 先说答案&#xff1a; 1、在单处理器下&#xff0c;如果将 count; 语句 翻译为单指令时&#xff0c;是原子操作。 不过现在处理器都会对语句进行优化。 2、在多处理器下&#xf…

信号量进程同步与互斥

2.哲学家吃面问题 semaphore fork[5]; for(int i0; i<5;i) fork[i]1; cobegin process philosopher_i( ){ while(ture){ think( ); P(fork[i]); P(fork[(i10%5])&#xff1b; eat&#xff08;&#xff09;&#xff1b; V(fork[i]); V(fork[(i10%5])&#xff1b; } } coend 5…

企业面试中关于MYSQL重点的28道面试题解答

问题1&#xff1a;char、varchar的区别是什么&#xff1f; varchar是变长而char的长度是固定的。如果你的内容是固定大小的&#xff0c;你会得到更好的性能。 问题2: TRUNCATE和DELETE的区别是什么&#xff1f; DELETE命令从一个表中删除某一行&#xff0c;或多行&#xff0c;T…

普通的int main(){}没有写return 0;会怎么样?

结论可能大家看上面的图就知道了&#xff0c;没有加return 0;编译器会自动添加一个。那怎么证明呢&#xff1f; 可以查看相应的汇编代码&#xff0c;查看汇编代码推荐使用godbolt.org网站&#xff0c;相当方便。 如上图&#xff0c;输入C代码&#xff0c;在右半部分会显示编译…

HDU1029

这道题没啥好说的&#xff0c;直接飘过…… #include<iostream>#include<map>using namespace std;int main(void){ int ans,n,b; while(cin>>n) { map<int,int>a; for(int i0;i<n;i) { cin>>b;//用cin就超时了&#xff0c;超1000ms(换…

python 内置方法 BUILT-IN METHODS

setattr getattr hasattr 1. abs() returns absolute value of a number 返回绝对值 integer -20 print(Absolute value of -20 is:, abs(integer)) 2. all() returns true when all elements in iterable is true 都为true则为true 3. any() Checks if any Element of an Ite…

ubuntu下安装拼音输入法ibus

可以安装ibus输入法。ibus有取代scim到趋势。 使用方法&#xff1a; 启用:ctrolspace&#xff1b; 中英文切换&#xff1a;shift&#xff1b;

并发与并行的区别

学习多线程的时候会遇到一个名词&#xff1a;并发。这是属于操作系统中的词汇&#xff0c;需要了解并发和并行的区别&#xff0c;从网上搜集了几种说法帮助理解。 一&#xff1a; 并发是指一个处理器同时处理多个任务。 并行是指多个处理器或者是多核的处理器同时处理多个不同的…