Entity Framework object Framework formation journey -- using Fluent API configuration in Code First mode (6), -- codefluent
The previous article Entity Framework formation journey-Code First Framework Design (5) introduced the experience of Entity Framework Based on the Code First model, this mode is implemented by adding corresponding feature descriptions to the object class (POCO class). However, sometimes we may need to consider multiple database-based methods, this method may be inappropriate. This article describes how to use Fluent API configuration to construct an Entity Framework in Code First mode.
When Code First is used, the default behavior is to map POCO classes to tables using a set of embedded conventions in EF. However, sometimes you cannot or do not want to comply with these conventions, You need to map entities to other objects beyond the conventions. In particular, these embedded conventions may be related to databases and may have different Representation Methods for different databases, or we may have different table names and field names for different databases; in addition, we want to keep the purity of the POCO class as much as possible, and do not want to make it too boring. So we will introduce Fluent API configuration in a timely and necessary manner.
1. Code review in Code First Mode
In the previous article, I constructed several representative table structures. The specific relationships are as follows.
These tables contain several classic relationships. One is a Role table with a self-reference relationship, the other is a many-to-many relationship between the User and the Role table, and the other is a reference relationship between the User and UserDetail.
We can see that the entity class code automatically generated by EF is as follows.
[Table("Role")] public partial class Role { public Role() { Children = new HashSet<Role>(); Users = new HashSet<User>(); } [StringLength(50)] public string ID { get; set; } [StringLength(50)] public string Name { get; set; } [StringLength(50)] public string ParentID { get; set; } public virtual ICollection<Role> Children { get; set; } public virtual Role Parent { get; set; } public virtual ICollection<User> Users { get; set; } }
The generated database operation context class code is as follows.
public partial class DbEntities : DbContext { public DbEntities() : base("name=Model1") { } public virtual DbSet<Role> Roles { get; set; } public virtual DbSet<User> Users { get; set; } public virtual DbSet<UserDetail> UserDetails { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Role>() .HasMany(e => e.Children) .WithOptional(e => e.Parent) .HasForeignKey(e => e.ParentID); modelBuilder.Entity<Role>() .HasMany(e => e.Users) .WithMany(e => e.Roles) .Map(m => m.ToTable("UserRole")); modelBuilder.Entity<User>() .HasMany(e => e.UserDetails) .WithOptional(e => e.User) .HasForeignKey(e => e.User_ID); modelBuilder.Entity<UserDetail>() .Property(e => e.Height) .HasPrecision(18, 0); } }
2. Use the Code First mode Code structure configured by Fluent API
Whether using Fluent API configuration in Code First mode or using the preceding Attribute feature tag instructions, it is to build information between entity classes and tables at the Code level, or there are some relationships between multiple tables. However, if we remove the Attribute feature tags of these object classes, we can specify the attributes and relationships through Fluent API configuration.
In fact, this method has already been used in the previous OnModelCreating function to configure the relationship between tables. to purely use Fluent API configuration, we also need to simplify the entity class, in the end, we can obtain the real object class information as follows.
public partial class User { public User() { UserDetails = new HashSet<UserDetail>(); Roles = new HashSet<Role>(); } public string ID { get; set; } public string Account { get; set; } public string Password { get; set; } public virtual ICollection<UserDetail> UserDetails { get; set; } public virtual ICollection<Role> Roles { get; set; } }
This entity class is almost the same as what we used to do in the past. There is no redundant information. The only difference is that it is completely object-based, including some additional associated object information.
As mentioned above, all the fields of the Oracle entity class generated are uppercase letters. However, we still need to maintain the Pascal format of the entity class, you can specify its field name in the Fluent API ConfigurationUppercase(Note: You must specify the field name in uppercase in Oracle because it is case sensitive ).
Finally, we defined the ing relationship of the Oracle database USERS table as follows.
/// <Summary> /// User table USERS ing information (Fluent API configuration) /// </summary> public class UserMap: EntityTypeConfiguration <User> {public UserMap () {HasMany (e => e. userDetails ). witexceptional (e => e. user ). hasForeignKey (e => e. user_ID); Property (t => t. ID ). hasColumnName ("ID"); Property (t => t. account ). hasColumnName ("ACCOUNT"); Property (t => t. password ). hasColumnName ("PASSWORD"); ToTable ("WHC. USERS ");}}
We map the field names for each field, and Oracle needs to be capitalized. We also map it to the WHC. USERS table through ToTable ("WHC. USERS.
For a Role with many-to-many intermediate table relationships, let's look at its relational Code as follows.
/// <Summary> /// User table ROLE ing information (Fluent API configuration) /// </summary> public class RoleMap: EntityTypeConfiguration <Role> {public RoleMap () {Property (t => t. ID ). hasColumnName ("ID"); Property (t => t. name ). hasColumnName ("NAME"); Property (t => t. parentID ). hasColumnName ("PARENTID"); ToTable ("WHC. ROLE "); HasMany (e => e. children ). witexceptional (e => e. parent ). hasForeignKey (e => e. parentID); HasMany (e => e. users ). withtasks (e => e. roles ). map (m => {m. mapLeftKey ("ROLE_ID"); m. mapRightKey ("USER_ID"); m. toTable ("USERROLE", "WHC ");});}}
Note that the MapLeftKey and MapRightKey correspond well. Otherwise, an error may occur. In general, it may be hard to understand that the one is Left and the one is Right. However, after testing, we can find that Left is definitely the key pointing to the current ing object (as shown above, ROLE_ID is the same as Left, because the current ing object is a Role object ).
Through the creation of these ing codes, we have established a one-to-one correspondence relationship for each table. The rest is to load the ing relationship into the database context object, do you still remember the OnModelCreating mentioned earlier? It is there. The general loading method is as follows.
// Manually load modelBuilder. Configurations. Add (new UserMap (); modelBuilder. Configurations. Add (new RoleMap (); modelBuilder. Configurations. Add (new UserDetailMap ());
This method replaces the original bloated code method.
modelBuilder.Entity<Role>() .HasMany(e => e.Children) .WithOptional(e => e.Parent) .HasForeignKey(e => e.ParentID); modelBuilder.Entity<Role>() .HasMany(e => e.Users) .WithMany(e => e.Roles) .Map(m => m.ToTable("UserRole")); modelBuilder.Entity<User>() .HasMany(e => e.UserDetails) .WithOptional(e => e.User) .HasForeignKey(e => e.User_ID); modelBuilder.Entity<UserDetail>() .Property(e => e.Height) .HasPrecision(18, 0);
Generally, here I think the entire idea has been introduced, but it is always a good thing to keep improving. I still think it is not good enough for the above Code, every time I load the Fluent API configuration, I need to specify the specific ing classes. This is very bad. If I can dynamically load them in, it is not amazing.
Hard encoding similar to the following link is not a good thing.
modelBuilder.Configurations.Add(new UserMap());modelBuilder.Configurations.Add(new RoleMap());modelBuilder.Configurations.Add(new UserDetailMap());
We can dynamically load them through reflection. In this way, the OnModelCreating function is flexible, and the OnModelCreating function is only mapped once when the program is started. Even if the context object DbEntities is constructed repeatedly, this OnModelCreating function will not be repeatedly triggered, so we will not worry about using reflection. performance is just a little slower for the first time, and will not be repeatedly triggered later.
Finally, let's look at the Code as follows (the annotated code is no longer used ).
Protected override void OnModelCreating (DbModelBuilder modelBuilder) {# region MyRegion // modelBuilder. entity <Role> ()//. haswon (e => e. children )//. witexceptional (e => e. parent )//. hasForeignKey (e => e. parentID); // modelBuilder. entity <Role> ()//. haswon (e => e. users )//. withtasks (e => e. roles )//. map (m => m. toTable ("UserRole"); // modelBuilder. entity <User> ()//. haswon (e => e. userDetails )//. Witexceptional (e => e. user )//. hasForeignKey (e => e. user_ID); // modelBuilder. entity <UserDetail> ()//. property (e => e. height )//. hasPrecision (18, 0); // manually load // modelBuilder. events. add (new UserMap (); // modelBuilder. events. add (new RoleMap (); // modelBuilder. events. add (new UserDetailMap (); # endregion // use the database suffix name to ensure that the specified database ing content is loaded // string mapSuffix = ". oracle ";//. sqlServer /. orac Le /. mySql /. SQLite string mapSuffix = ConvertProviderNameToSuffix (defaultConnectStr. providerName); var typesToRegister = Assembly. getExecutingAssembly (). getTypes (). where (type => type. namespace. endsWith (mapSuffix, StringComparison. ordinalIgnoreCase )). where (type =>! String. IsNullOrEmpty (type. Namespace). Where (type => type. BaseType! = Null & type. baseType. isGenericType & type. baseType. getGenericTypeDefinition () = typeof (EntityTypeConfiguration <>); foreach (var type in typesToRegister) {dynamic configurationInstance = Activator. createInstance (type); modelBuilder. events. add (configurationInstance);} base. onModelCreating (modelBuilder );}
In this way, the program runs normally, and the fields not restricted to the object class must be capitalized. Dynamic Loading is still a good thing for us to use other databases, because other databases only need to modify the ing, it is truly far away from complicated XML and entity-class bloated Attribute writing content, achieving a very flexible ing.
Finally, I posted the test code example, which is not much different from the previous example.
Private void button1_Click (object sender, EventArgs e) {DbEntities db = new DbEntities (); User user User = new user (); User. account = "TestName" + DateTime. now. toShortTimeString (); user. ID = Guid. newGuid (). toString (); user. password = "Test"; UserDetail detail = new UserDetail () {ID = Guid. newGuid (). toString (), Name = "userName33", Sex = 1, Note = "Test content 33", Height = 175}; user. userDetails. add (detail ); Db. users. add (user); Role role = new Role (); role. ID = Guid. newGuid (). toString (); role. name = "TestRole"; // role. users. add (user); user. roles. add (role); db. users. add (user); // db. roles. add (role); db. saveChanges (); Role roleInfo = db. roles. firstOrDefault (); if (roleInfo! = Null) {Console. writeLine (roleInfo. name); if (roleInfo. users. count> 0) {Console. writeLine (roleInfo. users. toList () [0]. account);} MessageBox. show ("OK ");}}
Test the Oracle database. We can see that the data is added to the database.
In addition, the corresponding relationship of the summary table is also created in the above example. The specific data is as follows.
For SQLServer, we can also see that an additional table is added to the database, as shown below.
If the table information changes, clear the records in the Table. Otherwise, some error messages are prompted, it may be a waste of time to locate the specific problem.
This table information is not found in other databases, such as Oracle, Mysql, and Sqlite. The specific data of SQLServer is as follows.
After the entire project structure is optimized to a standard framework structure, the structural hierarchy is shown below.