sql注入pythonpoco_.NET EF(Entity Framework)详解
一丶Entity Framework
(一)EF簡介
(1)ORM:Object Relation Mapping ,通俗說:用操作對象的方式來操作數據庫。
(2)插入數據庫不再是執行Insert,而是類似于Person p = new Person();p.Age=3;p.Name=“英萊特”;db.Save§;這樣的做法。
(3)ORM工具有很多Dapper、PetaPoco、NHibernate,最首推的還是微軟官方的Entity Framework,簡稱EF。
(4)EF底層仍然是對ADO.Net的封裝。EF支持SQLServer、MYSQL、Oracle、Sqlite等所有主流數據庫。
(5)使用EF進行數據庫開發的時候有兩個東西建:建數據庫(T_Persons),建模型類(Person)。根據這兩種創建的先后順序有EF的三種創建方法:
DataBase First(數據庫優先):先創建數據庫表,然后自動生成EDM文件,EDM文件生成模型類。簡單展示一下DataBase First 的使用。
Model First(模型優先):先創建Edm文件,Edm文件自動生成模型類和數據庫;
Code First(代碼優先):程序員自己寫模型類,然后自動生成數據庫。沒有Edm。
DataBase First 簡單、方便,但是當項目大了之后會非常痛苦;
Code First 入門門檻高,但是適合于大項目。
Model First……
無論哪種First,一旦創建好了數據庫、模型類之后,后面的用法都是一樣的。業界都是推薦使用Code First,新版的EF中只支持Code First,因此我們這里只講Code First。
(6)Code First的微軟的推薦用法是程序員只寫模型類,數據庫由EF 幫我們生成,當修改模型類之后,EF 使用“DB Migration”自動幫我們更改數據庫。但是這種做法太激進,不適合很多大項目的開發流程和優化,只適合于項目的初始開發階段。Java的Hibernate 中也有類似的DDL2SQL 技術,但是也是用的較少?!癉B Migration”也不利于理解EF,因此在初學階段,我們將會禁用“DB Migration”,采用更實際的“手動建數據庫和模型類”的方式。
(7)如果大家用過 NHibernate 等ORM 工具的話,會發現開發過程特別麻煩,需要在配置文件中指定模型類屬性和數據庫字段的對應關系,哪怕名字完全也一樣也要手動配置。使用過Java 中Struts、Spring 等技術的同學也有過類似“配置文件地獄”的感覺。 像ASP.Net MVC 一樣,EF 也是采用“約定大于配置”這樣的框架設計原則,省去了很多配置,能用約定就不要自己配置。
在.Net Framework SP1微軟包含一個實體框架(Entity Framework),此框架可以理解成微軟的一個ORM產品。用于支持開發人員通過對概念性應用程序模型編程(而不是直接對關系存儲架構編程)來創建數據訪問應用程序。目標是降低面向數據的應用程序所需的代碼量并減輕維護工作。
(二)Entity Framework應用程序有以下優點:
(1)應用程序可以通過更加以應用程序為中心的概念性模型(包括具有繼承性、復雜成員和關系的類型)來工作。
(2)應用程序不再對特定的數據引擎或存儲架構具有硬編碼依賴性。
(3)可以在不更改應用程序代碼的情況下更改概念性模型與特定于存儲的架構之間的映射。
(4)開發人員可以使用可映射到各種存儲架構(可能在不同的數據庫管理系統中實現)的一致的應用程序對象模型。
(5)多個概念性模型可以映射到同一個存儲架構。
(6)語言集成查詢支持可為查詢提供針對概念性模型的編譯時語法驗證。
實體框架Entity Framework是 DO.NET中的一組支持開發面向數據的軟件應用程序的技術。在EF中的實體數據模型(EDM)由以下三種模型和具有相應文件擴展名的映射文件進行定義。
概念架構定義語言文件 (.csdl) – 定義概念模型。
存儲架構定義語言文件 (.ssdl) – 定義存儲模型(又稱邏輯模型)。
映射規范語言文件 (.msl) – 定義存儲模型與概念模型之間的映射。
實體框架使用這些基于XML的模型和映射文件將對概念模型中的實體和關系的創建、讀取、更新和刪除操作轉換為數據源中的等效操作。EDM甚至支持將概念模型中的實體映射到數據源中的存儲過程。它提供以下方式用于查詢 EDM 并返回對象:
LINQ to Entities–提供語言集成查詢(LINQ)支持用于查詢在概念模型中定義的實體類型。
Entity SQL – 與存儲無關的SQL方言,直接使用概念模型中的實體并支持諸如繼承和關系等 EDM 功能。
查詢生成器方法 --可以使用LINQ風格的查詢方法構造 Entity SQL 查詢。
(三)相關知識復習
(1)var類型推斷:var p =new Person();
(2)匿名類型。var a =new {p.Name,Age=5,Gender=p.Gender,Name1=a.Name};//{p.Name}=={Name=p.Name}
(3)給新創建對象的屬性賦值的簡化方法:Person p = new Person{Name=“tom”,Age=5};等價于Person p = new Person();p.Name=“tom”;p.Age=5;
(四)lambda表達式:
函數式編程,在Entity framework編程中用的很多
Actional= delegate(int i) { Console.Writeline(i); };
可以簡化成(=>讀作goes to) :
Action< int> a2 = (inti) = > { Console.Writeline(i); };
還可以省略參數類型(編譯器會自動根據委托類型推斷):
Action< int> a3 = (i) = > { Console.Writeline(i); };
如果只有一個參數還可以省略參數的小括號(多個參數不行)
Actiona4 = i = > { Console.Writeline(i); };
如果委托有返回值,并且方法體只有一行代碼,這一行代碼還是返回值,那么就可以連方法的大括號和return都省略:
Funcfl= delegate(int i, int j) { return "結果是" + (i + j); };
Funcf2= (i,j)=>"結果是"+ (i+ j);
(五)集合常用擴展方法
where (支持委托)、Select (支持委托)、Max 、Min 、OrderBy
First (獲取第一個,如果一個都沒有則異常)
FirstOrDefault (獲取第一個,如果—個都沒有則返回默認值)
Single (獲取唯一一個,如果沒有或者有多個則異常)
SingleOrDefoult (獲取唯一一個, 如果沒有則返回默認值,如果有多個則異常)
注意lambda中照樣要避免變量重名的問題:var p =persons.Where(p => p.Name ==“yltedu.com”).First();
(六)高級集合擴展方法
//學生
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public bool Gender { get; set; }
public int Salary { get; set; }
public override string ToString()
{
return string.Format("Name={0},Age={1},Gender={2},Salary={3}",Name, Age, Gender, Salary);
}
}
//老師
public class Teacher
{
public Teacher()
{
this.Students=new List();
}
public string Name { get; set; }
public ListStudents { get; set; }
}
var s0 =new Person { Name="tom",Age=3,Gender=true,Salary=6000};
var s1 = new Person { Name = "jerry", Age = 8, Gender = true, Salary = 5000 };
var s2 = new Person { Name = "jim", Age = 3, Gender = true, Salary = 3000 };
var s3 = new Person { Name = "lily", Age = 5, Gender = false, Salary = 9000 };
var s4 = new Person { Name = "lucy", Age = 6, Gender = false, Salary = 2000 };
var s5 = new Person { Name = "kimi", Age = 5, Gender = true, Salary = 1000 };
Listlist = new List();
list.Add(s0);
list.Add(s1);
list.Add(s2);
list.Add(s3);
list.Add(s4);
list.Add(s5);
Teacher t1 = new Teacher { Name="英萊特.net"};
t1.Students.Add(s1);
t1.Students.Add(s2);
Teacher t2 = new Teacher { Name = "英萊特Python" };
t2.Students.Add(s2);
t2.Students.Add(s3);
t2.Students.Add(s5);
Teacher[] teachers = { t1,t2};
(1)Any(),判斷集合是否包含元素,返回值是bool,一般比Cout()>0 效率高。Any還可以指定條件表達式。
bool b = list.Any(p => p.Age > 50); 等價于bool b =list.Where(p=>p.Age>50).Any();
(2)Distinct(),剔除完全重復數據。(*)注意自定義對象的Equals 問題:需要重寫Equals 和GetHashCode 方法來進行內容比較。
(3)排序:升序list.OrderBy(p=>p.Age);降序list.OrderByDescending(p=>p.Age)。指定多個排序規 則,而不是多個OrderBy,而是:list.OrderByDescending(p=>p.Age).ThenBy(p=>p.Salary),也支 持ThenByDescending()。注意這些操作不會影響原始的集合數據。
(4)Skip(n)跳過前n條數據;
(5)Take(n)獲取最多n條數據,如果不足n條也不會報錯。常用來分頁獲取數據。
(6)list.Skip(3).Take(2)跳過前3條數據獲取2條數據。
(7)Except(items1)排除當前集合中在items1中存在的元素。用int數組舉例。
(8)Union(items1)把當前集合和items1中組合。用int 數組舉例。
(9)Intersect(items1) 把當前集合和items1 中取交集。用int 數組舉例。
(10)分組:
foreach(var g in list.GroupBy(p => p.Age))
{
Console.WriteLine(g.Key+":"+g.Average(p=>p.Salary));
}
(11)SelectMany:把集合中每個對象的另外集合屬性的值重新拼接為一個新的集合
foreach(var s in teachers.SelectMany(t => t.Students))
{
Console.WriteLine(s);//每個元素都是Person
}
}
注意不會去重,如果需要去重要自己再次調用Distinct()
(12)Join
class Master
{
public long Id { get; set; }
public string Name { get; set; }
}
class Dog
{
public long Id { get; set; }
public long MasterId { get; set; }
public string Name { get; set; }
}
Master m1 = new Master { Id = 1, Name = "英萊特" };
Master m2 = new Master { Id = 2, Name = "比爾蓋茨" };
Master m3 = new Master { Id = 3, Name = "周星馳" };
Master[] masters = { m1,m2,m3};
Dog d1 = new Dog { Id = 1, MasterId = 3, Name = "旺財" };
Dog d2 = new Dog { Id = 2, MasterId = 3, Name = "汪汪" };
Dog d3 = new Dog { Id = 3, MasterId = 1, Name = "京巴" };
Dog d4 = new Dog { Id = 4, MasterId = 2, Name = "泰迪" };
Dog d5 = new Dog { Id = 5, MasterId = 1, Name = "中華田園" };
Dog[] dogs = { d1, d2, d3, d4, d5 };
Join 可以實現和數據庫一樣的Join 效果,對有關聯關系的數據進行聯合查詢 下面的語句查詢所有Id=1 的狗,并且查詢狗的主人的姓名。
var result = dogs.Where(d => d.Id > 1).Join(masters, d => d.MasterId, m => m.Id,(d,m)=>new {DogName=d.Name,MasterName=m.Name});
foreach(var item in result)
{
Console.WriteLine(item.DogName+","+item.MasterName);
}
(七)EF 的安裝
(1)基礎階段用控制臺項目。使用NuGet 安裝EntityFramework。會自動在App.config中增加兩個entityFramework 相關配置段;
(2)在 web.config 中配置連接字符串
易錯點:不能忘了寫providerName="System.Data.SqlClient"增加兩個entityFramework 相關配置段;
(八)EF 簡單DataAnnotations 實體配置
(1)數據庫中建表T_Perons,有Id(主鍵,自動增長)、Name、CreateDateTime字段。
(2)創建Person類[Table(“T_Persons”)]因為類名和表名不一樣,所以要使用Table標注
[Table("T_Persons")]
public class Person
{
public long ID { get; set; }
public string Name { get; set; }
public DateTime CreateTime { get; set; }
}
因為EF約定主鍵字段名是Id,所以不用再特殊指定Id是主鍵,如果非要指定就指定[Key]。因為字段名字和屬性名字一致,所以不用再特殊指定屬性和字段名的對應關系,如果需要特殊指定,則要用[Column(“Name”)]
(*)必填字段標注[Required]、字段長度[MaxLength(5)]、可空字段用int?、如果字段在數據庫有默認值,則要在屬性上標注[DatabaseGenerated]注意實體類都要寫成public,否則后面可能會有麻煩。
(3)創建DbContext類(模型類、實體類)
public class MyDBContext: DbContext
{
//表示使用連接字符串中名字為conn1 的去連接數據庫
public MyDBContext() : base("name=strcon")
{
}
//通過對Persons 集合的操作就可以完成對T_Persons的操作
public DbSetPersons { get; set; }
}
(4)測試
protected void Button1_Click(object sender, EventArgs e)
{
MyDBContext context = new MyDBContext();
Person p=new Person();
p.Name =TextBox1.Text;
p.CreateTime = DateTime.Now;
context.Persons.Add(p);
context.SaveChanges();
}
注意:MyDbContext 對象是否需要using有爭議,不using也沒事。每次用的時候new MyDbContext就行,不用共享同一個實例,共享反而會有問題。SaveChanges()才會把修改更新到數據庫中。
EF的開發團隊都說要using DbContext,很多人不using,只是想利用LazyLoad 而已,但是那樣做是違反分層原則的。我的習慣還是using。
異常的處理:如果數據有錯誤可能在SaveChanges()的時候出現異常,一般仔細查看異常信息或者一直深入一層層的鉆InnerException 就能發現錯誤信息。
舉例:創建一個Person對象,不給Name、CreateDateTime賦值就保存。
二丶 EF 模型的兩種配置方式
EF 中的模型類的配置有DataAnnotations、FluentAPI 兩種。
上面這種在模型類上[Table(“T_Persons”)]、[Column(“Name”)]這種方式就叫DataAnnotations這種方式比較方便,但是耦合度太高,一般的類最好是POCO(Plain Old C# Object,沒有繼承什么特殊的父類,沒有標注什么特殊的Attribute,沒有定義什么特殊的方法,就是一堆普通的屬性);不符合大項目開發的要求。微軟推薦使用FluentAPI 的使用方式,因此后面主要用FluentAPI 的使用方式。
(一) FluentAPI 配置T_Persons 的方式
(1)數據庫中建表T_Perons,有Id(主鍵,自動增長)、Name、CreateDateTime 字段。
(2)創建 Person 類。模型類就是普通C#類
public class Person
{
public long ID { get; set; }
public string Name { get; set; }
public DateTime CreateTime { get; set; }
}
(3)創建一個 PersonConfig 類,放到ModelConfig 文件夾下(PersonConfig、EntityConfig這樣的名字都不是必須的)
public class PersonConfig : EntityTypeConfiguration{
public PersonConfig()
{
this.ToTable("T_Person");
}
}
(4)創建 DbContext 類
public class MyDBContext:DbContext
{
public MyDBContext() : base("name=strcon")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly());
}
public DbSetPersons { get; set; }
}
下面這句話:
modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly());
代表從這句話所在的程序集加載所有的繼承自EntityTypeConfiguration 為模型配置類。還有很多加載配置文件的做法(把配置寫到OnModelCreating中或者把加載的代碼寫死到OnModelCreating 中),但是這種做法是最符合大項目規范的做法。
和以前唯一的不同就是:模型不需要標注Attribute;編寫一個XXXConfig類配置映射關系;DbContext 中override OnModelCreating;
(5)測試
protected void Button1_Click(object sender, EventArgs e)
{
MyDBContext context = new MyDBContext();
Person p = new Person();
p.Name = TextBox1.Text;
p.CreateTime = DateTime.Now;
context.Persons.Add(p);
context.SaveChanges();
}
(二)EF 的基本增刪改查
獲取DbSet除了可以ctx.Persons之外,還可以ctx.Set()。
(1)增加,一個點:如果Id是自動增長的,創建的對象顯然不用指定Id的值,并且在SaveChanges ()后會自動給對象的Id屬性賦值為新增行的Id字段的值。
(2)刪除。先查詢出來要刪除的數據,然后Remove。這種方式問題最少,雖然性能略低,但是刪除操作一般不頻繁,不用考慮性能。后續在“狀態管理”中會講其他實現方法。
MyDBContext context = new MyDBContext();
if (e.CommandName=="BtnDelete")
{
int id = Convert.ToInt32(e.CommandArgument);
var p = context.Persons.Where(per => per.ID == id).SingleOrDefault();
if (p!=null)
{
context.Persons.Remove(p);
}
int i= context.SaveChanges();
if (i>0)
{
Repeater1.DataSource = context.Persons.ToList();
Repeater1.DataBind();
}
}
怎么批量刪除,比如刪除Id>3 的?查詢出來一個個Remove。性能坑爹。如果操作不頻繁或者數據量不大不用考慮性能,如果需要考慮性能就直接執行sql 語句
(3)修改:先查詢出來要修改的數據,然后修改,然后SaveChanges()
MyDbContext ctx = new MyDbContext();
var ps = ctx.Persons.Where(p => p.Id > 3);
foreach(var p in ps)
{
p.CreateDateTime = p.CreateDateTime.AddDays(3);
p.Name = "haha";
}
ctx.SaveChanges();
性能問題?同上。
(4)查。因為DbSet 實現了IQueryable 接口,而IQueryable 接口繼承了IEnumerable 接口,所以可以使用所有的linq、lambda 操作。給表增加一個Age 字段,然后舉例orderby、groupby、where 操作、分頁等。一樣一樣的。
(5)查詢 order by 的一個細節
EF調用Skip之前必須調用OrderBy:如下調用var items = ctx.Persons.Skip(3).Take(5); 會報錯“The method ‘OrderBy’ must be called before the method ‘Skip’.)”,要改成:var items = ctx.Persons.OrderBy(p=>p.CreateDateTime).Skip(3).Take(5);
這也是一個好習慣,因為以前就發生過(寫原始sql):分頁查詢的時候沒有指定排序規則,以為默認是按照Id 排序,其實有的時候不是,就造成數據混亂。寫原始SQL 的時候也要注意一定要指定排序規則。
(三)EF 原理及SQL 監控
EF 會自動把Where()、OrderBy()、Select()等這些編譯成“表達式樹(Expression Tree)”,然后會把表達式樹翻譯成SQL 語句去執行。(編譯原理,AST)因此不是“把數據都取到內存中,然后使用集合的方法進行數據過濾”,因此性能不會低。但是如果這個操作不能被翻譯成SQL語句,則或者報錯,或者被放到內存中操作,性能就會非常低。
(1)怎么查看真正執行的SQL是什么樣呢?
DbContext有一個Database屬性,其中的Log屬性,是Action委托類型,也就是可以指向一個void A(string s)方法,其中的參數就是執行的SQL語句,每次EF執行SQL語句的時候都會執行Log。因此就可以知道執行了什么SQL。
EF的查詢是“延遲執行”的,只有遍歷結果集的時候才執行select 查詢,ToList()內部也是遍歷結果集形成List。
查看Update操作,會發現只更新了修改的字段。
(2)觀察一下前面學學習時候執行的SQL是什么樣的。Skip().Take()被翻譯成了?Count()被翻譯成了?
var result = ctx.Persons.Where(p => p.Name.StartsWith("inlett"));//看看翻譯成了什么?
var result = ctx.Persons.Where(p => p.Name.Contains("com"));
var result = ctx.Persons.Where(p => p.Name.Length>5);
var result = ctx.Persons.Where(p => p.CreateDateTime>DateTime.Now);
long[] ids = { 2,5,6};//不要寫成int[]
var result = ctx.Persons.Where(p => ids.Contains(p.Id));
(3)EF中還可以多次指定where來實現動態的復合檢索:
//必須寫成IQueryable,如果寫成IEnumerable 就會在內存中取后續數據
IQueryableitems = ctx.Persons;//為什么把IQueryable換成var 會編譯出錯
items = items.Where(p=>p.Name=="inlett");
items = items.Where(p=>p.Id>5);
查看一下生成的SQL語句。
(4)EF是跨數據庫的,如果遷移到MYSQL上,就會翻譯成MYSQL的語法。要配置對應數據庫的Entity Framework Provider。
(5)細節:
每次開始執行的__MigrationHistory等這些SQL語句是什么?是DBMigration用的,也就是由EF幫我們建數據庫,現在我們用不到,用下面的代碼禁用:
Database.SetInitializer(null);
XXXDbContext就是項目DbContext的類名。一般建議放到XXXDbContext構造函數中。注意這里的Database 是System.Data.Entity下的類,不是DbContext的Database屬性。如果寫到DbContext中,最好用上全名,防止出錯。
(四)執行原始SQL
不要“手里有錘子,到處都是釘子”在一些特殊場合,需要執行原生SQL。
執行非查詢語句,調用DbContext的Database屬性的ExecuteSqlCommand方法,可以通過占位符的方式傳遞參數:
ctx.Database.ExecuteSqlCommand("update T_Persons set Name={0},CreateDateTime=GetDate()","YLT.com");
占位符的方式不是字符串拼接,經過觀察生成的SQL語句,發現仍然是參數化查詢,因此不會有SQL注入漏洞。
執行查詢:
var q1 = ctx.Database.SqlQuery("select Name,Count(*) Count from T_Persons where Id>{0} and CreateDateTime<={1} group by Name",2, DateTime.Now); //返回值是DbRawSqlQuery類型,也是實現IEnumerable 接口
foreach(var item in q1)
{
Console.WriteLine(item.Name+":"+item.Count);
}
class Item1
{
public string Name { get; set; }
public int Count { get; set; }
}
類似于ExecuteScalar的操作比較麻煩:
int c = ctx.Database.SqlQuery("select count(*) from T_Persons").SingleOrDefault();
(五)不是所有lambda 寫法都能被支持
下面想把Id轉換為字符串比較一下是否為"3"(別管為什么):
var result = ctx.Persons.Where(p => Convert.ToString(p.Id)=="3");
運行會報錯(也許高版本支持了就不報錯了),這是一個語法、邏輯上合法的寫法,但是EF目前無法把他解析為一個SQL語句。
出現“System.NotSupportedException”異常一般就說明你的寫法無法翻譯成SQL語句
想獲取創建日期早于當前時間一小時以上的數據:
var result = ctx.Persons.Where(p => (DateTime.Now - p.CreateDateTime).TotalHours>1);
同樣也可能會報錯。
怎么解決?
嘗試其他替代方案(沒有依據,只能亂試):
var result = ctx.Persons.Where(p => p.Id==3);
EF中提供了一個SQLServer專用的類SqlFunctions,對于EF不支持的函數提供了支持,比如:
var result = ctx.Persons.Where(p =>SqlFunctions.DateDiff("hour",p.CreateDateTime,DateTime.Now)>1);
(六)EF對象的狀態
簡介
為什么查詢出來的對象Remove()、再SaveChanges()就會把數據刪除。而自己new一個Person()對象,然后Remove()不行?為什么查詢出來的對象修改屬性值后、再SaveChanges()就會把數據庫中的數據修改。
因為EF會跟蹤對象狀態的改變。
EF中中對象有五個狀態:Detached(游離態,脫離態)、Unchanged(未改變)、Added(新增)、Deleted(刪除)、Modified(被修改)。
Add()、Remove()修改對象的狀態。所有狀態之間幾乎都可以通過:Entry§.State=xxx的方式進行強制狀態轉換。
通過代碼來演示一下。這個狀態轉換圖沒必要記住,了解即可。
狀態改變都是依賴于Id的(Added除外)
應用(*)
當SavaChanged()方法執行期間,會查看當前對象的EntityState的值,決定是去新增(Added)、修改Modified)、刪除(Deleted)或者什么也不做(UnChanged)。下面的做法不推薦,在舊版本中一些寫法不被支持,到新版EF中可能也會不支持。
(1)不先查詢再修改再保存,而是直接更新部分字段的方法
var p = new Person();
p.Id = 2;
ctx.Entry(p).State = System.Data.Entity.EntityState.Unchanged;
p.Name = "adfad";
ctx.SaveChanges();
也可以:
var p = new Person();
p.Id = 5;
p.Name = "yltedu";
ctx.Persons.Attach(p);//等價于ctx.Entry(p).State = System.Data.Entity.EntityState.Unchanged;
ctx.Entry(p).Property(a => a.Name).IsModified = true;
ctx.SaveChanges();
(2)不先查詢再Remove再保存,而是直接根據Id刪除的方法:
var p = new Person();
p.Id = 2;
ctx.Entry(p).State = System.Data.Entity.EntityState.Deleted;
ctx.SaveChanges();
注意下面的做法并不會刪除所有Name=“ylt.com” 的,因為更新、刪除等都是根據Id進行的:
var p = new Person();
p.Name = "yltedu.com";
ctx.Entry(p).State = System.Data.Entity.EntityState.Deleted;
ctx.SaveChanges();
上面其實是在:
delete * from t_persons where Id=0
EF優化的一個技巧
如果查詢出來的對象只是供顯示使用,不會修改、刪除后保存,那么可以使用AsNoTracking()來使得查詢出來的對象是Detached狀態,這樣對對象的修改也還是Detached狀態,EF不再跟蹤這個對象狀態的改變,能夠提升性能。
var p1 = ctx.Persons.Where(p => p.Name == "rupeng.com").FirstOrDefault();
Console.WriteLine(ctx.Entry(p1).State);
改成:
var p1 = ctx.Persons.AsNoTracking().Where(p => p.Name == "rupeng.com").FirstOrDefault();
Console.WriteLine(ctx.Entry(p1).State);
因為AsNoTracking()是DbQuery類(DbSet的父類)的方法,所以要先在DbSet后調用AsNoTracking()。
Fluent API更多配置
基本EF配置只要配置實體類和表、字段的對應關系、表間關聯關系即可。如果利用EF的高級配置,可以達到更多效果:如果數據錯誤(比如字段不能為空、字符串超長等),會在EF層就會報錯,而不會被提交給數據庫服務器再報錯;如果使用自動生成數據庫,也能幫助EF生成更完美的數據庫表。
這些配置方法無論是DataAnnotations、FluentAPI都支持,下面講FluentAPI的用法,DataAnnotations感興趣的自己查(http://blog.csdn.net/beglorious/article/details/39637475)。
盡量用約定,EF配置越少越好。Simple is best 參考資料:http://www.cnblogs.com/nianming/archive/2012/11/07/2757997.html
HasMaxLength設定字段的最大長度
public PersonConfig()
{
this.ToTable("T_Persons");
this.Property(p => p.Name).HasMaxLength(50);//長度為50
}
依賴于數據庫的“字段長度、是否為空”等的約束是在數據提交到數據庫服務器的時候才會檢查;EF的配置,則是由EF來檢查的,如果檢查出錯,根本不會被提交給服務器。
如果插入一個Person對象,Name屬性的值非常長,保存的時候就會報DbEntityValidationException異常,這個異常的Message中看不到詳細的報錯消息,要看EntityValidationErrors屬性的值。
var p = new Person();
p.Name = "非常長的字符串";
ctx.Persons.Add(p);
try
{
ctx.SaveChanges();
}
catch(DbEntityValidationException ex)
{
StringBuilder sb = new StringBuilder();
foreach(var ve in ex.EntityValidationErrors.SelectMany(eve=>eve.ValidationErrors))
{
sb.AppendLine(ve.PropertyName+":"+ve.ErrorMessage);
}
Console.WriteLine(sb);
}
(有用)字段是否可空
this.Property(p => p.Name).IsRequired() 屬性不能為空;
this.Property(p => p.Name).IsOptional() 屬性可以為空;(沒用的雞肋!)
EF默認規則是“主鍵屬性不允許為空,引用類型允許為空,可空的值類型long?等允許為空,值類型不允許為空?!被凇氨M量少配置”的原則:如果屬性是值類型并且允許為null,就聲明成long?等,否則聲明成long等;如果屬性屬性值是引用類型,只有不允許為空的時候設置IsRequired()。
其他一般不用設置的(了解即可)
(1)主鍵:this.HasKey(p => p.pId);
(2)某個字段不參與映射數據庫:this.Ignore(p => p.Name1);
(3)this.Property(p => p.Name).IsFixedLength(); 是否對應固定長度
(4)this.Property(p => p.Name).IsUnicode(false) 對應的數據庫類型是varchar類型,而不是nvarchar
(5)this.Property(p => p.Id).HasColumnName(“Id1”); Id列對應數據庫中名字為Id的字段
(6)this.Property(p=>p.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity) 指定字段是自動增長類型。
流動起來
因為ToTable()、Property()、IsRequired()等方法的還是配置對象本身,因此可以實現類似于StringBuilder的鏈式編程,這就是“Fluent”一詞的含義; 因此下面的寫法:
public PersonConfig ()
{
this. ToTabl e ("T—Persons");
this.HasKey(p => p. Id);
this. Ignore(p => p. Name2);
this.Property(p => p.Name) . HasMaxLength (50);
this. Property (p => p. Name) . I sRequired ();
this.Property(p => p.CreateDateTime) . HasCol umnName ("CreateDateTi me");
this. Property (p => p. Name) . I sRequired () ;
}
可以簡化成:
public PersonConfig()
{
this. ToTable ("T_Persons") . HasKey (p => p. Id). Ignore (p => p. Name2) ;
this. Property (p => p. Name) . HasMaxLength (50). IsRequired O ;
this. Property (p => p. CreateDateTime) . HasColumnName ("CreateDateTime") . IsRequiredO;
}
后面用的時候都Database.SetInitializer(null);
一對多關系映射
EF最有魅力的地方在于對于多表間關系的映射,可以簡化工作。 復習一下表間關系:
(1)一對多(多對一):一個班級對應著多個學生,一個學生對著一個班級。一方是另外一方的唯一。在多端有一個指向一端的外鍵。舉例:班級表:T_Classes(Id,Name) 學生表
T_Students(Id,Name,Age,ClassId)
(2)多對多:一個老師對應多個學生,一個學生對于多個老師。任何一方都不是對方的唯一。 需要一個中間關系表。具體: 學生表T_Students(Id,Name,Age,ClassId) , 老師表 T_Teachers(Id,Name,PhoneNum),關系表T_StudentsTeachers(Id,StudentId,TeacherId)
和關系映射相關的方法:
(1)基本套路this.Has(p=>p.A).With***() 當前這個表和A 屬性的表的關系是Has 定義, With 定義的是A 對應的表和這個表的關系。Optional/Required/Many
(2)HasOptional() 有一個可選的(可以為空的)
(3)HasRequired() 有一個必須的(不能為空的)
(4)HasMany() 有很多的
(5)WithOptional() 可選的
(6)WithRequired() 必須的
(7)WithMany() 很多的
舉例:
在AAA 實體中配置this.HasRequired(p=>p.BBB).WithMany();是什么意思? 在AAA 實體中配置this.HasRequired(p=>p.BBB).WithRequired ();是什么意思?
配置一對多關系
(1)先按照正常的單表配置把Student、Class 配置起來,T_Students 的ClassId 字段就對應Student類的ClassId 屬性。WithOptional()
using (MyDbContext ctx = new MyDbContext ())
{
Class c l = new Class { Name= " 三年二班,, } ;
ctx. Cl asses. Add (cl) ;
ctx. SaveChanges () ;
Student s l = new Student { Age = 11, Nam e = " 張三" , Cl assl d = cl. Id } ;
Student s2 = new Student { Name = " 李四" , Classld = cl. Id } ;
ctx.Students.Add(s1);
ctx. Students. Add(s2);
ctx. SaveChanges O ;
}
(2)給Student類增加一個Class類型、名字為Class(不一定非叫這個,但是習慣是:外鍵名去掉Id)的屬性,要聲明成virtual(后面講原因)。
(3)然后就可以實現各種對象間操作了:
Console.WriteLine(ctx.Students.First().Class.Name)
然后數據插入也變得簡單了,不用再考慮“先保存Class,生成Id,再保存Student”了。這樣就是純正的“面向對象模型”,ClassId 屬性可以刪掉。
Class c1 = new Class { Name = "五年三班" };
ctx.Classes.Add(c1);
Student s1 = new Student { Age = 11, Name = "皮皮蝦"};
Student s2 = new Student { Name = "巴斯"};
s1.Class = c1;
s2.Class = c1;
ctx.Students.Add(s1);
ctx.Students.Add(s2);
ctx.Classes.Add(c1);
ctx.SaveChanges();
(4)如果ClassId 字段可空怎么辦?直接把ClassId 屬性設置為long?
(5)還可以在Class中配置一個public virtual ICollection Students { get; set; } = new List(); 屬性。最好給這個屬性初始化一個對象。注意是virtual。這樣就可以獲得所有指向了當前對象的Stuent 集合,也就是這個班級的所有學生。我個人不喜歡這個屬性,業界的大佬也是建議“盡量不要設計雙向關系”,因為可以通過Class clz = ctx.Classes.First(); var students =ctx.Students.Where(s => s.ClassId == clz.Id);來查詢獲取到,思路更清晰。
不過有了這樣的集合屬性之后一個方便的地方:
Class c1 = new Class { Name = "五年三班" };
ctx.Classes.Add(c1);
Student s1 = new Student { Age = 11, Name = "皮皮蝦" };
Student s2 = new Student { Name = "巴斯" };
c1.Students.Add(s1);//注意要在Students屬性聲明的時候= new List();或者在之前賦值
c1.Students.Add(s2);
ctx.Classes.Add(c1);
ctx.SaveChanges();
EF會自動追蹤對象的關聯關系,給那些有關聯的對象也自動進行處理。
在進行數據遍歷的時候可能會報錯“已有打開的與此 Command 相關聯的 DataReader,必須首先將它關閉。”
foreach(var s in ctx.Students)
{
Console.WriteLine(s.Name);
Console.WriteLine(s.Class.Name);
}
一對多深入:
默認約定配置即可,如果非要配置,可以在StudentConfig 中如下配置:
this.HasRequired(s=> s.Class).WithMany().HasForeignKey(s => s.ClassId);
表示“我需要(Require)一個Class,Class有很多(Many)的Student;ClassId是這樣一個外鍵”。
如果ClassId 可空,那么就要寫成:
this.HasOptional (s => s.Class).WithMany().HasForeignKey(s => s.ClassId);
如果這樣Class clz = ctx.Classes.First();foreach (Student s in clz.Students)訪問,也就是從一端發起對多端的方法,那么就會報錯“找不到Class_Id 字段”需要在ClassConfig中再反向配置一遍 HasMany(e =>e.Students).WithRequired().HasForeignKey(e=>e.ClassId); 因為如果在Class 中引入Students 屬性,還要再在ClassConfig 再配置一遍反向關系,很麻煩。因此再次驗證“不要設計雙向關系”。
如果一張表中有兩個指向另外一個表的外鍵怎么辦?比如學生有“正常班級Class”(不能空)和“小灶班級XZClass”(可以空)兩個班。在StudentConfig 中:
this.HasRequired(s => s.Class).WithMany().HasForeignKey(s => s.ClassId);
this. HasOptional (s => s.XZClass).WithMany().HasForeignKey(s => s.XZClassId);
多對多關系配置
老師和學生:
class Student
{
public long Id { set; get; }
public string Name { get; set; }
public virtual ICollectionTeachers { get; set; }=new List();
}
class Teacher
{
public long Id { set; get; }
public string Name { get; set; }
public virtual ICollectionStudents { get; set; }=new List< Student >();
}
class StudentConfig : EntityTypeConfiguration{
public StudentConfig()
{
ToTable("T_Students");
}
}
class TeacherConfig : EntityTypeConfiguration{
public TeacherConfig()
{
ToTable("T_Teachers");
this.HasMany(e => e.Students).WithMany(e => e.Teachers)//易錯,容易丟了WithMany 的參數
.Map(m =>
m.ToTable("T_TeacherStudentRelations").MapLeftKey("TeacherId").MapRightKey("StudentId"));
}
}
關系配置到任何一方都可以
這樣不用中間表建實體(也可以為中間表建立一個實體,其實思路更清晰),就可以完成多對多映射。當然如果中間關系表還想有其他字段,則要必須為中間表建立實體類。 測試:
Teacher t1 = new Teacher();
t1.Name = "張老師";
t1.Students = new List();
Teacher t2 = new Teacher();
t2.Name = "王老師";
t2.Students = new List();
Student s1 = new Student();
s1.Name = "tom";
s1.Teachers = new List();
Student s2 = new Student();
s2.Name = "jerry";
s2.Teachers = new List();
t1.Students.Add(s1);
附錄:
(1)關于WithMany()的參數
在一對多關系中,如果只配置多端關系并且沒有給WithMany()指定參數的話,在進行反向關系操作的時候就會報錯。要么在一端也配置一次,最好的方法就是還是只配置多端,只不過給WithMany()指定參數:
class StudentConfig:EntityTypeConfiguration{
public StudentConfig()
{
ToTable("T_Students");
this.HasRequired(e => e.Class).WithMany(e=>e.Students)
.HasForeignKey(e=>e.ClassId);
}
}
當然還是不建議用反向的集合屬性,如果Class沒有Students這個集合屬性的話,就不用(也不能)WithMany的參數了。
關于多對多關系配置的WithMany()問題
上次講配置多對多的關系沒有給WithMany設定參數,這樣反向操作的時候就會出錯,應該改成:this.HasMany(e => e.Students).WithMany(e=>e.Teachers)
總結:一對多的中不建議配置一端的集合屬性,因此配置的時候不用給WithMany()參數,如果配置了集合屬性,則必須給WithMany 參數;多對多關系必須要給WithMany()參數。
總結一對多、多對多的“最佳實踐”
(2)一對多最佳方法(不配置一端的集合屬性):
多端
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public long ClassId { get; set; }
public virtual Class Class { get; set; }
}
一端
public class Class
{
public long Id { get; set; }
public string Name { get; set; }
}
在多端的模型配置(StudentConfig)中:
this.HasRequired(e => e.Class).WithMany() .HasForeignKey(e=>e.ClassId);
(3)一對多的配置(在一端配置一個集合屬性,極端不推薦)
多端
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public long ClassId { get; set; }
public virtual Class Class { get; set; }
}
一端
public class Class
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollectionStudents { get; set; } = new List();
}
多端的配置(StudentConfig)中
this.HasRequired(e => e.Class).WithMany(e=>e.Students)//WithMany()的參數不能丟 .HasForeignKey(e=>e.ClassId);
(4)多對多最佳配置
兩端模型
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollectionTeachers { get; set; } = new List();
}
public class Teacher
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollectionStudents { get; set; } = new List();
}
在其中一端配置(StudentConfig)
this.HasMany(e => e.Teachers).WithMany(e=>e.Students).Map(m =>//不要忘了WithMany的參數 m.ToTable("T_StudentTeachers").MapLeftKey("StudentId").MapRightKey("TeacherId"));
多對多中 移除關系:t.Students.Remove(t.Students.First()); 添加關系
()多對多中還可以為中間表建立一個實體方式映射。當然如果中間關系表還想有其他字段,則要必須為中間表建立實體類(中間表和兩個表之間就是兩個一對多的關系了)。
數據庫創建策略(): 如果數據庫創建好了再修改模型或者配置,運行就會報錯,那么就要手動刪除數據庫或者:Database.SetInitializer(new DropCreateDatabaseIfModelChanges());如果報錯“數據庫正在使用”,可能是因為開著Mangement Studio,先關掉就行了。知道就行了,只適合學習時候使用。
CodeFirst Migration 參考(*): http://www.cnblogs.com/libingql/p/3330880.html 太復雜, 不符合Simple is Best 的原則,這是為什么有一些開發者不用EF,而使用Dapper 的原因。
做項目的時候建議初期先把主要的類使用EF 自動生成表,然后干掉Migration 表,然后就 Database.SetInitializer(null);以后對數據庫表的修改都手動完成,也就是手動改實體類、 手動改數據庫表。
三丶 延遲加載(LazyLoad)
如果public virtual Class Class { get; set; }(實體之間的關聯屬性又叫做“導航屬性(Navigation Property)”)把virtual 去掉,那么下面的代碼就會報空引用異常
var s = ctx.Students.First();
Console.WriteLine(s.Class.Name);
聯想為什么?憑什么!!! 改成virtual觀察SQL的執行。執行了兩個SQL,先查詢T_Students,再到T_Classes中查到對應的行。 這叫“延遲加載”(LazyLoad),只有用到關聯的對象的數據,才會再去執行select 查詢。注意延遲加載只在關聯對象屬性上,普通屬性沒這個東西。 注意:啟用延遲加載需要配置如下兩個屬性(默認就是true,因此不需要去配置,只要別手賤設置為false 即可)
context.Configuration.ProxyCreationEnabled = true;
context.Configuration.LazyLoadingEnabled = true;
分析延遲加載的原理:打印一下拿到的對象的GetType(),再打印一下GetType().BaseType;我們發現拿到的對象其實是Student子類的對象。(如果和我這里結果不一致的話,說明:類不是public,沒有關聯的virtual 屬性) 因此EF其實是動態生成了實體類對象的子類,然后override了這些virtual屬性,類似于這樣的 實現:
public class StudentProxy:Student
{
private Class clz;
public override Class Class
{
get
{
if(this.clz==null)
{
this.clz= ....//這里是從數據庫中加載Class 對象的代碼
}
return this.clz;
}
}
}
再次強調:如果要使用延遲加載,類必須是public,關聯屬性必須是virtual。 延遲加載(LazyLoad)的優點:用到的時候才加載,沒用到的時候才加載,因此避免了一次性加載所有數據,提高了加載的速度。缺點:如果不用延遲加載,就可以一次數據庫查詢就可以把所有數據都取出來(使用join實現),用了延遲加載就要多次執行數據庫操作,提高了數據庫服務器的壓力。 因此:如果關聯的屬性幾乎都要讀取到,那么就不要用延遲加載;如果關聯的屬性只有較小的概率(比如年齡大于7 歲的學生顯示班級名字,否則就不顯示)則可以啟用延遲加載。這個概率到底是多少是沒有一個固定的值,和數據、業務、技術架構的特點都有關系,這是需要經驗和直覺,也需要測試和平衡的。 注意:啟用延遲加載的時候拿到的對象是動態生成類的對象,是不可序列化的,因此不能直接放到進程外Session、Redis 等中,解決方法?
(一) 不延遲加載,怎么樣一次性加
用EF永遠都要把導航屬性設置為virtual。又想方便(必須是virtual)又想效率高!
使用Include()方法:
var s = ctx.Students.Include("Class").First();
觀察生成的SQL語句,會發現只執行一個使用join的SQL就把所有用到的數據取出來了。當然拿到的對象還是Student 的子類對象,但是不會延遲加載。(不用研究“怎么讓他返回Student 對象”)
Include(“Class”)的意思是直接加載Student 的Class 屬性的數據。注意只有關聯的對象屬性才可以用Include,普通字段不可以直接寫"Class"可能拼寫錯誤,如果用C#6.0,可以使用nameof語法解決問這個問題:
var s = ctx.Students.Include(nameof(Student.Class)).First();
也可以using System.Data.Entity;然后var s = ctx.Students.Include(e=>e.Class).First(); 推薦這種做法。 如果有多個屬性需要一次性加載,也可以寫多個Include:
var s = ctx.Students.Include(e=>e.Class) .Include(e=>e.Teacher).First();
如果Class對象還有一個School屬性,也想把School對象的屬性也加載,就要:
var s = ctx.Students.Include("Class").Include("Class. School").First(); 或者更好的
var s = ctx.Students.Include(nameof(Student.Class)).Include(nameof(Student.Class)+"."+nameof(Class.School)).First();
(二) 延遲加載的一些坑
DbContext銷毀后就不能再延遲加載了,因為數據庫連接已經斷開
下面的代碼最后一行會報錯:
Student s;
using (MyDbContext ctx = new MyDbContext())
{
s = ctx.Students.First();
}
Console.WriteLine(s.Class.Name);
兩種解決方法:
用Include,不延遲加載(推薦)
Student s;
using (MyDbContext ctx = new MyDbContext())
{
s = ctx.Students.Include(t=>t.Class).First();
}
Console.WriteLine(s.Class.Name);
關閉前把要用到的數據取出來
Class c;
using (MyDbContext ctx = new MyDbContext())
{
Student s = ctx.Students.Include(t=>t.Class).First();\
c = s.Class;
}
Console.WriteLine(c.Name);
(2)兩個取數據一起使用
下面的程序會報錯:已有打開的與此 Command 相關聯的 DataReader,必須首先將它關閉。
foreach(var s in ctx.Students)
{
Console.WriteLine(s.Name);
Console.WriteLine(s.Class.Name);
}
(3)因為EF的查詢是“延遲執行”的,只有遍歷結果集的時候才執行select 查詢,而由于延遲加載的存在到s.Class.Name也會再次執行查詢。ADO.Net中默認是不能同時遍歷兩個DataReader。因此就報錯。
解決方法有如下
允許多個DataReader 一起執行:在連接字符串上加上MultipleActiveResultSets=true,但只適用于SQL 2005以后的版本。其他數據庫不支持。
執行一下ToList(),因為ToList()就遍歷然后生成List:
foreach(var s in ctx.Students.ToList())
{
Console.WriteLine(s.Name);
Console.WriteLine(s.Class.Name);
}
推薦做法:用Include預先加載:
foreach(var s in ctx.Students.Include(e=>e.Class))
{
Console.WriteLine(s.Name);
Console.WriteLine(s.Class.Name);
}
四丶 實體類的繼承
所有實體類都會有一些公共屬性,可以把這些屬性定義到一個父類中。比如:
public abstract class BaseEntity
{
public long Id { get; set; } //主鍵
public bool IsDeleted { get; set; } = false; //軟刪除
public DateTime CreateDateTime { get; set; } = DateTime.Now;//創建時間
public DateTime DeleteDateTime { get; set; } //刪除時間
}
使用公共父類的好處不僅是寫實體類簡單了,而且可以提供一個公共的Entity 操作類:
public abstract class BaseEntity
{
public long Id { get; set; } //主鍵
public bool IsDeleted { get; set; } = false; //軟刪除
public DateTime CreateDateTime { get; set; } = DateTime.Now;//創建時間
public DateTime DeleteDateTime { get; set; } //刪除時間
}
使用公共父類的好處不僅是寫實體類簡單了,而且可以提供一個公共的Entity 操作類:
class BaseDAOwhere T:BaseEntity
{
private MyDbContext ctx;//不自己維護MyDbContext 而是由調用者傳遞,因為調用者可以要執行很多操作,由調用者決定什么時候銷毀。
public BaseDAO (MyDbContext ctx)
{
this.ctx = ctx;
}
public IQueryableGetAll()//獲得所有數據(不要軟刪除的)
{
return ctx.Set().Where(t=>t.IsDeleted==false);//這樣自動處理軟刪除,避免了忘了過濾軟刪除的數據
}
public IQueryableGetAll(int start,int count) //分頁獲得所有數據(不要軟刪除的)
{
return GetAll().Skip(start).Take(count);
}
public long GetTotalCount()//獲取所有數據的條數
{
return GetAll().LongCount();
}
public T GetById(long id)//根據id 獲取
{
return GetAll().Where(t=>t.Id==id).SingleOrDefault();
}
public void MarkDeleted(long id)//軟刪除
{
T en = GetById(id);
if(en!=null)
{
en.IsDeleted = true;
en.DeleteDateTime = DateTime.Now;
ctx.SaveChanges();
}
}
}
DAL同層內返回IQueryable比IEnumerable更好
下面的代碼會報錯:
using (MyDbContext ctx = new MyDbContext())
{
BaseDAOdao = new BaseDAO(ctx);
foreach(var s in dao.GetAll())
{
Console.WriteLine(s.Name);
Console.WriteLine(s.Class.Name);
}
}
原因是什么?怎么Include?需要using System.Data.Entity;
using (MyDbContext ctx = new MyDbContext())
{
BaseDAOdao = new BaseDAO(ctx);
foreach(var s in dao.GetAll().Include(t=>t.Class))
{
Console.WriteLine(s.Name);
Console.WriteLine(s.Class.Name);
}
}
有兩個版本的Include、AsNoTracking:
(1)DbQuery 中的:DbQuery AsNoTracking()、DbQuery Include(string path)
(2)QueryableExtensions 中的擴展方法: AsNoTracking(this IQueryable source) 、 Include(this IQueryable source, string path)、Include(this IQueryablesource, Expression> path)
DbSet繼承自DbQuery;
Where()、Order、Skip()等這些方法返回的是IQueryable接口。
因此如果在IQueryable接口類型的對象上調用Include、AsNoTracking就要using System.Data.Entity
五丶 linq
(一) 簡介
查詢Id>1的狗有如下兩種寫法:
var r1 = dogs.Where(d => d.Id > 1);
var r2 = from d in dogs where d.Id>1 select d;
第一種寫法是使用lambda 的方式寫的,官方沒有正式的叫法,我們就叫“lambda寫法”;
第二種是使用一種叫Linq(讀作:link)的寫法,是微軟發明的一種類似SQL的語法,給我們一個新選擇。兩種方法是可以互相替代的,沒有哪個好、哪個壞,看個人習慣。
我的經驗:需要join等復雜用法的時候Linq更易懂,一般的時候“lambda寫法”更清晰,更緊湊。反編譯得知,這兩種寫法最終編譯成同樣的東西,所以本質上一樣的。
(二) 辟謠
“Linq被淘汰了”是錯誤的說法,應該是“Linq2SQL被淘汰了”。linq就是微軟發明的這個語法,可以用這種語法操作很多數據,操作SQL數據就是Linq2SQL,linq操作后面學的EntityFramework就是Linq2Entity,linq操作普通.Net 對象就是Linq2Object、Linq操作XML文檔就是Linq2XML。
(三) Linq 基本語法
以from item in items 開始,items為待處理的集合,item為每一項的變量名;最后要加上select,表示結果的數據;記得select一定要最后。這是剛用比較別扭的地方。
看各種用法,不用解析:
var r= from d in dogs select d.Id;
var r= from d in dogs select new{d.Id,d.Name,Desc="一條狗"};
排序
var items = from d in dogs
//orderby d.Age
//orderby d.Age descending
orderby d.Age,d.MasterId descending
select d;
join
var r9 = from d in dogs
join m in masters on d.MasterId equals m1.Id
select new { DogName=d.Name,MasterName=m.Name};
注意join中相等不要用==,要用equals。寫join的時候linq比“lambda”漂亮
group by
var r1 = from p in list
group p by p.Age into g
select new { Age = g.Key, MaxSalary = g.Max(p=>p.Salary), Count = g.Count() };
(四) 混用
只有Where,Select,OrderBy,GroupBy,Join 等這些能用linq寫法,如果要用下面的 “Max,Min,Count,Average,Sum,Any,First,FirstOrDefault,Single,SingleOrDefault,Distinct,Skip,Take等”則還要用lambda 的寫法(因為編譯后是同一個東西,所以當然可以混用)。
var r1 = from p in list
group p by p.Age into g
select new { Age = g.Key, MaxSalary = g.Max(p=>p.Salary), Count = g.Count() };
int c = r1.Count();
var item = r1.SingleOrDefault();
var c = (from p in list
where p.Age>3
select p
).Count();
lambda對linq說:論漂亮我不行,論強大你不行!
六丶C#6.0 語法
(1)屬性的初始化“public int Age{get;set;}=6”。低版本.Net中怎么辦?
(2)nameof:可以直接獲得變量、屬性、方法等的名字的字符串表現形式。獲取的是最后一段的名稱。如果在低版本中怎么辦?
class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
string s1 = nameof(p1);
string s2 = nameof(Person);
string s3 = nameof(p1.Age);
string s4 = nameof(Person.Age);
string s5 = nameof(p1.F1);
Console.ReadKey();
}
}
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public void Hello()
{
}
public static void F1()
{
}
}
好處:避免寫錯了,可以利用編譯時檢查。
應用案例:ASP.Net MVC 中的[Compare(“BirthDay”)]改成[Compare(nameof(BirthDay))]
(3)??語法:int j= i??3; 如果i為null則表達式的值為3,否則表達式的值就是i的值。如果在低版本中怎么辦?int j = (i== null)?3:(int)i;
應用案例:string name = null;Console.WriteLine(name??“未知”);
(4)?.語法:string s8 = null;string s9 = s8?.Trim(); 如果s8為null,則不執行Trim(),讓表達式的結果為null。在低版本中怎么辦?
string s9=null;if(s8!=null){s9=s8.Trim();};
七丶ORM
(一)ORM簡介
ORM(Object Relational Mapping)對象關系映射,一般指持久化數據和實體對象的映射
數據存儲是絕大多數軟件系統都要接觸到的技術,具有一定規模的軟件產品,為了方便存儲和管理數據,便引入了數據庫這一工具,但是數據如何從程序寫入數據庫的呢?
為方便程序員通過代碼將數據寫入數據庫,一般的語言開發的廠商都會為各種數據庫適配數據庫連接的驅動程序,比如ADO.Net,JDBC等。
但是數據庫連接的驅動程序的職責在于管理連接數據庫,設置連接參數等信息,通常會返回各自封裝好的數據集類型,驅動程序封裝的類型往往是以數據為核心進行描述的,現代化的軟件設計為了簡便描述事物的特征都而以面向對象思想為核心,兩者之間的轉換還有很多的路要走。
除卻轉換部分,Sql語句的編寫也是一大學問,一般的編程語言都沒有為sql語句定義類型,這是因為每種數據庫的sql語句風格都是不一樣的,難以給出一個統一的方案。退而求其次,一般的編程語言都采用字符串形式傳遞sql語句到數據庫驅動程序。拋棄各種各樣的sql語句的學習之外,這種方式有一個很大的弊端,那就是sql語句的拼寫極容易由于手誤而犯錯。
在這種場景下,ORM框架誕生了!
(二)ORM的工作原理
沒有ORM的情況下,主要有兩個槽點:
驅動返回類型和對象不能良好映射
SQL語句的學習成本及易錯率(多種數據庫語句難以全部掌握)
那么,且看我們的ORM如何改善這兩個槽點:
(1)數據驅動返回的數據通常都是以數據為核心的數據集合,我們需要通過手動將類對象和數據庫返回的列數據進行一一匹配獲取,然后賦值到對象上。在這里要感謝泛型和反射兩大語法,通過泛型和反射,我們可以獲取到任何實體類的屬性而不是具體到某一種類型,通過遍歷實體類的屬性去數據集合中一一獲取并復制返回。這一操作便將數據集合的數據完美包裝成了以面向對象為核心的和類相關的對象數據集合。
(2)sql語句的拼寫,我們可以提供一套公共sql語句模板,然后在具體實體對象操作的時候將實體對象的屬性名稱和屬性值當作參數拼接進去,組裝成完整的sql語句(例如java體系中的Mybatis框架)或者依舊采用封裝一套淺顯易懂的Api,Api內部通過對應方法和實體對象的組裝成sql語句(例如.Net體系中EntityFramework框架)
(3)最重要的兩個問題解決完之后,我們可以在框架中做一些對我們有幫助的其他事情。ORM框架做的最多的便是“緩存”。
作為程序員應該掌握的基礎知識,數據庫操作是要和硬盤打交道的,而程序是在內存中運行的,操作內存的速度要比操作硬盤快數十倍以上,可見一個訪問量較高的大型系統很容易由于數據庫操作過于頻繁而拖慢整體速度,從而影響系統的使用。因此,ORM框架要幫助我們減少數據庫的訪問,加快系統速度。
ORM框架的緩存系統一般是較為復雜的,而且每種ORM框架對緩存的實現機制都是不同的。整體的思路卻是一致的,對訪問頻率較高的數據進行緩存,并在對數據編輯的時候要對緩存進行更新,以免出現數據不一致的問題。詳細的緩存實現策略這里不一一贅述,感興趣可以針對某個ORM框架進行剖析。
(三)ORM的優缺點
優點:
(1)ORM框架降低了學習門檻,一個對sql語句并不熟悉的開發人員也可以很容易通過簡易的ORM框架Api進行數據庫的操作。
(2)提高了開發效率,ORM使我們減少很多繁瑣重復的工作量,讓我們的注意力集中在實現業務上。
(3)一定程度上提高了程序的響應速度。
弊端:
(1)框架會自動生成Sql語句,所有場景的sql語句都是同一套模板,難以自動針對場景對sql語句進行良好的優化,某種場景下很容易生成執行很慢的sql語句。如果讓DBA看到這樣的執行sql,必定引來抓狂崩潰。
(2)ORM框架只是為了滿足絕大多數的場景而生的,特殊需要優化sql的場景下,我們完全可以直接使用驅動手動執行sql或使用ORM框架內提供的sql語句api進行自定義sql語句。
總結
以上是生活随笔為你收集整理的sql注入pythonpoco_.NET EF(Entity Framework)详解的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 魔兽世界裂解符文任务怎么完成 裂解符文任
 - 下一篇: 网站安全证书有问题如何解决