1、導航查詢特點
作用:主要處理主對象裏面有子對象這種層級關係查詢
1.1 無外鍵開箱就用
其它ORM導航查詢 需要 各種配置或者外鍵,而SqlSugar則開箱就用,無外鍵,只需配置特性和主鍵就能使用
1.2 高性能優
查詢 性能非常強悍 5.0.8.1preview02版本進行了性能優化
支持大數據分頁導航查詢
3.3 語法超級爽
var list=db.Queryable<Test>() .Includes(t=> t.Provinces, pro=>pro.Citys ,cit=>cit.Street) //多層級 .Includes(t=> t.ClassInfo) // 一個層級查詢 .ToList(); //具體用法看下面文檔介紹 //多層級可以看2.5 |
2、新導航查詢
適合有主鍵的常規操作, 請升級到5.0.6.8
2.1 一對一 ( one to one )
//實體 public class StudentA { [SugarColumn(IsPrimaryKey = true , IsIdentity = true )] public int StudentId { get ; set ; } public string Name { get ; set ; } public string SexCode { get ; set ;} public int SchoolId { get ; set ; } //用例1:主鍵模式 StudentA(主表)表中的 SchoolId 和SchoolA(子表)中的主鍵關聯 [Navigate(NavigateType.OneToOne, nameof(SchoolId))] //一對一 SchoolId是StudentA類裏面的 public SchoolA SchoolA { get ; set ; } //不能賦值只能是null //用例2:反向導航,2個字段匹配關係 [Navigate(NavigateType.OneToOne, nameof(SchoolId),nameof(SchoolA.Id))] public SchoolA SchoolA { get ; set ; } //不能賦值只能是null //第一個主表字段,第二從表字段 順序不要錯了 //用例3: 字典導航 多了個SQL條件參數 //[SqlSugar.Navigate(NavigateType.OneToOne,nameof(SexId),nameof(DataDictionary1.Code),"type='sex'")] //具體用法可以看配置查詢 https://www.donet5.com/Home/Doc?typeId=2309 } public class SchoolA { [SugarColumn(IsPrimaryKey = true , IsIdentity = true )] public int Id{ get ; set ; } public string SchoolName { get ; set ; } } //導航+主表過濾 導航屬性過濾 var list = db.Queryable<StudentA>() .Includes(x => x.SchoolA) //填充子對象 (不填充可以不寫) .Where(x =>x.SchoolA.SchoolName== "北大" ) .ToList(); //導航+主表過濾 只查有導航數據 (新功能:5.1.2.8) var list = db.Queryable<StudentA>() .Includes(x => x.SchoolA) //填充子對象 (不填充可以不寫) .Where(x => SqlFunc.Exists(x.SchoolA.主鍵)) .ToList(); //導航+子表過濾 5.0.9.4-preview06 請注意升級 //創建一個擴展函數,默認是Class不支持Where public static List<T> Where<T>( this T thisValue, Func<T, bool > whereExpression ) where T: class , new () { return new List<T>() { thisValue }; } var list = db.Queryable<Student_003>() .Includes(x => x.school_001.Where(z=>z.Name== "a" ).ToList()) //擴展的Where對子表進行過濾 .ToList(); //5.0.9.4-preview06 才支持 請注意升級 請注意升級 //導航 指定字段 5.1.3.38 var list = db.Queryable<StudentA>() .Includes(x => x.SchoolA.ToList(it=> new SchoolA(){ Name =it.Name,id=it.Id})) .ToList(); //導航如果只查一個字段 var list = db.Queryable<StudentA>() .Where(x => x.id>1) //Where和Select中別名要寫一樣 .Select(x => new { x=x, SchoolName= x.SchoolA.SchoolName }).ToList(); |
多字段1對1 看文檔2.4
2.2 一對多 ( one to many)
BookA(子表)中的studenId和StudentA(主表)中的主鍵關聯
//實體 public class StudentA { [SugarColumn(IsPrimaryKey = true , IsIdentity = true )] public int Id{ get ; set ; } public string Name { get ; set ; } public int SchoolId { get ; set ; } //用例1:正常一對多 [Navigate(NavigateType.OneToMany, nameof(BookA.studenId))] //BookA表中的studenId public List<BookA> Books { get ; set ; } //注意禁止給books手動賦值 //用例2:反向導航支持:StudentA沒有主鍵或者指定關係 [Navigate(NavigateType.OneToMany, nameof(BookA.studenId),nameof(Id))] public List<BookA> Books { get ; set ; } //注意禁止給books手動賦值 //與一對一相反 第一個 從表字段,第二個主表字段 } public class BookA { [SugarColumn(IsPrimaryKey = true , IsIdentity = true )] public int BookId { get ; set ; } public string Name { get ; set ; } public int studenId { get ; set ; } } //例1:簡單用法 var list = db.Queryable<StudentA>() .Includes(x => x.Books) .ToList(); //例2:支持Any和Count 對主表進行過濾 (子對象過濾看下面) var list = db.Queryable<StudentA>() .Includes(x => x.Books) .Where(x => x.Books.Any()) //帶條件的 //.Where(x => x.Books.Any(z=>z.Name=="jack"))) .ToList(); //例3: 沒有Includes也可以使用過濾 var list2 = db.Queryable<StudentA>() .Where(x => x.Books.Any()) //Any中可以加條件 Any(z=>z.BookId==1) .ToList(); //例4 Where子對象進行排序和過濾 (支持WhereIF) var list = db.Queryable<StudentA>() .Includes(x => x.Books.Where(y => y.BookId > 0).OrderBy(y => y.BookId).ToList()) .ToList(); //例5 主表+子表都過濾 var list = db.Queryable<StudentA>() .Includes(x => x.Books.Where(it=>it.Name== "jack" )) //只過濾子表 .Where(x => x.Books.Any(z=>z.Name== "jack" ))) //通過子表過濾主表 .ToList(); //例6:Select指定字段 var list= db.Queryable<StudentA>() .Includes(x => x.Books.Select(z=> new BookA() { Names = z.Names }).ToList()) //例7:Select多層級 (結構:StudentAt->books->BookItems) var list= db.Queryable<StudentA>() .Includes(x => x.Books.Select(z=> new BookA(){Names=z.Name}.ToList(),it=>BookItems)) .ToList();、 //Includes中的Select只能是字段 ,不能導航對象 //例8:OrderBy指定字段 (Skip Take可以分頁) var list= db.Queryable<StudentA>() .Includes(x => x.Books.OrderBy(z=>z.Id).ToList()) .ToList(); //例9:Take取前幾條 var list= db.Queryable<StudentA>() .Includes(x => x.Books.Take(10).ToList()) .ToList(); //例10:DTO支持進行了強化 看標題2.7 //例11:一對多後還可用追加字段映射MappingField 如果以前是1個字關聯,現在追加後就成了1+1 db.Queryable<StudentA>().Includes(x => x.Books.MappingField(z=>z.字段,()=>x.字段).ToList() ) .ToList(); //MappingField 和 Where區別 //MappingField MappingField用來指定2個對象的關係,Where只能當前表過濾不能和主表進行關聯 //MappingField 可以多個也可以和Where一起始用 |
非標準1對多 看文檔2.4
2.3 多對多 ( many to many)
//實體 public class ABMapping1 { [SugarColumn(IsPrimaryKey = true )] //中間表可以不是主鍵 public int AId { get ; set ; } [SugarColumn(IsPrimaryKey = true )] //中間表可以不是主鍵 public int BId { get ; set ; } } public class A1 { [SugarColumn(IsPrimaryKey = true , IsIdentity = true )] public int Id { get ; set ; } public string Name { get ; set ; } [Navigate( typeof (ABMapping1), nameof(ABMapping1.AId), nameof(ABMapping1.BId))] //注意順序 public List<B1> BList { get ; set ; } //只能是null不能賦默認值 } public class B1 { [SugarColumn(IsPrimaryKey = true , IsIdentity = true )] public int Id { get ; set ; } public string Name { get ; set ; } [Navigat( typeof (ABMapping1), nameof(ABMapping1.BId), nameof(ABMapping1.AId))] //注意順序 public List<A1> AList { get ; set ; } //只能是null不能賦默認值 } //例1:簡單用法 直接填充B的集合,只要配置好特性非常簡單 var list3= db.Queryable<A1>().Includes(x => x.BList).ToList(); //例2:支持子對象排序和過濾 (支持WhereIF) var list3= db.Queryable<A1>().Includes(x => x.BList.Where(z=>z.Id>0).ToList()).ToList(); //例3:支持主表過濾 Any和Count var list3= db.Queryable<A1>().Includes(x => x.BList) .Where(x=>x.BList.Any()) //Any裏面可以加條件 Any(z=>z.xxxx>0) .ToList(); //例4主表+子表都過濾 var list = db.Queryable<StudentA>() .Includes(x => x.BList.Where(it=>it.Name== "jack" )) //只過濾子表 .Where(x => x.BList.Any(z=>z.Name== "jack" ))) //通過子表過濾主表 .ToList(); //不使用Includes一樣可以過濾 var list3= db.Queryable<A1>() .Where(x=>x.BList.Any()) //可以加條件.Where(x=>x.BList.Any(z=>z.xxx==x.yyy)) .ToList(); //多對多後還可用追加字段映射MappingField 如果以前是2個字段關聯,現在追加後就成了2+1 db.Queryable<A1>().Includes(x => x.BList.MappingField(z=>z.字段,()=>x.字段).ToList() ) .ToList(); //MappingField 和 Where區別 //MappingField MappingField用來指定2個對象的關係,Where只能當前表過濾不能和主表進行關聯 //MappingField 可以多個也可以和Where一起始用 |
2.4 多字段關係映射(可以是N個)
支持多個字段關聯 5.1.4.108-preview32
var list=db.Queryable<UnitAddress011>().Includes(x => x.Persons).ToList(); //m是主表字段 c是子表字段 是一個json數組 格式不要錯了 [Navigate(NavigateType.Dynamic, "[{m:\"Id\",c:\"AddressId\"},{m:\"Id2\",c:\"AddressId2\"}]" )] public List<UnitPerson011> Persons { get ; set ; } |
注意:該功能只能用在查詢上,能用正常導航就儘量使用正常導航
2.5 多級導航(例如:省>市>區)
配置好實體類,我們可以多級查詢(一對多、一對多、多對多都支持只要配好類就可以使用)
public class StudentA { [SugarColumn(IsPrimaryKey = true )] public int StudentId { get ; set ; } public string Name { get ; set ; } public int SchoolId { get ; set ; } [Navigate(NavigateType.OneToOne, nameof(SchoolId))] //一對一 public SchoolA SchoolA { get ; set ; } [Navigate(NavigateType.OneToMany, nameof(BookA.studenId))] //一對多 public List<BookA> Books { get ; set ; } //只能是null不能賦默認值 } public class SchoolA { [SugarColumn(IsPrimaryKey = true )] public int SchoolId { get ; set ; } public string SchoolName { get ; set ; } [Navigate(NavigateType.OneToMany, nameof(RoomA.SchoolId))] //一對多 public List<RoomA> RoomList { get ; set ; } //只能是null不能賦默認值 } public class RoomA { [SugarColumn(IsPrimaryKey = true )] public int RoomId { get ; set ; } public string RoomName { get ; set ; } public int SchoolId { get ; set ; } } public class BookA { [SugarColumn(IsPrimaryKey = true )] public int BookId { get ; set ; } public string Name { get ; set ; } public int studenId { get ; set ; } } var list2 = db.Queryable<StudentA>() //查2層 .Includes(st => st.SchoolA, sch=> sch.RoomList) //查詢2級(等於EF ThenInclude) //查1層 .Includes(st=> st.Books) .ToList() //說明: 一對多 多對多 一對多 只要配好了都可以多層級使用 //如果想超過3個層級需要.AsNavQueryable() //缺點VS提示會消失,直接寫不要在乎意提示不出來,VS關掉在開就行了,只要不改這個代碼提示就不會有問題 db.Queryable<Order>() Includes(it=>it.xx) .AsNavQueryable() //加這個前面 .Includes(it=>it.1,it=>it.2,it=>it.3,it=>it.4,it=>it.5..) //.AsNavQueryable()能不用盡量不要用,正常Includes(+3)重載完全夠用了 |
2.6 性能優化
1、升級 如果搜索不到勾選預覽版本
5.0.8.1 preview02版本針對大數據導航有了很好的性能優化
2、分頁導航
底層分批量查詢 適合一次性查詢1000條以上的導航
var list = new List<Tree1>(); db.Queryable<Tree1>() .Includes(it => it.Child) .ForEach(it => list.Add(it), 300); //每次查詢300條 |
更多用法:https://www.donet5.com/Home/Doc?typeId=2414
3、關聯字段推薦用主鍵,如果非主鍵導航加索引爲佳
2.7 轉DTO (必學的技巧)
1.自動DTO (推薦 )
//Mapster 工具映射 (推薦) 比AutoMapper方便不需要配置 //Nuget直接安裝就行了 //簡單示例:結構一樣直接轉換 var list=db.Queryable<StudentA>() .Includes(x => x.Books).ToList(); var dtoList=list.Adapt<List<StudentDTO>>() //技巧示例:這個用法必學通過規則映射DTO public class TreeDTO { public int Id { get ; set ; } public string Name { get ; set ; } public int ParentId { get ; set ; } public string ParentName { get ; set ; } //對應Parent中的Name } public class Tree { [SqlSugar.SugarColumn(IsPrimaryKey = true )] public int Id { get ; set ; } public string Name { get ; set ; } public int ParentId { get ; set ; } [Navigate(NavigateType.OneToOne,nameof(ParentId))] public Tree Parent { get ; set ; } } var list= db.Queryable<Tree>() .Includes(it => it.Parent) .ToList(); //DTO和List不能是同一個類不然這種映射會失效 var dtolist= list.Adapt<List<TreeDTO>>(); //DTO中的ParentName就有值了 |
2. 手動轉DTO 升級: 5.1.4.71
老版本注意:是Select中用導航對象
//簡單的用法 5.1.4.71 var list = db.Queryable<Student_004>() .Includes(x => x.books) .Select(x => new Student_004DTO { books = x.books }, true ) //true是自動映射其他屬性,匿名對象需要手動 .ToList(); //Mapster轉換 5.1.4.71 var list = db.Queryable<Student_004>() .Includes(x => x.books) .Select(x => new Student_004DTO { name=x.Name, books = x.books.Adapt<List<BooksDTO>>() //導航對象用 Mapster轉換 (NUGET安裝) }) .ToList(); //DTO中用方法 5.1.4.71 var list = db.Queryable<Student_004>() .Includes(x => x.books) .Select(x => new Student_004DTO { name=x.Name, //導航對象books可以是C#任何方法結尾 bookIds=x.books.Select(it=>it.id).ToList(), booksDto=x.books.Select(it=> new BookDTO() { id=it.Id, Name=it.Name }).ToList() }) .ToList(); //聯表查詢用DTO寫法 5.1.4.71 var list5= db.Queryable<Student_004>() .Includes(x => x.school_001, x => x.rooms) .Includes(x => x.books) .LeftJoin<Order>((x, y) => x.Id==y.sid) .Select((x,y) => new Student_004DTO { SchoolId = x.SchoolId, books = x.books, school_001 = x.school_001, Name=y.Name }) .ToList(); |
2.8 導航方法
一對多和多對多
在我們一對多和多對多對象我們可以用導航方法Any()和導航方法Count
//注意:不需 Includes 就可以使用 Where(it=>it.導航對象.Any()) Where(it=>it.導航對象.Any(z=>z.id==1)) Where(it=>it..導航對象.Any(List<IConditionalModel>) //5.1 //Count用法類似 |
一對一函數 5.1.2.9
//注意:不需 Includes 就可以使用 Where(x=>SqlFunc.Exists(x.SchoolA.Id)) //查詢存在一對一的主表數據 Where(x=>SqlFunc.Exists(x.SchoolA.Id,List<IConditionalModel>)) //查詢存在一對一的主表數據 |
2.9 Root->books->[A,B]
如果Books下面有2個導航A和B
//自動寫法,Books下面的A和B都會查詢出來 .IncludesAllSecondLayer(x=>x.Books) //自動只能有這麼多層次,更深層級需要手動寫法 //手動寫法 .Includes(x => x.Books,x=>x.A) .Includes(x => x.Books,x=>x.B) |
2.91 跨庫導航
https://www.donet5.com/Home/Doc?typeId=2244
3、支持聯表的導航
手動映射適合沒有主鍵或者複雜的一些操作,該功能和Includes文檔 2.4比較接近
3.1 創建測試數據
創建類
public class StudentA { [SugarColumn(IsPrimaryKey = true )] public int StudentId { get ; set ; } public string Name { get ; set ; } public int SchoolId { get ; set ; } [SugarColumn(IsIgnore = true )] public SchoolA SchoolA { get ; set ; } } public class SchoolA { [SugarColumn(IsPrimaryKey = true )] public int SchoolId { get ; set ; } public string SchoolName { get ; set ; } [SugarColumn(IsIgnore = true )] public List<RoomA> RoomList { get ; set ; } [SugarColumn(IsIgnore = true )] public List<TeacherA> TeacherList { get ; set ; } } public class TeacherA { [SugarColumn(IsPrimaryKey = true )] public int Id { get ; set ; } public int SchoolId { get ; set ; } public string Name { get ; set ; } } public class RoomA { [SugarColumn(IsPrimaryKey = true )] public int RoomId { get ; set ; } public string RoomName { get ; set ; } public int SchoolId { get ; set ; } } |
創建測試數據
db.CodeFirst.InitTables<StudentA, RoomA, SchoolA,TeacherA>(); db.DbMaintenance.TruncateTable<StudentA>(); db.DbMaintenance.TruncateTable<RoomA>(); db.DbMaintenance.TruncateTable<SchoolA>(); db.DbMaintenance.TruncateTable<TeacherA>(); db.Insertable( new RoomA() { RoomId = 1, RoomName = "北大001室" , SchoolId = 1 }).ExecuteCommand(); db.Insertable( new RoomA() { RoomId = 2, RoomName = "北大002室" , SchoolId = 1 }).ExecuteCommand(); db.Insertable( new RoomA() { RoomId = 3, RoomName = "北大003室" , SchoolId = 1 }).ExecuteCommand(); db.Insertable( new RoomA() { RoomId = 4, RoomName = "清華001廳" , SchoolId = 2 }).ExecuteCommand(); db.Insertable( new RoomA() { RoomId = 5, RoomName = "清華002廳" , SchoolId = 2 }).ExecuteCommand(); db.Insertable( new RoomA() { RoomId = 6, RoomName = "清華003廳" , SchoolId = 2 }).ExecuteCommand(); db.Insertable( new SchoolA() { SchoolId = 1, SchoolName = "北大" }).ExecuteCommand(); db.Insertable( new SchoolA() { SchoolId = 2, SchoolName = "清華" }).ExecuteCommand(); db.Insertable( new StudentA() { StudentId = 1, SchoolId = 1, Name = "北大jack" }).ExecuteCommand(); db.Insertable( new StudentA() { StudentId = 2, SchoolId = 1, Name = "北大tom" }).ExecuteCommand(); db.Insertable( new StudentA() { StudentId = 3, SchoolId = 2, Name = "清華jack" }).ExecuteCommand(); db.Insertable( new StudentA() { StudentId = 4, SchoolId = 2, Name = "清華tom" }).ExecuteCommand(); db.Insertable( new TeacherA() { SchoolId=1, Id=1, Name= "北大老師01" }).ExecuteCommand(); db.Insertable( new TeacherA() { SchoolId = 1, Id =2, Name = "北大老師02" }).ExecuteCommand(); db.Insertable( new TeacherA() { SchoolId = 2, Id = 3, Name = "清華老師01" }).ExecuteCommand(); db.Insertable( new TeacherA() { SchoolId = 2, Id = 4, Name = "清華老師02" }).ExecuteCommand(); |
3.2 手動實現二層
注意:普通導航看標題2 ,ThenMapper是用來處理 普通導航不能實現的功能
結構: Student->SchoolA
var list = db.Queryable<StudentA>().ToList(); //這兒也可以聯表查詢 db.ThenMapper(list, stu => { //如果加Where不能帶有stu參數,stu參數寫到 SetContext //可以用Where寫SetContext但是不能帶有stu對象 stu.SchoolA=db.Queryable<SchoolA>().SetContext(scl=>scl.SchoolId,()=>stu.SchoolId,stu).FirstOrDefault(); //可以聯查詢的 //stu.xxxx=db.Queryable<SchoolA>().LeftJoin<XXX>().Select(xxxx).SetContext(....).ToList(); }); // SetContext不會生成循環操作,高性能 和直接Where性能是不一樣的 |
注意:1、如果沒有SetContext那麼這個查詢將會循環
2、db.ConextId外面和裏面需要是同一個
3.3 聯表導航多層級
注意:普通導航看標題2,ThenMapper是用來處理 普通導航不能實現的功能
瞭解原理後我們用ThenMapper想映射哪層就映射哪層
var treeRoot=db.Queryable<Tree>().Where(it => it.Id == 1).ToList(); //第一層 db.ThenMapper(treeRoot, item => { item.Child = db.Queryable<Tree>().SetContext(x => x.ParentId, () => item.Id, item).ToList(); }); //第二層 db.ThenMapper(treeRoot.SelectMany(it=>it.Child), it => { it.Child = db.Queryable<Tree>().SetContext(x => x.ParentId, () => it.Id, it).ToList(); }); //第三層 db.ThenMapper(treeRoot.SelectMany(it => it.Child).SelectMany(it=>it.Child), it => { it.Child = db.Queryable<Tree>().SetContext(x => x.ParentId, () => it.Id, it).ToList(); }); //這兒只是用樹型結構來證明可以實現無限級別導航查詢 ,實際開發中樹型結構用ToTree實現 public class Tree { [SqlSugar.SugarColumn(IsPrimaryKey = true )] public int Id { get ; set ; } public string Name { get ; set ; } public int ParentId { get ; set ; } [SqlSugar.SugarColumn(IsIgnore = true )] public Tree Parent { get ; set ; } [SqlSugar.SugarColumn(IsIgnore = true )] public List<Tree> Child { get ; set ; } } // SetContext不會生成循環操作,高性能 和直接Where性能是不一樣的 |
新功能 : 請升級到5.0.6.7 預覽版本 及以上
4、樹型查詢
https://www.donet5.com/Home/Doc?typeId=2311
5、老版導航查詢
如果使用Mapper的用戶可以看這個
https://www.donet5.com/ask/9/15831
6、不加特性使用導航
通過實體AOP方法實現,具體用法看實體配置
EntityService= (type, columnInfo) => { p.IfTable<Order>().OneToOne(it => it.Item, nameof(Order.ItemId)); } |
7、自動Include 5.1.4.63
第二層的所有導航自動Include(不支持第三層和第四層)
var list3 = db.Queryable<UnitaStudentA>() .IncludesAllFirstLayer().ToList(); //有重載可以排除不想要的 //排除說明: //IncludesAllFirstLayer(nameof(UnitaStudentA.ProjectPhases)) //這樣就是排除ProjectPhases的導航屬性 //可以排除多個 //IncludesAllFirstLayer("a","b") //自動導航如果有重複的情況: 誰在前面執行哪個 var list3 = db.Queryable<UnitaStudentA>() .Includes(it=>it.Order.Where(s=>s.id==1).ToList()) .IncludesAllFirstLayer().ToList(); //自動導航和Order重複 //根據名字導航 db.Queryable<Order>() //等同於Includes(it=>it.ProjectPhases) .IncludesByNameString(nameof(Order.ProjectPhases)).ToList() |
可以看下圖 自動導航替換了下面註釋代碼
注意:
第一層it下面的通過IncludesAllFirstLayer全自動
第二層(it.ProjectTransferDocs)通過IncludesAllSecondLayer半自動。三層四級需要全部手動
9、兼容EF CORE 非List<T>的導航
vra list=db.Queryable<Order>() .Includes(it=>it.導航對象.ToList()) //通過.ToList()轉成SqlSugar導航類型就行 .ToList(); |
10、泛型導航
通過鑑別器實現
var dis=db.Queryable<UnitTestDis<Cat>>() .Includes(x => x.Animals).ToList(); //T是Cat那麼就能導航Cat var dis2 = db.Queryable<UnitTestDis<Dog>>() .Includes(x => x.Animals).ToList(); //T是Dog那麼就能導航Dog |