Entity Framework使用建模之Code First 一对多

使用这种方式请首先安装EntityFramework4.1.exe,您可以去这里下载:

ADO.NETEntity Framework 4.1

Code First是EF建模的第三种方式,也是最灵活的一种方式,这种方式需要自己手动编写代码实现实体对象、对象关系,并提供注解映射信息,这种方式的本质是将基于图形界面的Model First设计方式改为基于手动编写代码的方式,这并不意味着code就效率低下,通过代码生成工具支持,开发效率一样很高。

一对多的示例

一个游戏账号可以创建多个游戏角色

/// <summary>
    /// 游戏账号
    /// 通过Data Annotations实现实体到数据库结构的映射
    /// </summary>
    [Table("GameUsers")]
    public class GameUser
    {
        [Key]
        //[Column("GAME_USER_ID")]        
        [MaxLength(8)]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public string GameUserID { get; set; }

        [Column("GAME_USER_NAME")]
        [Required]
        [MaxLength(32)]
        public string GameUserName { get; set; }

        [Column("GAME_USER_PASSWORD")]
        [MaxLength(16)]
        public string GameUserPassword { get; set; }

        
        public DateTime GameUserTime { get; set; }

        /// <summary>
        /// 该属性不被映射
        /// </summary>
        [NotMapped]
        public string Comment { get; set; }

        [ConcurrencyCheck]
        [Timestamp]
        public byte[] TimeStamp { get; set; }

        /// <summary>
        /// 游戏账户档案
        /// </summary>
        public GameUserArchive GameUserArchive { get; set; }
        
        public virtual ICollection<GameRole> GameRoles { get; set; }

    }        [Key]
        //[Column("GAME_USER_ID")]        
        [MaxLength(8)]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public string GameUserID { get; set; }

        [Column("GAME_USER_NAME")]
        [Required]
        [MaxLength(32)]
        public string GameUserName { get; set; }

        [Column("GAME_USER_PASSWORD")]
        [MaxLength(16)]
        public string GameUserPassword { get; set; }

        [MaxLength(16)]
        public DateTime GameUserTime { get; set; }

        /// <summary>
        /// 该属性不被映射
        /// </summary>
        [NotMapped]
        public string Comment { get; set; }

        [ConcurrencyCheck]
        [Timestamp]
        public byte[] TimeStamp { get; set; }

        /// <summary>
        /// 游戏账户档案
        /// </summary>        
        public GameUserArchive GameUserArchive { get; set; }
        
        public virtual ICollection<GameRole> GameRoles { get; set; }

    }

/// <summary>
    /// 账号角色
    /// 通过Data Annotations实现实体到数据库结构的映射
    /// </summary>
    [Table("GameRoles")]
    public class GameRole
    {
        [Key]
        //[Column("GameRoleID")]
        //[DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
        public int GameRoleID { get; set; }

        /// <summary>
        /// 所属账户编号
        /// </summary>
        [Required]
        [MaxLength(8)]
        public string GameUserID { get; set; }
        
        /// <summary>
        /// 角色名
        /// </summary>
        //[Required]
        //[MaxLength(32)]
        public string GameRoleName { get; set; }

        /// <summary>
        /// 所属账户对象
        /// </summary>
        [ForeignKey("GameUserID")]
        [InverseProperty("GameRoles")]
        public virtual GameUser GameUser { get; set; }


    }


/// <summary>
    /// 游戏用户档案
    /// 通过Data Annotations实现实体到数据库结构的映射
    /// </summary>
    [ComplexType]
    public class GameUserArchive
    {
        /// <summary>
        /// 身份证
        /// </summary
        [Required]
        [MaxLength(18)]
        public string IdentityCard { get; set; }

       
        [MaxLength(64)]
        public string HomeAddress { get; set; }

        [MaxLength(16)]
        public string ContactPhone { get; set; }
    }

说明:

实体到数据库结构的映射是通过默认的约定来进行的,如果需要修改的话,有两种方式,分别是:Data Annotations 和 Fluent API,本例使用的是Data Annotations 来修改实体到数据库结构的映射

以上类的编写中,我们用了很多C#的Attribute[特性]:

       Table:指定实体所对应的数据库的表名,不指定则对应的表名为类名的复数形式 

       Key:指定是否是主键,不指定则 Code First 会将名为“Id”或“<类名>Id”的字段推断为主键,且如果它的类型是"int"或"long"或"short"的话,则会在数据库中默认注册为 identity 字段。注:主键的推断与大小写无关 

DatabaseGenerated:指定字段的值在数据库中的生成方式

DatabaseGeneratedOption.None :不做任何处理

 

             DatabaseGeneratedOption.Identity:标识列

DatabaseGeneratedOption.Compute:计算列

              Required:指定为必填字段,即指定数据库对应的列不允许为 null 值

              MaxLength:指定字段的最大长度,未指定则为 max

              StringLength:指定字段的长度范围

              Column:指定字段所对应的数据库中的列名,默认情况下数据库中的列名同 Code First 中的字段名

              NotMapped:没有对应关系,即此字段不会在数据库中生成对应的列

              Timestam:指定对应的数据库中的列的类型为DateTime

              ForeignKey:指定外键的名称,默认情况下与导航属性的主键名称相同的字段会自动被标记为外键

              InverseProperty :指定导航属性的反转属性,默认情况下按实体的互相引用自行推断  

ComplexType :复杂类型,如果字段类型为一个实体类,则此字段会被自动标记为复杂类型,被标记为复杂类型的字段为必填字段,复杂类型的每个属性将作为所属类型的表的字段存在。

              Timestamp :将 Code First 中的类型为 byte[] 的字段对应到数据库中的类型为 timestamp 的列

              ConcurrencyCheck:指定字段为用于乐观并发检查的字段,为了简单,建议同时将此字段也标记为Timestamp

 

CodeFirstDbContext实现:


/// <summary>
    /// 自定义类必须继承自DbContext,DbContext来自EF4.1的EntiyFramework.dll这个程序集
    /// </summary>
    public class CodeFirstDbContext:DbContext
    {
        public DbSet<GameUser> GameUsers { get; set; }
        public DbSet<GameRole> GameRoles { get; set; }

        public CodeFirstDbContext()
        {

        }

        public CodeFirstDbContext(string conn):base(conn)  
        {
            //是否启用延迟加载:
            //  true:   延迟加载(Lazy Loading):获取实体时不会加载其导航属性,一旦用到导航属性就会自动加载
            //  false:  直接加载(Eager loading):通过 Include 之类的方法显示加载导航属性,获取实体时会即时加载通过 Include 指定的导航属性
            this.Configuration.LazyLoadingEnabled = true;   

            this.Configuration.AutoDetectChangesEnabled = true;  //自动监测变化,默认值为 true

        }

        /// <summary>
        /// 实体到数据库结构的映射是通过默认的约定来进行的,如果需要修改的话,有两种方式,分别是:Data Annotations 和 Fluent API
        //  以下示范通过 Fluent API 来修改实体到数据库结构的映射
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<GameRole>()
                    .Property(p => p.GameRoleID)
                    .HasColumnName("GameRoleID")//设置映射的表字段名
                    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)//设置映射字段的值生成方式为标识列
                    .IsRequired();//设置字段值是必须的


            modelBuilder.Entity<GameRole>()
                    .Property(p => p.GameRoleName)
                    .HasMaxLength(32)//字段长度
                    .IsOptional()//字段的值可以为空
                    .IsUnicode()//字段值类型为nvarchar
                    .IsVariableLength();//字段长度是可变的


            base.OnModelCreating(modelBuilder);
                    
        }
    }

使用示范:

/// <summary>
        /// 创建数据库
        /// </summary>
        static void Demo4()
        {
            using (CodeFirstDbContext db = new CodeFirstDbContext())
            {
                if (db.Database.Exists())
                {
                    Console.WriteLine("数据库已经存在,您是否删除数据库重新创建(yes/no)?");
                    if (Console.ReadLine().ToLower() == "yes")
                    {
                        Console.WriteLine("准备删除数据库");
                        db.Database.Delete();
                        Console.WriteLine("准备创建数据库");
                        db.Database.Create();
                    }
                }
            }
        }

        /// <summary>
        /// 添加数据
        /// </summary>
        static void Demo5()
        {
            using (EFLabCodeFirst.CodeFirstDbContext db = new CodeFirstDbContext())
            {
                db.GameUsers.Add(new GameUser { GameUserID = "GU0001", GameUserName = "飞影", GameUserPassword = "123", GameUserTime = DateTime.Now, GameUserArchive = new GameUserArchive { IdentityCard = "123123123123123123", HomeAddress = "兰台路", ContactPhone = "111" } });
                db.GameUsers.Add(new GameUser { GameUserID = "GU0002", GameUserName = "藏龙", GameUserPassword = "112", GameUserTime = DateTime.Now, GameUserArchive = new GameUserArchive { IdentityCard = "112233221122332211", HomeAddress = "科华路", ContactPhone = "222" } });
                GameUser user3 = new GameUser { GameUserID = "GU0003", GameUserName = "卧虎", GameUserPassword = "212", GameUserTime = DateTime.Now, GameUserArchive = new GameUserArchive { IdentityCard = "111222333444444444", HomeAddress = "仙桥路", ContactPhone = "333" } };
                db.GameUsers.Add(user3);
                

                db.GameRoles.Add(new GameRole { GameRoleName = "火影", GameUserID = "GU0001" });//采用GameUserID关联一个账户ID
                db.GameRoles.Add(new GameRole { GameRoleName = "飞天", GameUserID = "GU0001" });//采用GameUserID关联一个账户ID
                db.GameRoles.Add(new GameRole { GameRoleName = "小刀", GameUserID = "GU0002" });//采用GameUserID关联一个账户ID
                db.GameRoles.Add(new GameRole { GameRoleName = "飞虎", GameUserID = "GU0003" });//采用GameUserID关联一个账户ID
                
                db.SaveChanges();
                

            }
        }

        /// <summary>
        /// 查询所有账户和角色
        /// </summary>
        static void Demo6()
        {
            using (CodeFirstDbContext db = new CodeFirstDbContext())
            {
                db.GameUsers.Include("GameRoles").AsEnumerable()//Include:预加载关联的对象
                        .Select(u => {
                            Console.WriteLine(u.GameUserID+"\t"+u.GameUserName);
                            u.GameRoles.AsEnumerable().Select(r => {
                                Console.WriteLine("\t"+r.GameRoleID+"\t"+r.GameRoleName);
                                return r;
                            }).Count();
                            return u;
                        }).Count();
            }
        }

        //以下内容摘自别人博客,时间紧张这里不再示范:
        /* 
                 DbContext API常用方法
                    db.GameUsers.Find() - 通过传递主键值作为参数查找实体,复合主键就传多个参数
                    db.GameUsers.Add() - 把一个新增的实体添加到上下文
                    db.GameUsers.Attach() - 把一个已存在的实体添加到上下文
                    db.Entry(entity).State = System.Data.EntityState.Modified - 修改实体状态
                    db.GameUsers.AsNoTracking() - 不被 Context 跟踪,通过 NoTracking 获取的实体,其状态是 Detached 状态。当仅仅是获取数据的时候可以用,有助于提高效率
                    
                    属性的相关操作,当属性改变时,会自动监测实体状态,即 IsModified = true  

                    db.Entry(GameUser).Property(p => p.GameUserName).CurrentValue
                    db.Entry(GameUser).Property("GameUserName").CurrentValue  

                 直接加载(Eager loading)的方法

                     db.GameUsers.Include(p => p.GameRoles.First())  

                     db.GameUsers.Include(p => p.GameRoles)  

                     db.Entry(GameRole).Reference(p => p.GameUser).Load()  

                 使用 sql 

                     db.GameUsers.SqlQuery("select * from GameUsers").ToList() // 有实体的情况  

                     db.Database.SqlQuery<string>("select GameUserName from GameUsers").ToList(); // 无实体的情况  

                     db.Database.ExecuteSqlCommand(commandText); // 直接执行 sql  

                 
        */


生成的表结构如下:




你可能感兴趣的:(游戏,数据库,String,user,entityframework,Annotations)