死去活来,而不变质:Domain Model(领域模型) 和 EntityFramework 如何正确进行对象关系映射?

写在前面

  阅读目录:

  • 设计误区
  • 数据库已死
  • 枚举映射
  • 关联映射
  • 后记

  在上一篇《一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)?》博文中,探讨的是如何聚焦领域模型(抛开一些干扰因素,才能把精力集中在领域模型的设计上)?需要注意的是,上一篇我讲的并不是如何设计领域模型(本篇也是)?而是如何聚焦领域模型,领域模型的设计是个迭代过程,不能一概而论,还在路上。

  当有一个简单的领域模型用例,完成一个从上而下过程的时候,就需要对领域模型和数据库进行对象关系映射(ORM),首先,在领域驱动设计中,领域模型是活的(具有自己的行为和状态),而映射到数据库中所谓的表是死的(只是一些字段),如何把活的变成死的?又如何把死的变成活的?更重要的是如何保证在这个“死去活来”的过程中,死的和活的是同一个?

  转换过程很简单,使用 ORM(对象关系映射)工具就很方便的完成这个“死去活来”的过程,但是有时候我们在这个转换过程中,可能会失去转换对象的本质,以致活的会变成死的,最后转换过程就只有死的变成死的(反复循环)。

设计误区

  在 MessageManager 项目的上一个版本中,主要存在两个领域模型:Messgae 和 User,他们数据库之间的映射关系是一对多的关系,就是一个用户拥有多个消息,但是一个消息只能对应一个用户(发件人或收件人),我们看下领域模型的设计(暂不包含业务逻辑)。

  Domain Model-Message:

 1 namespace MessageManager.Domain.DomainModel
 2 {
 3     public class Message : IAggregateRoot
 4     {
 5         #region 构造方法
 6         public Message()
 7         {
 8             this.ID = Guid.NewGuid().ToString();
 9         }
10         #endregion
11
12         #region 实体成员
13         public string FromUserID { get; set; }
14         public string FromUserName { get; set; }
15         public string ToUserID { get; set; }
16         public string ToUserName { get; set; }
17         public string Title { get; set; }
18         public string Content { get; set; }
19         public DateTime SendTime { get; set; }
20         public bool IsRead { get; set; }
21         public virtual User FromUser { get; set; }
22         public virtual User ToUser { get; set; }
23         #endregion
24
25         #region IEntity成员
26         /// <summary>
27         /// 获取或设置当前实体对象的全局唯一标识。
28         /// </summary>
29         public string ID { get; set; }
30         #endregion
31     }
32 }

  Domain Model-User:

 1 namespace MessageManager.Domain.DomainModel
 2 {
 3     public class User : IAggregateRoot
 4     {
 5         #region 构造方法
 6         public User()
 7         {
 8             this.ID = Guid.NewGuid().ToString();
 9         }
10         #endregion
11
12         #region 实体成员
13         public string Name { get; set; }
14         public virtual ICollection<Message> SendMessages { get; set; }
15         public virtual ICollection<Message> ReceiveMessages { get; set; }
16         #endregion
17
18         #region IEntity成员
19         /// <summary>
20         /// 获取或设置当前实体对象的全局唯一标识。
21         /// </summary>
22         public string ID { get; set; }
23         #endregion
24     }
25 }

  乍一看,Message 和 User 领域模型并没有什么问题,只是设计的太贫血(只是包含一些属性字段),抛开业务逻辑,我们看下 Message 和 User 之间的关联,Message 模型中拥有 FromUserID,FromUserName,ToUserID,ToUserName 字段,用来表示和 User 模型的关联,Navigation Properties(导航属性)为:FromUser 和 ToUser,类型为 User,再看一下 User 模型的导航属性:SendMessages 和 ReceiveMessages,类型为 ICollection<Message>,我们如果按照平常的开发模式(脚本驱动模式),这样设计没有一点问题,很方便对 ORM 进行配置:

 1         /// <summary>
 2         /// Initializes a new instance of <c>MessageConfiguration</c> class.
 3         /// </summary>
 4         public MessageConfiguration()
 5         {
 6             HasKey(c => c.ID);
 7             Property(c => c.ID)
 8                 .IsRequired()
 9                 .HasMaxLength(36)
10                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
11             Property(c => c.FromUserID)
12                 .IsRequired()
13                 .HasMaxLength(36);
14             Property(c => c.ToUserID)
15                 .IsRequired()
16                 .HasMaxLength(36);
17             Property(c => c.Title)
18                 .IsRequired()
19                 .HasMaxLength(50);
20             Property(c => c.Content)
21                 .IsRequired()
22                 .HasMaxLength(2000);
23             Property(c => c.SendTime)
24                 .IsRequired();
25             Property(c => c.IsRead)
26                 .IsRequired();
27             ToTable("Messages");
28
29             // Relationships
30             this.HasRequired(t => t.FromUser)
31                 .WithMany(t => t.SendMessages)
32                 .HasForeignKey(t => t.FromUserID)
33                 .WillCascadeOnDelete(false);
34             this.HasRequired(t => t.ToUser)
35                 .WithMany(t => t.ReceiveMessages)
36                 .HasForeignKey(t => t.ToUserID)
37                 .WillCascadeOnDelete(false);
38         }

  上面代码表示 Message 的映射配置,如果外键可以为 NULL,则使用 HasOptional 方法,多对对则使用 HasMany 和 WithMany,WillCascadeOnDelete 用来级联删除配置,EntityTypeConfiguration 的具体详细配置,请参照:http://msdn.microsoft.com/zh-cn/data/jj591620.aspx

  上面的设计到底有没有问题?我们来分析一下,首先 User 领域模型中的 SendMessages 和 ReceiveMessages 属性,如果单独作为导航属性,这是没有什么问题的,因为我们可以使用导航属性很方便的进行映射配置(比如上面代码),但是放在领域模型中就有点不伦不类了,User 是一个用户对象,我们不能在它的身上来挂一些属于它的东西,因为这些并不是用户本身所具有的,这就好像我设计一个用户模型,它拥有手机,电脑,背包,房子,车子等等,然后就必须在这个用户模型中添加这个属性,这样设计就会很不合理,这个应该设计在它所拥有的物品上,因为只有这些物品拥有用户,这些物品相对于用户来说,才有真正的存在意义。

  再来看 Message 领域模型,首先 FromUserID,FromUserName,ToUserID,ToUserName 这四个字段就让我们看得很不顺眼,因为这些都是已死的字段,Message 应该关联的是活的 User,而并不是在它身上打上几个 User 的标签,这个表现应该在数据库中(因为数据库中就是存的这些已死的字段),而并不是在活的 Message 领域模型中,FromUser 和 ToUser 的设计是没有问题的,因为关联的就是活的 User 对象。

  为什么有了 FromUser 和 ToUser 对象,Message 领域模型中还要添加上面那四个字段呢?主要原因还是受思维模式的影响(脚本驱动模式),虽然是基于领域模型设计,但是在设计过程中就会不自觉的往脚本驱动模式上套,为什么?因为我们要使用数据库,不管怎么设计,这些对象都是要存在数据库中的,而数据库存的都是一些已死的对象(只是包含字段),对象死了,那怎么来表示 Message 和 User 对象之间的关联呢?答案就是 FromUserID 和 ToUserID,因为只有通过这两个字段,才能在数据库中体现 Message 和 User 对象之间的关联,数据库存储中确实是这么做的,但是我们把数据库中的关联表现在领域模型中就很不合适了,最后的结果就是 FromUser 和 ToUser 对象的作用只是用来映射配置,Message 领域模型变成和数据库中的 Message 表一样,状态都是已死,转换也就是死的对象转换为死的对象。

  那到底怎么设计?答案就是把 Message 领域模型中的 FromUserID,FromUserName,ToUserID,ToUserName 四个属性去掉,User 领域模型中的 SendMessages 和 ReceiveMessages 属性也去掉,让领域模型变得干净。那有人会问了,你把这些关联字段去掉了,怎么去映射数据库呢?天无绝人之路,使用 EntityFramework(ORM 工具之一)就很方便的进行映射配置,具体配置,可以看下枚举映射和关联映射两个节点。

数据库已死

  本节点纯属扯淡,兄台们不感兴趣的话,可以直接略过。

  “数据库已死”的这个概念,并不是本人提出的,早在六年前在解道中就有人提出,具体可以参考:

  首先,强调一点,数据库已死的概念,并不是说我们项目中不使用数据库(想想应用程序不使用数据库也不可能),只是说应用程序设计的核心不再是基于数据库设计的,而应该是基于面向对象设计,数据库只是存储数据的一种方式,当然也可以配置文件存储或者内存存储。以往我们进行应用程序设计的时候,都是先根据业务需求定义表结构,然后根据表结构用“面向对象”的语言去传递 SQL 放到数据库中执行,这样面向对象语言就成了所谓的 SQL 搬运工,这样造成的问题就是非常难维护,牵一发而动全身,而且性能瓶颈也主要体现在数据库方面,想想应用程序的性能问题(排除代码问题),我们可以使用负载均衡增加服务器,来分担所带来的压力,而应对数据库性能问题呢?从“MySpace”的经历上就可以看出,那是相当的难处理,而且性能问题主要集中在数据库方面,也是设计的不合理所造成的。

  我们来看一下 MySqace 的信息系统发展历程:

  • 第一代架构—添置更多的Web服务器:因为用户量小,所以我们一般部署应用程序的时候,都是应用程序和数据库各部署一台,当用户暴增之后,我们就开始部署更多的应用程序服务器(数据库服务器还是一台),但是当用户量达到一定的程度后,部署再多的应用程序服务器已没有什么用,因为数据库服务器就一台。
  • 第二代架构—增加数据库服务器:与增加 Web 服务器不同,增加数据库并没那么简单。如果一个站点由多个数据库支持,设计者必须考虑的是,如何在保证数据一致性的前提下让多个数据库分担压力。MySpace 运行在三个 SQL Server 数据库服务器上—一个为主,所有的新数据都向它提交,然后由它复制到其它两个;另两个数据库服务器全力向用户供给数据,用以在博客和个人资料栏显示。这种方式在一段时间内效果很好—只要增加数据库服务器,加大硬盘,就可以应对用户数和访问量的增加。说到低,这种方式就是拆分数据库,不同的应用程序使用数据库不同,然后部署再不同的服务器上,但是当用户再次达到一定程度后,这种方案也不太适合了。
  • 第三代架构—转到分布式计算架构:MySpace 将目光移到分布式计算架构——它在物理上分布的众多服务器,整体必须逻辑上等同于单台机器。拿数据库来说,就不能再像过去那样将应用拆分,再以不同数据库分别支持,而必须将整个站点看作一个应用。现在,数据库模型里只有一个用户表,支持博客、个人资料和其他核心功能的数据都存储在相同数据库。既然所有的核心数据逻辑上都组织到一个数据库,那么 MySpace 必须找到新的办法以分担负荷——显然,运行在普通硬件上的单个数据库服务器是无能为力的。这次,不再按站点功能和应用分割数据库,MySpace 开始将它的用户按每百万一组分割,然后将各组的全部数据分别存入独立的SQL Server实例。可以看出这种方式显然也不能满足高用户量的需求。
  • 第四代架构—求助于微软方案:2005年早期,账户达到九百万,MySpace 开始用微软的 C# 编写 ASP.NET 程序。在收到一定成效后,MySpace 开始大规模迁移到 ASP.NET。用户达到一千万时,MySpace 再次遭遇存储瓶颈问题。SAN 的引入解决了早期一些性能问题,但站点目前的要求已经开始周期性超越 SAN 的 I/O 容量——即它从磁盘存储系统读写数据的极限速度。
  • 第五代架构—增加数据缓存层并转到支持 64 位处理器的 SQL Server 2005:MySpace 账户达到一千七百万,MySpace 又启用了新的策略以减轻存储系统压力,即增加数据缓存层——位于 Web 服务器和数据库服务器之间,其唯一职能是在内存中建立被频繁请求数据对象的副本,如此一来,不访问数据库也可以向 Web 应用供给数据。2005年中期,服务账户数达到两千六百万时,MySpace 因为我们对内存的渴求而切换到了还处于 beta 测试的支持 64 位处理器的 SQL Server 2005。升级到 SQL Server 2005 和 64 位 Windows Server 2003 后,MySpace 每台服务器配备了 32G 内存,后于 2006 年再次将配置标准提升到 64G。
  • 。。。。

  总结:从 MySpace 看更加验证,数据库是软件系统的瓶颈,而且最不可伸缩,一旦数据库成为系统瓶颈,就得动大手术,实现架构上的变迁,这是伤筋动骨,变迁人员压力巨大的。另外由于是社区,就是变迁数据丢失也没什么大不了,如果是企业那就......

  如果我们从软件系统开始之初,就使用对象分析设计,不与数据库沾边,整个流程就完全 OO,分析设计直至代码都摆脱了数据库影响,这个流程如下:

  1. 分析建模(基于领域驱动设计的业务建模)
  2. 细化设计(基于领域驱动设计的架构设计)
  3. 代码实现
  4. 调试测试
  5. 部署运行

  那么数据库在什么时候建立呢?数据库表结构的创建可以延缓到部署运行时,这样,整个上游环节就不涉及数据库技术,而是使用更符合自然的表达 OO 方式,软件质量就更高了。现在,很多人已经理解,分析设计要用 OO,但是数据库是运行阶段缺少不了的,确实,这是正确观点,我们夺取数据库的王位,不是将它打倒,只是理性和平移交权力重心而已,数据库退出主角地位,让位于中间件,也预示着过去数据库为王的时代的结束,
但是数据库会和操作系统一样,成为我们现代软件系统一个不可缺少重要的基础环节。

  了解了这么多,回到”设计误区“这一节点,你会发现,造成设计误区的主要原因还是,在设计的时候不自觉以数据库为中心了,而并非领域模型。

枚举映射

  在 Message 领域模型中,有个 MessageState 枚举类型,用来表示消息的状态,当然我们也可以使用 Bool 类型的字段来表示,但是消息状态是消息本身的一种状态,用对象来表示更为合适,MessageState 枚举定义如下:

1 namespace MessageManager.Domain.DomainModel
2 {
3     public enum MessageState
4     {
5         Read,
6         NoRead
7     }
8 }

  我们使用单元测试对映射转换进行测试,也就是 Code First 模式,测试代码:

 1 namespace MessageManager.Repositories.Tests
 2 {
 3     public class UserRepositoryTest
 4     {
 5         [Fact]
 6         public void AddUserRepository()
 7         {
 8             IUserRepository userRepository = new UserRepository(new EntityFrameworkRepositoryContext());
 9             User user1 = new User("小菜");
10             User user2 = new User("大神");
11             userRepository.Add(user1);
12             userRepository.Add(user2);
13             userRepository.Context.Commit();
14         }
15     }
16 }

  生成数据库发生异常:

  这个主要原因是当前 EntityFramework 版本不支持枚举类型映射,当前使用的 EntityFramework 版本为 4.3.1:

1 <?xml version="1.0" encoding="utf-8"?>
2 <packages>
3   <package id="EntityFramework" version="4.3.1" targetFramework="net40" />
4 </packages>

  EntityFramework 的版本太老了,更新版本为 6.1.1,NuGet 更新命令:update-package EntityFramework

  EntityFramework 从 4.3.1 升级到 6.1.1 更改的地方(http://msdn.microsoft.com/en-us/data/upgradeef6.aspx):

  1. System.Data.EntityState.Modified; 更改为 System.Data.Entity.EntityState.Modified;
  2. DatabaseGeneratedOption 需要添加 System.ComponentModel.DataAnnotations.Schema 命名空间。

  在升级完 EntityFramework 版本后,重新运行单元测试,但是发现又报如下错误:

  解决方案:

  在 MessageManagerDbContext 构造函数中添加如下代码:

1         public MessageManagerDbContext()
2             : base("MessageManagerDB")
3         {
4             var ensureDLLIsCopied = System.Data.Entity.SqlServer.SqlProviderServices.Instance;
5             this.Configuration.LazyLoadingEnabled = true;
6         }

  重新运行单元测试,测试成功,就会发现在 MessageManagerDB 数据库的 Messages 表中已生成 State 字段,类型为 Int,当然也可以通过 EntityTypeConfiguration 中的 HasColumnType 进行自定义字段类型。

关联映射

  先看一下,如果我们没有进行任何的 EntityTypeConfiguration 关联设置,生成数据库会是怎样?MessageConfiguration 和 UserConfiguration 配置如下:

 1     public class MessageConfiguration : EntityTypeConfiguration<Message>
 2     {
 3         /// <summary>
 4         /// Initializes a new instance of <c>MessageConfiguration</c> class.
 5         /// </summary>
 6         public MessageConfiguration()
 7         {
 8             HasKey(c => c.ID);
 9             Property(c => c.ID)
10                 .IsRequired()
11                 .HasMaxLength(36)
12                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
13             Property(c => c.Title)
14                 .IsRequired()
15                 .HasMaxLength(50);
16             Property(c => c.Content)
17                 .IsRequired()
18                 .HasMaxLength(2000);
19             Property(c => c.SendTime)
20                 .IsRequired();
21         }
22     }
 1     /// <summary>
 2     /// Represents the entity type configuration for the <see cref="Customer"/> entity.
 3     /// </summary>
 4     public class UserConfiguration : EntityTypeConfiguration<User>
 5     {
 6         #region Ctor
 7         /// <summary>
 8         /// Initializes a new instance of <c>UserConfiguration</c> class.
 9         /// </summary>
10         public UserConfiguration()
11         {
12             HasKey(c => c.ID);
13             Property(c => c.ID)
14                 .IsRequired()
15                 .HasMaxLength(36)
16                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
17             Property(c => c.Name)
18                 .IsRequired()
19                 .HasMaxLength(20);
20         }
21         #endregion
22     }

  上面代码中我们并没有进行关联配置,生成 MessageManagerDB 数据库中 Messages 表结构:

  可以看到我们虽然没有进行任何的关联设置,Code First 会自动为我们创建外键关联,仅仅是在 Message 领域模型中添加:

1         public virtual User SendUser { get; set; }
2         public virtual User ReceiveUser { get; set; }

  以上效果是我们想要的,这也是 EntityFramework 的进步之处,符合领域驱动设计的思想,领域模型中没有数据库中所谓的主外键关联,有的只是对象之间的关联,而数据库只是存储数据的一种表现,这样数据库设计的概念就不存在了,也让我们忘了数据库的存在,而把更多的精力放在领域模型的设计上,这就是领域驱动设计关键所在。

  除了 EntityFramework 默认生成关联配置,我们也可以进行自定义配置,比如,上面生成外键字段为:SendUser_ID 和 ReceiveUser_ID,也可以自定义字段名称:

1             HasRequired(x => x.SendUser)
2                 .WithMany()
3                 .Map(x => x.MapKey("SendUserID"))
4                 .WillCascadeOnDelete(false);
5             HasRequired(x => x.ReceiveUser)
6                 .WithMany()
7                 .Map(x => x.MapKey("ReceiveUserID"))
8                 .WillCascadeOnDelete(false);

  上面就是自定义外键字段为:SendUserID 和 ReceiveUserID,关于 EntityTypeConfiguration 的配置,比如一对一,一对多,多对多,联合主外键等等,可以参考:

后记

  更新项目:MessageManager.Domain,MessageManager.Domain.Tests,MessageManager.Repositories 和 MessageManager.Repositories.Tests,其他未更新,获取生成会报错。

  幻想下,如果存在对象性数据库,存储的都是“活生生”的对象,那是怎样的一种情形?咳咳,只是幻想。

  如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^

  参考资料:

死去活来,而不变质:Domain Model(领域模型) 和 EntityFramework 如何正确进行对象关系映射?

时间: 2024-10-24 21:48:45

死去活来,而不变质:Domain Model(领域模型) 和 EntityFramework 如何正确进行对象关系映射?的相关文章

hibernate(四)__由表逆向创建Domain对象和对象关系映射文件

之前我们是手写Domain对象和对象关系映射文件->然后生成数据库中的Table. 现在我们反过来先在数据库中建好Table->然后用工具生成Domain对象和对象关系映射文件. 步骤: 1.创建一个web工程项目 2.通过myeclipse 提供的数据库浏览器连接到我们的数据库. ①新建一个数据库连接: ②配置数据库连接(这里借用以oracle一张图,后面都是在sql server的数据库下的配置,可以忽视不影响): ③成功之后输入登录密码进入数据库: 右键表可直接修改删除表,当然这也是相当

拨开迷雾,找回自我:DDD(领域驱动设计)应对具体业务场景,Domain Model(领域模型)到底如何设计?

写在前面 阅读目录: 迷雾森林 找回自我 开源地址 后记 毫无疑问,领域驱动设计的核心是领域模型,领域模型的核心是实现业务逻辑,也就是说,在应对具体的业务场景的时候,实现业务逻辑是领域驱动设计最重要的一环,在写这篇博文之前,先总结下之前关于 DDD(领域驱动设计)的三篇博文: 我的“第一次”,就这样没了:DDD(领域驱动设计)理论结合实践:伪领域驱动设计,只是用 .NET 实现的一个“空壳”,仅此而已. 一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模

贫血模型;DTO:数据传输对象(Data Transfer Object);AutoMapper ;Domain Model(领域模型);DDD(领域驱动设计)

====================== 我自己的理解 ========================== 一:  DTO  我自己的理解,就是 比如你有一个类,跟数据库的table表结构一模一样,主键外键什么的都有,但是这个 model类,你返回数据到 UI层的时候,有些数据是不用的,你就得自己new一个新类出来,新的类从旧的类里面拿值,然后给别人用的就是新的类别,有点类似于我们做接口给android手机用一样的,数据库的类和接口用的类,很相似,但是东西少了的很多,这个新的类(缺胳膊断腿

拨乱反正:DDD 回归具体的业务场景,Domain Model 再再重新设计

首先,把最真挚的情感送与梅西,加油! 写在前面 阅读目录: 重申业务场景 Domain Model 设计 后记 上一篇<设计窘境:来自 Repository 的一丝线索,Domain Model 再重新设计>. 讲本篇内容之前,先回顾上一篇所讨论的内容,主要是 Repository(仓储)的职责问题,属于领域?还是应用层?其实到头来也没有准确的结论,但是最终比较偏向于仓储定义在领域,实现在基础层,调用在应用层.你可能有些疑问,为什么要讨论仓储的职责问题?看过上一篇的内容你可能会有些答案,这也就

设计窘境:来自 Repository 的一丝线索,Domain Model 再重新设计

写在前面 阅读目录: 疑惑解读 设计窘境 一幅图的灵感 为嘛还是你-Repository 后记 上一篇<No zuo no die:DDD 应对具体业务场景,Domain Model 重新设计>. 希望本篇博文废话少点,注:上一篇瞎扯的地方太多. 疑惑解读 先回顾一下,在上一篇博文中,主要阐述的是领域模型的重新设计,包含:真正的去理解领域模型和领域服务的加入(感兴趣的朋友可以看下前几篇来了解一下前因后果.).凡事都有修改的理由,为什么加入领域服务,主要是之前对领域模型的认知不够(实体充当起了伪

业务逻辑-Domain Model

Domain Model是对现实世界中的业务抽象为类,所以类不只包含数据,还包括方法(现实世界的业务逻辑).但领域模型不包括数据的存取,一般通过仓储模式将(POCO)对象管理数据. 设计一个复杂的系统,应先将现实世界的业务逻辑分割为不同的界限上下文,其实每个界限上下文对应现实世界的一部分独立业务逻辑,将不同的独立业务逻辑拼成一个复杂系统.现实逻辑与程序界限逻辑对应利于代码管理. 仓储模型 这里用Entity Framwork作为仓储模型,因为Entity Framwork已经是仓储模型,没有必要

Struts2之Domain Model(域模型)。

为了避免  在action中有 太多的 类,而需要写大量的get().set().所以,Struts2 使用 了 domain model. eg: private User user;    public String add(){        System.out.println("name:"+user.getUname());        System.out.println("pwd:"+user.getPwd());        System.ou

[转]在NopCommerce中新增一个Domain Model的步骤

本文转自:http://www.cnblogs.com/aneasystone/archive/2012/08/27/2659183.html 在NopCommerce中新增一个Domain Model,需要以下几个步骤: 1. 新建一个Entity Class (Nop/Core/Domain/Entity.cs) 2. 新建一个Mapping Class (Nop/Data/Mapping/EntityMap.cs) 3. 新建一个View Model (Nop/Admin/Models/E

EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public?

前言 不知我们是否思考过一个问题,在关系映射中对于导航属性的访问修饰符是否一定必须为public呢?如果从未想过这个问题,那么我们接下来来探讨这个问题. EF 6.x和EF Core 何种情况下必须配置映射关系? 在EF 6.x中我们创建如下示例类. public partial class Customer { public int Id { get; set; } public string Name { get; set; } public string Email { get; set;