前言
之前有學過EF一段時間那時EF才4.0似乎還不太穩定,而現在EF都已7.0版本,同時AspNet Identity都與此大有關聯,看來是大勢所趨於是開始學習EF,在學EF過程中也遇到一些小問題,特此錄下,以備忘!
數據庫和表基本創建
為了更好的循序漸進稍微概括下典型創建EF Code First過程(看之即懂,懂即略過)
第一步先定義兩個類,如下:
public class Student { public Student() { } public int StudentID { get; set; } public string StudentName { get; set; } } public class Standard { public Standard() { } public int StandardId { get; set; } public string StandardName { get; set; } }
第二步:繼承EF上下文DbContext
public class SchoolContext : DbContext { public SchoolContext():base("name=DBConnectionString") { } public DbSet<Student> Students { get; set; } public DbSet<Standard> Standards { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } }
學生上下文中構造函數中的name去讀取如下配置文件來命名數據庫名稱: DBByConnectionString
<add name="DBConnectionString" connectionString="Data Source=.;Initial Catalog=DBByConnectionString;Integrated Security=true" providerName="System.Data.SqlClient"/> </connectionStrings>
然后在控制台中通過EF上下文添加數據並保存,如下:
using (var ctx = new SchoolContext()) { Student stud = new Student() { StudentName = "New Student" }; ctx.Students.Add(stud); ctx.SaveChanges(); }
最終生成數據庫以及表如下圖:
上述創建數據庫的過程只需注意:可以手動通過添加構造函數的name來命名數據庫名稱或者無需添加name那么生成的數據庫名稱是以上下文中的命名空間+上下文類來命名數據庫名稱。
數據庫創建以及表一勞永逸配置
下面創建方法是看過園友hystar(EF教程)而寫的,確實是好方法,就搬過來了,為什么說一勞永逸呢?不明白的話,可以去看看他的文章!首先添加兩個類Student(學生類)和Course(課程類)。
public class Student { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } public virtual Course Course { get; set; } } public class Course { public int StudentID { get; set; } public string Name { get; set; } public virtual Student Student { get; set; } }
添加EFDbContext類並繼承DbContext上下文,代碼如下:
public class EntityDbContext : DbContext { public EntityDbContext() : base("name=test2") { } /// <summary> /// 通過反射一次性將表進行映射 /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { var typesRegister = Assembly.GetExecutingAssembly().GetTypes() .Where(type => !(string.IsNullOrEmpty(type.Namespace))).Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); foreach (var type in typesRegister) { dynamic configurationInstance = Activator.CreateInstance(type); modelBuilder.Configurations.Add(configurationInstance); } } }
由於是手動命名數據庫名稱,當然得讀取配置文件
<connectionStrings> <add name="test2" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=test2;Integrated Security=true" providerName="System.Data.SqlClient"/> </connectionStrings>
上述配置要添加的數據庫建立在VS2013自帶的實例中!我們首先初始化數據庫看看:
EntityDbContext ctx = new EntityDbContext();
結果運行就出現如下經典錯誤:
在與SQLServer建立連接時出現與網絡相關的或特定與實例的錯誤.未找到或無法訪問服務器.請驗證實例名稱是否正確並且SQL SERVER已配置允許遠程鏈接provide:命名管道提供程序,error:40 -無法打開到SQL Server的連接)
那肯定是無法連接到 (localdb)\v11.0 ,於是當我在服務器打開添加連接中添加服務器名為 (localdb)\v11.0 時也是無法響應,連接不到!最終通過SqlLocalDB命令在Command Prompt(命令行)中輸入
SqlLocalDB.exe start v11.0
啟動該實例才算完事,主要原因是安裝了SQL 2012默認啟動的實例該SQL 2012而VS 2013中的實例被停止運行得手動啟動,如果要查看其信息來查看是否已經啟動,通過以下命令即可:
SqlLocalDB.exe info v11.0
VS2013中默認的實例應該是(localdb)\v11.0,如果在服務器中添加連接輸入(localdb)\v11.0是錯誤的,你可以通過上述 SqlLocalDB.exe info v11.0 命令復制並添加如圖的字符串即可
似乎只要第一次啟動了,以后每次都會連接上,不會再出現如上問題!
上述中我們對於EF上下文不用每次都初始化數據庫,在EF中初始化數據庫有三種策略:
CreateDatabaseIfNotExists:該項也是默認初始化數據庫的一項,要是數據庫不存在就創建數據庫。
DropCreateDatabaseIfModelChanges:只要數據模型發生了改變就重新創建數據庫。
DropCreateDatabaseAlways:只要每次初始化上下文時就創建數據庫。
鑒於此我們在EFDbContext中采用第二種策略。創建一個初始化類的策略 EFDbContextInit
/// <summary> /// 當對象實體對象發生改變時重生創建數據庫 /// </summary> public class EntityDbContextInit : DropCreateDatabaseIfModelChanges<EntityDbContext> { protected override void Seed(EntityDbContext context) { base.Seed(context); } }
在EFDbContext靜態構造函數中進行初始化此方法:
static EntityDbContext() { Database.SetInitializer<EntityDbContext>(new EntityDbContextInit()); }
自此EFDbContext構建完畢!下面就是模型映射了,我們假設學生和課程是1:1關系,則我們添加的兩個實體映射如下:
StudentMap(學生類實體映射)
public class StudentMap : EntityTypeConfiguration<Student> { public StudentMap() { ToTable("Student"); HasKey(d => d.ID); //HasRequired(p => p.Course).WithRequiredDependent(i => i.Student); //HasRequired(p => p.Course).WithOptional(i => i.Student); HasRequired(p => p.Course).WithRequiredPrincipal(p => p.Student); HasOptional(p => p.Course).WithRequired(p => p.Student);
/*
對於上述映射關系不太理解的話可以去上述給出鏈接文章。我只說明怎么去很好的理解這兩組的意思,第一組 WithRequiredDependent 和第二組
WithRequiredPrincipal 一個是Dependent是依賴的意思說明后面緊接着的Student是依賴對象,而前面的Course是主體,而Principal
首先的意思,說明后面緊接着的是Student是主體,而Course是依賴對象。很顯然在這個關系中課程是依賴學生的。所以映射選第二組
*/
}
}
CourseMap(課程類映射)
public class CourseMap : EntityTypeConfiguration<Course> { public CourseMap() { ToTable("Course"); HasKey(p => p.StudentID); } }
接下來我們進行添加數據並保存通過如下代碼:
EntityDbContext ctx = new EntityDbContext(); var s = new Student() { Name = "1", Age = 12, Course = new Course() { Name = "12" } }; ctx.Set<Student>().Add(s); ctx.SaveChanges();
數據添加和保存都已通過,接下來進行查詢數據,查詢數據有兩種方式:
(1)直接通過EF中Set()方法獲得數據集合
(2)通過EF中SqlQuery()方法通過sql語句查詢
如要獲得上述學生數據列表集合,可以通過如下操作:
EntityDbContext ctx = new EntityDbContext(); var list = ctx.Set<Student>().ToList(); 或者 SqlParameter[] parameter = { }; var list = ctx.Database.SqlQuery<Student>("select * from student", parameter);
於是我監視下返回的list集合中的數據類型,如圖
oh,shit!和我們實際的實體類型不符,通過EF產生的卻是 DynamicProxies ,於是到Sytem.Data.Entity類下去看看是個什么類型,居然沒找到,估計看這單詞意思就是運行時產生的動態代理對象。那么,你覺得是不是沒什么影響了???那影響可大了,請看下面操作:
var list = ctx.Set<Student>().ToList(); var jsonString = JsonConvert.SerializeObject(list);
我嘗試將其序列化看看結果,一運行,oh,no!錯誤如下:
這意思是檢測到在Course里面有Student屬性,而Student類里又有Course這就相當於自己引用自己,導致了循環引用就成了死循環。(這就是因為 DynamicProxies 導致的結果)所以當前要將其代理對象轉換為我們的實體對象即可。
則通過Select()方法投影將其代碼進行改造后如下:
var list = ctx.Set<Student>().Include(p => p.Course).ToList().Select(entity => new Student() { ID = entity.ID, Name = entity.Name, Age = entity.Age }).ToList(); 或者 var list = ctx.Set<Student>().Include("Course").ToList().Select(entity => new Student() { ID = entity.ID, Name = entity.Name, Age = entity.Age }).ToList();
對象轉換成功,如下:
序列化成功結果如下:
【注意】你用EF獲得數據集合后得 ToList() 因為此時集合對象為代理對象,否則進行轉換將報錯,代碼如下:
var list = ctx.Set<Student>().Include(p => p.Course).Select(entity => new Student() { ID = entity.ID, Name = entity.Name, Age = entity.Age }).ToList();
報錯如下:
上述轉換也叫DTO(Data Transfer Objects)數據轉換為對象,像這種情況在EF中很常見。下面給出老外的用兩張圖在兩個常見的場景中來展現關於DTO的概念:
Getting Information: DAL=>BLL=>GUI
Insert Information: GUI=>BLL=>DAL
總結
(1)當安裝了sql時則默認啟動的是此實例,那么VS中的實例則會停止啟動,需要通過SqlLocalDB命令進行啟動。
(2)通過EF獲得的數據集合對象為代理對象,需要先轉換為實體對象才能進行序列化或者其他操作。
補充
在此感謝園友中華小鷹,經其提示用上述一勞永逸配置無法配置復雜類型!
modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly());
通過上述代碼既能配置實體類型也能配置復雜類型,用此方法更加精簡!當然若你將復雜類型作為另一個類的導航屬性時上述代碼也是可以滿足所需的!