首页 新闻 搜索 专区 学院

EntityFramework中实体属性值更新奇怪问题

0
悬赏园豆:50 [已解决问题] 解决于 2018-01-29 13:27

using (var scope = _dbContextScopeFactory.Create())
{
                var db = scope.DbContexts.Get<MyTestContext>();
                var entity = db.ProductInfos.Find(dto.Id);
                _mapper.Map(dto, entity);
                entity.ProductClass = await db.ProductClasss.FirstOrDefaultAsync(p => p.Id == dto.ProductClassId);
                await scope.SaveChangesAsync();
                return true;
 }
在这一行
entity.ProductClass = await db.ProductClasss.FirstOrDefaultAsync(p => p.Id == dto.ProductClassId);
如果使用下面代码,会提示ProductClass中的ProcClassName 属性值为空(数据库是要求必填的),实际调试时发现是有值的,导致发生异常,不知道是什么原因,希望那位知道的大侠告知下,谢谢
entity.ProductClass = await db.ProductClasss.FindAsync(p => p.Id == dto.ProductClassId);

 

 

开发环境vs2017,.net framwork4.6.1,entityframework6.1.3,Mehdime.Entity1.0.0

 

下面是.net framwork中的解释

FirstOrDefaultAsync

如果没有找到这样的元素,则异步返回满足指定条件的序列的第一个元素或默认值。不支持同一上下文实例上的多个活动操作。使用“等待”以确保任何异步操作在调用之前都已完成。在此上下文中的另一方法。

FindAsync

异步查找具有给定主键值的实体。如果具有给定主键值的实体存在于上下文中,则它将立即返回而不向存储库发出请求。否则,对具有给定主键值的实体发出一个请求,如果找到该实体,则将该实体附加到上下文并返回。如果在上下文或存储中找不到实体,则返回null。

不支持同一上下文实例上的多个活动操作。
使用“等待”以确保任何异步操作都已完成。
在此上下文中调用另一方法。

事理的主页 事理 | 初学一级 | 园豆:44
提问于:2018-01-28 14:44

建议贴一下具体的错误信息

dudu 4年前
< >
分享
最佳答案
1

_mapper.Map(dto, entity);是不是写反了?

收获园豆:25
dudu | 高人七级 |园豆:36310 | 2018-01-28 15:10

没有哦,调试都是有值的,其它的地方更新只要没有实体属性的都是可以的

事理 | 园豆:44 (初学一级) | 2018-01-28 15:19

@事理: 建议改为下面的代码,然后看一下 productClass 的值

va productClass = await db.ProductClasss.FindAsync(p => p.Id == dto.ProductClassId);
entity.ProductClass = productClass;
dudu | 园豆:36310 (高人七级) | 2018-01-28 16:45

@dudu: ProcClassName 有值,但是还是不能更新

事理 | 园豆:44 (初学一级) | 2018-01-28 16:47

@事理: 将 entity.ProductClass 的赋值操作放到 _mapper.Map(dto, entity); 之前试试

dudu | 园豆:36310 (高人七级) | 2018-01-28 17:15

@dudu: 放到_mapper.Map(dto, entity);之前也不行哦

事理 | 园豆:44 (初学一级) | 2018-01-28 17:24

@dudu: FirstOrDefaultAsync是可以的,应该是FirstOrDefaultAsync和FindAsync内部有差异导致,但.net framework里面没有详细说明

事理 | 园豆:44 (初学一级) | 2018-01-28 17:27

@事理: FirstOrDefaultAsync和FindAsync只不过FindAsync基于主键查询会有缓存,还是觉得您代码哪里是不是有问题

Jeffcky | 园豆:2589 (老鸟四级) | 2018-01-28 17:53

@事理: 如果你能提供重现这个的示例代码,应该可以找到解决方法的

dudu | 园豆:36310 (高人七级) | 2018-01-28 19:47

@dudu: 我写了一个例子,附加数据库后,在UserService中打断点后直接F5运行

https://pan.baidu.com_s_1pLZbyQn

将_替换为/

事理 | 园豆:44 (初学一级) | 2018-01-28 23:27

@事理: 已经下载好了

dudu | 园豆:36310 (高人七级) | 2018-01-29 11:19

@dudu: 调试时,您将UserService里面的代码修改为下图,会看到entity.UserType = uType;里面的TypeName实际是有值的

事理 | 园豆:44 (初学一级) | 2018-01-29 12:07

@事理: 稍等,我重新回复一下。

dudu | 园豆:36310 (高人七级) | 2018-01-29 12:08

@事理:  由于 userModel.UserType = new UserTypeDto() { TypeId = 1 };  造成在  _mapper.Map(dto, entity);  时对 entity.UserType 的主键 TypeId 进行了赋值,而  TypeName 的值为 null,在使用 FindAsync 得到的 uType 与 entity.UserType 的主键相同,所以 EF 不会进行赋值操作,TypeName 依然为 null。

dudu | 园豆:36310 (高人七级) | 2018-01-29 12:12

@事理: 解决这个问题最简单的方法是 AutoMapper 映射时忽略 UserType 属性

config.CreateMap<Users, UsersDto>().ReverseMap().ForMember(u => u.UserType, opt => opt.Ignore());
dudu | 园豆:36310 (高人七级) | 2018-01-29 12:20

@dudu: 通过var uType = await db.UserType.FirstOrDefaultAsync(ut => ut.TypeId == dto.UserType.TypeId);是可以的

就是不明白为什么不能重新赋值

事理 | 园豆:44 (初学一级) | 2018-01-29 12:30

@事理: 这与EF的实体状态跟踪机制有关,EF认为发生的状态变化是出现了一个新的TypeName为null的新的UserType实例。如果想知道背后的真正原因,需要深入了解EF的实体状态跟踪机制。

INSERT [dbo].[Data_UserType]([TypeName])
VALUES (NULL)
SELECT [TypeId]
FROM [dbo].[Data_UserType]
WHERE @@ROWCOUNT > 0 AND [TypeId] = scope_identity()
dudu | 园豆:36310 (高人七级) | 2018-01-29 13:06

@dudu: 好的,非常感谢!

事理 | 园豆:44 (初学一级) | 2018-01-29 13:26
其他回答(1)
0

这个问题可以远程看下,如果方便的话!

收获园豆:25
Jeffcky | 园豆:2589 (老鸟四级) | 2018-01-28 17:57

感谢您的回复,不太方便哦

支持(0) 反对(0) 事理 | 园豆:44 (初学一级) | 2018-01-28 18:05

我有写一个例子,麻烦帮忙看看

https://pan.baidu.com_s_1pLZbyQn

将_替换为/

支持(0) 反对(0) 事理 | 园豆:44 (初学一级) | 2018-01-28 23:29

@事理:刚看了您写的Demo,主要问题出在延迟加载。
【1】在控制器中您有此句【userModel.UserType = new UserTypeDto() { TypeId = 1 };】当您执行【 var entity = await db.Users.FirstOrDefaultAsync(u => u.UserId == dto.UserId);】去查询时,然后DTO【 _mapper.Map(dto, entity);】,此时User被上下文跟踪,同时UserType也会被跟踪,接着您再执行此句【 var uType = await db.UserType.FindAsync(dto.UserType.TypeId);】,根本不会去数据库中查询(您可以监控SQL语句得知),上下文知道主键等于1的UserType已被跟踪即在内存中存在,所以此时会在内存中查询,但是此时TypeName为空,所以会让您疑惑:明明TypeId等于1在数据库中存在,为何查询出的Type为空。如若还有疑惑您将【 var entity = await db.Users.FirstOrDefaultAsync(u => u.UserId == dto.UserId);】加上AsNoTracking再执行【 var uType = await db.UserType.FindAsync(dto.UserType.TypeId);】将能正确查询出数据,但是未跟踪导致数据无法正确被更新,又如果添加【 var entity = await db.Users..Include(d=>d.UserType).FirstOrDefaultAsync(u => u.UserId == dto.UserId);】此时再执行后续查询和提交将抛出【属性“TypeId”是对象的键信息的一部分,不能修改】异常。
【2】FirstOrDefaultAsync和FindAsync使用区别在于,利用FindAsync查询会去内存中查找主键对应的对象是否存在,若存在将不会去数据库中查询,这也就是它达到缓冲的实际目的。
【3】建议:对于关系应该手动配置而非由其自动生成外键无法维护从而抛出不可预料异常

支持(0) 反对(0) Jeffcky | 园豆:2589 (老鸟四级) | 2018-01-29 01:53

@Jeffcky: 

调试了下,的确是上下文跟踪导致的,我把延迟加载关闭了也会这样(Configuration.LazyLoadingEnabled = false),应该和延时加载没有关系,但是看上面图片,通过FindAsync查询出的TypeName明显是有值的,如果说在【 var entity = await db.Users.FirstOrDefaultAsync(u => u.UserId == dto.UserId);】时,上下文同时跟踪了【userModel.UserType = new UserTypeDto() { TypeId = 1 };】这个对象,当我在【var uType = await db.UserType.FindAsync(dto.UserType.TypeId);】查询出的UserType(TypeName是有值的,数据库中查询出来的),那么把这个对象赋值给了跟踪的【userModel.UserType = new UserTypeDto() { TypeId = 1 };】对象,引用地址也会跟着指向了从数据库中查询出来的UserType对象,也应该就有值了,不知道为什么还是提示没有值,这让人不容易理解啊

支持(0) 反对(0) 事理 | 园豆:44 (初学一级) | 2018-01-29 10:12

@Jeffcky: 

。。。。

支持(0) 反对(0) 事理 | 园豆:44 (初学一级) | 2018-01-29 10:13

@事理: 和延迟加载没关系,和变更追踪有关系!

支持(0) 反对(0) Jeffcky | 园豆:2589 (老鸟四级) | 2018-01-29 10:15

@Jeffcky: 是的,但是还是不太明白

支持(0) 反对(0) 事理 | 园豆:44 (初学一级) | 2018-01-29 10:25

第二次查询为NULL是否和AutoMapper映射有关系呢,导致TypeName未映射进来呢?

支持(0) 反对(0) Jeffcky | 园豆:2589 (老鸟四级) | 2018-01-29 10:27

@Jeffcky: 是在automapper的时候把typename赋值为null了,所以第二个firstordefault查询出的typename为什么会为null呢?如果firstordefault不从缓存中查询,应该要从数据库中获取才对吧,实际感觉也是从缓存中读取的

支持(0) 反对(0) 事理 | 园豆:44 (初学一级) | 2018-01-29 11:14

@事理: 这样来看的话,那firstordefault看来也是在缓存中获取,只是猜测,我没验证过!

支持(0) 反对(0) Jeffcky | 园豆:2589 (老鸟四级) | 2018-01-29 12:45

@Jeffcky: 好的,非常感谢!

支持(0) 反对(0) 事理 | 园豆:44 (初学一级) | 2018-01-29 13:26
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册