diff --git a/FreeSql.DbContext/DbSet/DbSetAsync.cs b/FreeSql.DbContext/DbSet/DbSetAsync.cs index 13533430e..ea5d35d18 100644 --- a/FreeSql.DbContext/DbSet/DbSetAsync.cs +++ b/FreeSql.DbContext/DbSet/DbSetAsync.cs @@ -155,8 +155,9 @@ async public Task SaveManyAsync(TEntity item, string propertyName, CancellationT if (tref == null) return; switch (tref.RefType) { - case Internal.Model.TableRefType.OneToOne: - case Internal.Model.TableRefType.ManyToOne: + case TableRefType.OneToOne: + case TableRefType.ManyToOne: + case TableRefType.PgArrayToMany: throw new ArgumentException(DbContextStrings.PropertyOfType_IsNot_OneToManyOrManyToMany(_table.Type.FullName, propertyName)); } @@ -166,7 +167,7 @@ async public Task SaveManyAsync(TEntity item, string propertyName, CancellationT try { await AddOrUpdateNavigateAsync(item, false, propertyName, cancellationToken); - if (tref.RefType == Internal.Model.TableRefType.OneToMany) + if (tref.RefType == TableRefType.OneToMany) { await DbContextFlushCommandAsync(cancellationToken); //删除没有保存的数据,求出主体的条件 @@ -213,7 +214,7 @@ async Task AddOrUpdateNavigateAsync(TEntity item, bool isAdd, string propertyNam DbSet refSet = null; switch (tref.RefType) { - case Internal.Model.TableRefType.OneToOne: + case TableRefType.OneToOne: refSet = GetDbSetObject(tref.RefEntityType); var propValItem = GetItemValue(item, prop); if (propValItem == null) return; @@ -225,7 +226,8 @@ async Task AddOrUpdateNavigateAsync(TEntity item, bool isAdd, string propertyNam if (isAdd) await refSet.AddAsync(propValItem); else await refSet.AddOrUpdateAsync(propValItem); return; - case Internal.Model.TableRefType.ManyToOne: + case TableRefType.ManyToOne: + case TableRefType.PgArrayToMany: return; } @@ -234,7 +236,7 @@ async Task AddOrUpdateNavigateAsync(TEntity item, bool isAdd, string propertyNam refSet = GetDbSetObject(tref.RefEntityType); switch (tref.RefType) { - case Internal.Model.TableRefType.ManyToMany: + case TableRefType.ManyToMany: var curList = new List(); foreach (var propValItem in propValEach) { @@ -327,7 +329,7 @@ async Task AddOrUpdateNavigateAsync(TEntity item, bool isAdd, string propertyNam await midSet.AddRangeAsync(midListAdd, cancellationToken); } break; - case Internal.Model.TableRefType.OneToMany: + case TableRefType.OneToMany: var addList = new List(); var addOrUpdateList = new List(); foreach (var propValItem in propValEach) diff --git a/FreeSql.DbContext/DbSet/DbSetSync.cs b/FreeSql.DbContext/DbSet/DbSetSync.cs index 8bafd8ad1..cc6f8a3e4 100644 --- a/FreeSql.DbContext/DbSet/DbSetSync.cs +++ b/FreeSql.DbContext/DbSet/DbSetSync.cs @@ -166,8 +166,9 @@ public void SaveMany(TEntity item, string propertyName) if (tref == null) return; switch (tref.RefType) { - case Internal.Model.TableRefType.OneToOne: - case Internal.Model.TableRefType.ManyToOne: + case TableRefType.OneToOne: + case TableRefType.ManyToOne: + case TableRefType.PgArrayToMany: throw new ArgumentException(DbContextStrings.PropertyOfType_IsNot_OneToManyOrManyToMany(_table.Type.FullName, propertyName)); } @@ -177,7 +178,7 @@ public void SaveMany(TEntity item, string propertyName) try { AddOrUpdateNavigate(item, false, propertyName); - if (tref.RefType == Internal.Model.TableRefType.OneToMany) + if (tref.RefType == TableRefType.OneToMany) { DbContextFlushCommand(); //删除没有保存的数据,求出主体的条件 @@ -224,7 +225,7 @@ void AddOrUpdateNavigate(TEntity item, bool isAdd, string propertyName) DbSet refSet = null; switch (tref.RefType) { - case Internal.Model.TableRefType.OneToOne: + case TableRefType.OneToOne: refSet = GetDbSetObject(tref.RefEntityType); var propValItem = GetItemValue(item, prop); if (propValItem == null) return; @@ -236,7 +237,8 @@ void AddOrUpdateNavigate(TEntity item, bool isAdd, string propertyName) if (isAdd) refSet.Add(propValItem); else refSet.AddOrUpdate(propValItem); return; - case Internal.Model.TableRefType.ManyToOne: + case TableRefType.ManyToOne: + case TableRefType.PgArrayToMany: return; } @@ -714,7 +716,7 @@ List> LocalGetNavigates(TableInfo tb) { return tb.Properties.Where(a => tb.ColumnsByCs.ContainsKey(a.Key) == false) .Select(a => new NativeTuple(tb.GetTableRef(a.Key, false), a.Value)) - .Where(a => a.Item1 != null && a.Item1.RefType != TableRefType.ManyToOne) + .Where(a => a.Item1 != null && new[] { TableRefType.OneToOne, TableRefType.OneToMany, TableRefType.ManyToMany }.Contains(a.Item1.RefType)) .ToList(); } void LocalEach(DbSet dbset, IEnumerable items, bool isOneToOne) @@ -860,6 +862,12 @@ void LocalEach(DbSet dbset, IEnumerable items, bool isOneToOne) } } + var atms = navs.Where(a => a.Item1.RefType == TableRefType.PgArrayToMany).ToList(); + if (atms.Any()) + { + + } + if (dbset == rootDbSet) { if (CanRemove(data, true) == false) return; diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index 1ab5bf1cf..6b638cad4 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -795,14 +795,5 @@ - - - 批量注入 Repository,可以参考代码自行调整 - - - - - - diff --git a/FreeSql.Tests/FreeSql.Tests/Issues/623.cs b/FreeSql.Tests/FreeSql.Tests/Issues/623.cs index db0da73ba..5898fc433 100644 --- a/FreeSql.Tests/FreeSql.Tests/Issues/623.cs +++ b/FreeSql.Tests/FreeSql.Tests/Issues/623.cs @@ -44,7 +44,7 @@ public void MySqlGroupBy() FacilityCount = a.Sum(a.Value.FacilityCount), FacilityOpenCount = a.Sum(a.Value.FacilityOpenCount) }); - Assert.Equal(@"SELECT a.`Dot`, sum(a.`FacilityCount`) as1, sum(a.`FacilityOpenCount`) as2 + Assert.Equal(@"SELECT a.`Dot` as1, sum(a.`FacilityCount`) as2, sum(a.`FacilityOpenCount`) as3 FROM `ts_facility` a WHERE (a.`Date` = cast(date_format('2020-12-30 00:00:00.000','%Y-%m-%d') as datetime)) AND (((a.`EnterpriseId`) in (5))) AND (a.`FacilityType` = 1) GROUP BY a.`Dot`", sql); diff --git a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/LazyLoadingTest .cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/LazyLoadingTest .cs new file mode 100644 index 000000000..834d83b8b --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/LazyLoadingTest .cs @@ -0,0 +1,275 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Xunit; + +namespace FreeSql.Tests.PostgreSQL.PgArrayToMany +{ + public class LazyLoadingTest + { + + [Table(Name = "pgarray_tomany_user_lazyloading")] + public class User + { + [Column(IsPrimary = true)] + public int UserId { get; set; } + public int[] RoleIds { get; set; } + public string UserName { get; set; } + + [Navigate(nameof(RoleIds))] + public virtual List Roles { get; set; } + } + + [Table(Name = "pgarray_tomany_role_lazyloading")] + public class Role + { + [Column(IsPrimary = true)] + public int RoleId { get; set; } + public string RoleName { get; set; } + + [Navigate(nameof(User.RoleIds))] + public virtual List Users { get; set; } + } + + [Fact] + public void LazyLoading() + { + var fsql = g.pgsql; + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + + var roles = new[] + { + new Role { RoleId = 1, RoleName = "role1" }, + new Role { RoleId = 2, RoleName = "role2" }, + new Role { RoleId = 3, RoleName = "role3" } + }; + Assert.Equal(3, fsql.Insert(roles).ExecuteAffrows()); + var users = new[] + { + new User { UserId = 11, RoleIds = new [] { 1,2 }, UserName = "user1" }, + new User { UserId = 12, RoleIds = new [] { 1,2,3 }, UserName = "user2" }, + new User { UserId = 13, RoleIds = new [] { 1,3 }, UserName = "user3" }, + new User { UserId = 14, RoleIds = new [] { 3,2,1 }, UserName = "user4" }, + new User { UserId = 15, RoleIds = null, UserName = "user5" }, + new User { UserId = 16, RoleIds = new int[0], UserName = "user6" }, + }; + Assert.Equal(6, fsql.Insert(users).ExecuteAffrows()); + + var users5Select = fsql.Select().Where(a => a.Roles.Any(b => b.RoleName == "role1")); + Assert.Equal(@"SELECT a.""userid"", a.""roleids"", a.""username"" +FROM ""pgarray_tomany_user_lazyloading"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_role_lazyloading"" b + WHERE (a.""roleids"" @> ARRAY[b.""roleid""]::int4[]) AND (b.""rolename"" = 'role1') + limit 1))", users5Select.ToSql()); + var users5 = users5Select.ToList(); + Assert.Equal(4, users5.Count); + users5Select = fsql.Select().Where(a => a.Roles.AsSelect().Any(b => b.RoleName == "role1")); + Assert.Equal(@"SELECT a.""userid"", a.""roleids"", a.""username"" +FROM ""pgarray_tomany_user_lazyloading"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_role_lazyloading"" b + WHERE (b.""rolename"" = 'role1') AND (a.""roleids"" @> ARRAY[b.""roleid""]::int4[]) + limit 1))", users5Select.ToSql()); + users5 = users5Select.ToList(); + Assert.Equal(4, users5.Count); + + var roles5Select = fsql.Select().Where(a => a.Users.Any(b => b.UserName == "user1")); + Assert.Equal(@"SELECT a.""roleid"", a.""rolename"" +FROM ""pgarray_tomany_role_lazyloading"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_user_lazyloading"" b + WHERE (b.""roleids"" @> ARRAY[a.""roleid""]::int4[]) AND (b.""username"" = 'user1') + limit 1))", roles5Select.ToSql()); + var roles5 = roles5Select.ToList(); + Assert.Equal(2, roles5.Count); + roles5Select = fsql.Select().Where(a => a.Users.AsSelect().Any(b => b.UserName == "user1")); + Assert.Equal(@"SELECT a.""roleid"", a.""rolename"" +FROM ""pgarray_tomany_role_lazyloading"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_user_lazyloading"" b + WHERE (b.""username"" = 'user1') AND (b.""roleids"" @> ARRAY[a.""roleid""]::int4[]) + limit 1))", roles5Select.ToSql()); + roles5 = roles5Select.ToList(); + Assert.Equal(2, roles5.Count); + + + var users4 = fsql.Select().IncludeMany(a => a.Roles).ToList(a => new + { + user = a, roles = a.Roles + }); + var roles4 = fsql.Select().IncludeMany(a => a.Users).ToList(a => new + { + role = a, + users = a.Users + }); + + + var users3 = fsql.Select().IncludeMany(a => a.Roles).ToList(); + Assert.Equal(6, users3.Count); + var users2 = users3; + Assert.Equal(11, users2[0].UserId); + Assert.Equal(12, users2[1].UserId); + Assert.Equal(13, users2[2].UserId); + Assert.Equal(14, users2[3].UserId); + Assert.Equal(15, users2[4].UserId); + Assert.Equal(16, users2[5].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("user5", users2[4].UserName); + Assert.Equal("user6", users2[5].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + Assert.Null(users2[4].RoleIds); + Assert.Empty(users2[5].RoleIds); + + var roles2 = users3[0].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + + roles2 = users3[1].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + roles2 = users3[2].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(3, roles2[1].RoleId); + Assert.Equal("role3", roles2[1].RoleName); + + roles2 = users3[3].Roles; + Assert.Equal(3, roles2[0].RoleId); + Assert.Equal("role3", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(1, roles2[2].RoleId); + Assert.Equal("role1", roles2[2].RoleName); + + Assert.Null(users3[4].Roles); + Assert.Empty(users3[5].Roles); + + var roles3 = fsql.Select().IncludeMany(a => a.Users).ToList(); + Assert.Equal(3, roles3.Count); + roles2 = roles3; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + users2 = roles2[0].Users; + Assert.Equal(4, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(13, users2[2].UserId); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal(14, users2[3].UserId); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + + users2 = roles2[1].Users; + Assert.Equal(3, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(14, users2[2].UserId); + Assert.Equal("user4", users2[2].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[2].RoleIds)); + + users2 = roles2[2].Users; + Assert.Equal(3, users2.Count); + Assert.Equal(12, users2[0].UserId); + Assert.Equal("user2", users2[0].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[0].RoleIds)); + Assert.Equal(13, users2[1].UserId); + Assert.Equal("user3", users2[1].UserName); + Assert.Equal("1,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(14, users2[2].UserId); + Assert.Equal("user4", users2[2].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[2].RoleIds)); + + var role = fsql.Select().Where(a => a.RoleId == 1).First(); + Assert.IsNotType(role); + + users2 = role.Users; + Assert.Equal(4, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(13, users2[2].UserId); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal(14, users2[3].UserId); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + + roles2 = users2[0].Roles; + Assert.Equal(2, roles2.Count); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + + roles2 = users2[1].Roles; + Assert.Equal(3, roles2.Count); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + roles2 = users2[2].Roles; + Assert.Equal(2, roles2.Count); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(3, roles2[1].RoleId); + Assert.Equal("role3", roles2[1].RoleName); + + roles2 = users2[3].Roles; + Assert.Equal(3, roles2.Count); + Assert.Equal(3, roles2[0].RoleId); + Assert.Equal("role3", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(1, roles2[2].RoleId); + Assert.Equal("role1", roles2[2].RoleName); + + var user = fsql.Select().Where(a => a.UserId == 11).First(); + Assert.IsNotType(user); + + roles2 = user.Roles; + Assert.Equal(2, roles2.Count); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + } + } +} diff --git a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/NormalTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/NormalTest.cs new file mode 100644 index 000000000..a697dc625 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/NormalTest.cs @@ -0,0 +1,221 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Xunit; + +namespace FreeSql.Tests.PostgreSQL.PgArrayToMany +{ + public class NormalTest + { + + [Table(Name = "pgarray_tomany_user_normal")] + public class User + { + [Column(IsPrimary = true)] + public int UserId { get; set; } + public int[] RoleIds { get; set; } + public string UserName { get; set; } + + [Navigate(nameof(RoleIds))] + public List Roles { get; set; } + } + + [Table(Name = "pgarray_tomany_role_normal")] + public class Role + { + [Column(IsPrimary = true)] + public int RoleId { get; set; } + public string RoleName { get; set; } + + [Navigate(nameof(User.RoleIds))] + public List Users { get; set; } + } + + [Fact] + public void Normal() + { + var fsql = g.pgsql; + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + + var roles = new[] + { + new Role { RoleId = 1, RoleName = "role1" }, + new Role { RoleId = 2, RoleName = "role2" }, + new Role { RoleId = 3, RoleName = "role3" } + }; + Assert.Equal(3, fsql.Insert(roles).ExecuteAffrows()); + var users = new[] + { + new User { UserId = 11, RoleIds = new [] { 1,2 }, UserName = "user1" }, + new User { UserId = 12, RoleIds = new [] { 1,2,3 }, UserName = "user2" }, + new User { UserId = 13, RoleIds = new [] { 1,3 }, UserName = "user3" }, + new User { UserId = 14, RoleIds = new [] { 3,2,1 }, UserName = "user4" }, + new User { UserId = 15, RoleIds = null, UserName = "user5" }, + new User { UserId = 16, RoleIds = new int[0], UserName = "user6" }, + }; + Assert.Equal(6, fsql.Insert(users).ExecuteAffrows()); + + var users5Select = fsql.Select().Where(a => a.Roles.Any(b => b.RoleName == "role1")); + Assert.Equal(@"SELECT a.""userid"", a.""roleids"", a.""username"" +FROM ""pgarray_tomany_user_normal"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_role_normal"" b + WHERE (a.""roleids"" @> ARRAY[b.""roleid""]::int4[]) AND (b.""rolename"" = 'role1') + limit 1))", users5Select.ToSql()); + var users5 = users5Select.ToList(); + Assert.Equal(4, users5.Count); + users5Select = fsql.Select().Where(a => a.Roles.AsSelect().Any(b => b.RoleName == "role1")); + Assert.Equal(@"SELECT a.""userid"", a.""roleids"", a.""username"" +FROM ""pgarray_tomany_user_normal"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_role_normal"" b + WHERE (b.""rolename"" = 'role1') AND (a.""roleids"" @> ARRAY[b.""roleid""]::int4[]) + limit 1))", users5Select.ToSql()); + users5 = users5Select.ToList(); + Assert.Equal(4, users5.Count); + + var roles5Select = fsql.Select().Where(a => a.Users.Any(b => b.UserName == "user1")); + Assert.Equal(@"SELECT a.""roleid"", a.""rolename"" +FROM ""pgarray_tomany_role_normal"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_user_normal"" b + WHERE (b.""roleids"" @> ARRAY[a.""roleid""]::int4[]) AND (b.""username"" = 'user1') + limit 1))", roles5Select.ToSql()); + var roles5 = roles5Select.ToList(); + Assert.Equal(2, roles5.Count); + roles5Select = fsql.Select().Where(a => a.Users.AsSelect().Any(b => b.UserName == "user1")); + Assert.Equal(@"SELECT a.""roleid"", a.""rolename"" +FROM ""pgarray_tomany_role_normal"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_user_normal"" b + WHERE (b.""username"" = 'user1') AND (b.""roleids"" @> ARRAY[a.""roleid""]::int4[]) + limit 1))", roles5Select.ToSql()); + roles5 = roles5Select.ToList(); + Assert.Equal(2, roles5.Count); + + + var users4 = fsql.Select().IncludeMany(a => a.Roles).ToList(a => new + { + user = a, roles = a.Roles + }); + var roles4 = fsql.Select().IncludeMany(a => a.Users).ToList(a => new + { + role = a, + users = a.Users + }); + + + var users3 = fsql.Select().IncludeMany(a => a.Roles).ToList(); + Assert.Equal(6, users3.Count); + var users2 = users3; + Assert.Equal(11, users2[0].UserId); + Assert.Equal(12, users2[1].UserId); + Assert.Equal(13, users2[2].UserId); + Assert.Equal(14, users2[3].UserId); + Assert.Equal(15, users2[4].UserId); + Assert.Equal(16, users2[5].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("user5", users2[4].UserName); + Assert.Equal("user6", users2[5].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + Assert.Null(users2[4].RoleIds); + Assert.Empty(users2[5].RoleIds); + + var roles2 = users3[0].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + + roles2 = users3[1].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + roles2 = users3[2].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(3, roles2[1].RoleId); + Assert.Equal("role3", roles2[1].RoleName); + + roles2 = users3[3].Roles; + Assert.Equal(3, roles2[0].RoleId); + Assert.Equal("role3", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(1, roles2[2].RoleId); + Assert.Equal("role1", roles2[2].RoleName); + + Assert.Null(users3[4].Roles); + Assert.Empty(users3[5].Roles); + + var roles3 = fsql.Select().IncludeMany(a => a.Users).ToList(); + Assert.Equal(3, roles3.Count); + roles2 = roles3; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + users2 = roles2[0].Users; + Assert.Equal(4, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(13, users2[2].UserId); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal(14, users2[3].UserId); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + + users2 = roles2[1].Users; + Assert.Equal(3, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(14, users2[2].UserId); + Assert.Equal("user4", users2[2].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[2].RoleIds)); + + users2 = roles2[2].Users; + Assert.Equal(3, users2.Count); + Assert.Equal(12, users2[0].UserId); + Assert.Equal("user2", users2[0].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[0].RoleIds)); + Assert.Equal(13, users2[1].UserId); + Assert.Equal("user3", users2[1].UserName); + Assert.Equal("1,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(14, users2[2].UserId); + Assert.Equal("user4", users2[2].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[2].RoleIds)); + + var role = fsql.Select().Where(a => a.RoleId == 1).First(); + Assert.IsType(role); + + var user = fsql.Select().Where(a => a.UserId == 11).First(); + Assert.IsType(user); + } + } +} diff --git a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/ObservableCollectionLazyLoadingTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/ObservableCollectionLazyLoadingTest.cs new file mode 100644 index 000000000..29d9d271b --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/ObservableCollectionLazyLoadingTest.cs @@ -0,0 +1,275 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Xunit; + +namespace FreeSql.Tests.PostgreSQL.PgArrayToMany +{ + public class ObservableCollectionLazyLoadingTest + { + + [Table(Name = "pgarray_tomany_user_observablecollection_lazyloading")] + public class User + { + [Column(IsPrimary = true)] + public int UserId { get; set; } + public int[] RoleIds { get; set; } + public string UserName { get; set; } + + [Navigate(nameof(RoleIds))] + public virtual List Roles { get; set; } + } + + [Table(Name = "pgarray_tomany_role_observablecollection_lazyloading")] + public class Role + { + [Column(IsPrimary = true)] + public int RoleId { get; set; } + public string RoleName { get; set; } + + [Navigate(nameof(User.RoleIds))] + public virtual List Users { get; set; } + } + + [Fact] + public void ObservableCollectionLazyLoading() + { + var fsql = g.pgsql; + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + + var roles = new[] + { + new Role { RoleId = 1, RoleName = "role1" }, + new Role { RoleId = 2, RoleName = "role2" }, + new Role { RoleId = 3, RoleName = "role3" } + }; + Assert.Equal(3, fsql.Insert(roles).ExecuteAffrows()); + var users = new[] + { + new User { UserId = 11, RoleIds = new [] { 1,2 }, UserName = "user1" }, + new User { UserId = 12, RoleIds = new [] { 1,2,3 }, UserName = "user2" }, + new User { UserId = 13, RoleIds = new [] { 1,3 }, UserName = "user3" }, + new User { UserId = 14, RoleIds = new [] { 3,2,1 }, UserName = "user4" }, + new User { UserId = 15, RoleIds = null, UserName = "user5" }, + new User { UserId = 16, RoleIds = new int[0], UserName = "user6" }, + }; + Assert.Equal(6, fsql.Insert(users).ExecuteAffrows()); + + var users5Select = fsql.Select().Where(a => a.Roles.Any(b => b.RoleName == "role1")); + Assert.Equal(@"SELECT a.""userid"", a.""roleids"", a.""username"" +FROM ""pgarray_tomany_user_observablecollection_lazyloading"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_role_observablecollection_lazyloading"" b + WHERE (a.""roleids"" @> ARRAY[b.""roleid""]::int4[]) AND (b.""rolename"" = 'role1') + limit 1))", users5Select.ToSql()); + var users5 = users5Select.ToList(); + Assert.Equal(4, users5.Count); + users5Select = fsql.Select().Where(a => a.Roles.AsSelect().Any(b => b.RoleName == "role1")); + Assert.Equal(@"SELECT a.""userid"", a.""roleids"", a.""username"" +FROM ""pgarray_tomany_user_observablecollection_lazyloading"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_role_observablecollection_lazyloading"" b + WHERE (b.""rolename"" = 'role1') AND (a.""roleids"" @> ARRAY[b.""roleid""]::int4[]) + limit 1))", users5Select.ToSql()); + users5 = users5Select.ToList(); + Assert.Equal(4, users5.Count); + + var roles5Select = fsql.Select().Where(a => a.Users.Any(b => b.UserName == "user1")); + Assert.Equal(@"SELECT a.""roleid"", a.""rolename"" +FROM ""pgarray_tomany_role_observablecollection_lazyloading"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_user_observablecollection_lazyloading"" b + WHERE (b.""roleids"" @> ARRAY[a.""roleid""]::int4[]) AND (b.""username"" = 'user1') + limit 1))", roles5Select.ToSql()); + var roles5 = roles5Select.ToList(); + Assert.Equal(2, roles5.Count); + roles5Select = fsql.Select().Where(a => a.Users.AsSelect().Any(b => b.UserName == "user1")); + Assert.Equal(@"SELECT a.""roleid"", a.""rolename"" +FROM ""pgarray_tomany_role_observablecollection_lazyloading"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_user_observablecollection_lazyloading"" b + WHERE (b.""username"" = 'user1') AND (b.""roleids"" @> ARRAY[a.""roleid""]::int4[]) + limit 1))", roles5Select.ToSql()); + roles5 = roles5Select.ToList(); + Assert.Equal(2, roles5.Count); + + + var users4 = fsql.Select().IncludeMany(a => a.Roles).ToList(a => new + { + user = a, roles = a.Roles + }); + var roles4 = fsql.Select().IncludeMany(a => a.Users).ToList(a => new + { + role = a, + users = a.Users + }); + + + var users3 = fsql.Select().IncludeMany(a => a.Roles).ToList(); + Assert.Equal(6, users3.Count); + var users2 = users3; + Assert.Equal(11, users2[0].UserId); + Assert.Equal(12, users2[1].UserId); + Assert.Equal(13, users2[2].UserId); + Assert.Equal(14, users2[3].UserId); + Assert.Equal(15, users2[4].UserId); + Assert.Equal(16, users2[5].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("user5", users2[4].UserName); + Assert.Equal("user6", users2[5].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + Assert.Null(users2[4].RoleIds); + Assert.Empty(users2[5].RoleIds); + + var roles2 = users3[0].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + + roles2 = users3[1].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + roles2 = users3[2].Roles; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(3, roles2[1].RoleId); + Assert.Equal("role3", roles2[1].RoleName); + + roles2 = users3[3].Roles; + Assert.Equal(3, roles2[0].RoleId); + Assert.Equal("role3", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(1, roles2[2].RoleId); + Assert.Equal("role1", roles2[2].RoleName); + + Assert.Null(users3[4].Roles); + Assert.Empty(users3[5].Roles); + + var roles3 = fsql.Select().IncludeMany(a => a.Users).ToList(); + Assert.Equal(3, roles3.Count); + roles2 = roles3; + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + users2 = roles2[0].Users; + Assert.Equal(4, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(13, users2[2].UserId); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal(14, users2[3].UserId); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + + users2 = roles2[1].Users; + Assert.Equal(3, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(14, users2[2].UserId); + Assert.Equal("user4", users2[2].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[2].RoleIds)); + + users2 = roles2[2].Users; + Assert.Equal(3, users2.Count); + Assert.Equal(12, users2[0].UserId); + Assert.Equal("user2", users2[0].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[0].RoleIds)); + Assert.Equal(13, users2[1].UserId); + Assert.Equal("user3", users2[1].UserName); + Assert.Equal("1,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(14, users2[2].UserId); + Assert.Equal("user4", users2[2].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[2].RoleIds)); + + var role = fsql.Select().Where(a => a.RoleId == 1).First(); + Assert.IsNotType(role); + + users2 = role.Users; + Assert.Equal(4, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(13, users2[2].UserId); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal(14, users2[3].UserId); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + + roles2 = users2[0].Roles; + Assert.Equal(2, roles2.Count); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + + roles2 = users2[1].Roles; + Assert.Equal(3, roles2.Count); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + roles2 = users2[2].Roles; + Assert.Equal(2, roles2.Count); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(3, roles2[1].RoleId); + Assert.Equal("role3", roles2[1].RoleName); + + roles2 = users2[3].Roles; + Assert.Equal(3, roles2.Count); + Assert.Equal(3, roles2[0].RoleId); + Assert.Equal("role3", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(1, roles2[2].RoleId); + Assert.Equal("role1", roles2[2].RoleName); + + var user = fsql.Select().Where(a => a.UserId == 11).First(); + Assert.IsNotType(user); + + roles2 = user.Roles; + Assert.Equal(2, roles2.Count); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + } + } +} diff --git a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/ObservableCollectionTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/ObservableCollectionTest.cs new file mode 100644 index 000000000..671dadca4 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/PgArrayToMany/ObservableCollectionTest.cs @@ -0,0 +1,223 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Text; +using Xunit; + +namespace FreeSql.Tests.PostgreSQL.PgArrayToMany +{ + public class ObservableCollectionTest + { + + [Table(Name = "pgarray_tomany_user_observablecollection")] + public class User + { + [Column(IsPrimary = true)] + public int UserId { get; set; } + public int[] RoleIds { get; set; } + public string UserName { get; set; } + + [Navigate(nameof(RoleIds))] + public ObservableCollection Roles { get; set; } + } + + [Table(Name = "pgarray_tomany_role_observablecollection")] + public class Role + { + [Column(IsPrimary = true)] + public int RoleId { get; set; } + public string RoleName { get; set; } + + [Navigate(nameof(User.RoleIds))] + public ObservableCollection Users { get; set; } + } + + [Fact] + public void ObservableCollection() + { + var fsql = g.pgsql; + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + + var roles = new[] + { + new Role { RoleId = 1, RoleName = "role1" }, + new Role { RoleId = 2, RoleName = "role2" }, + new Role { RoleId = 3, RoleName = "role3" } + }; + Assert.Equal(3, fsql.Insert(roles).ExecuteAffrows()); + var users = new[] + { + new User { UserId = 11, RoleIds = new [] { 1,2 }, UserName = "user1" }, + new User { UserId = 12, RoleIds = new [] { 1,2,3 }, UserName = "user2" }, + new User { UserId = 13, RoleIds = new [] { 1,3 }, UserName = "user3" }, + new User { UserId = 14, RoleIds = new [] { 3,2,1 }, UserName = "user4" }, + new User { UserId = 15, RoleIds = null, UserName = "user5" }, + new User { UserId = 16, RoleIds = new int[0], UserName = "user6" }, + }; + Assert.Equal(6, fsql.Insert(users).ExecuteAffrows()); + + var users5Select = fsql.Select().Where(a => a.Roles.Any(b => b.RoleName == "role1")); + Assert.Equal(@"SELECT a.""userid"", a.""roleids"", a.""username"" +FROM ""pgarray_tomany_user_observablecollection"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_role_observablecollection"" b + WHERE (a.""roleids"" @> ARRAY[b.""roleid""]::int4[]) AND (b.""rolename"" = 'role1') + limit 1))", users5Select.ToSql()); + var users5 = users5Select.ToList(); + Assert.Equal(4, users5.Count); + users5Select = fsql.Select().Where(a => a.Roles.AsSelect().Any(b => b.RoleName == "role1")); + Assert.Equal(@"SELECT a.""userid"", a.""roleids"", a.""username"" +FROM ""pgarray_tomany_user_observablecollection"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_role_observablecollection"" b + WHERE (b.""rolename"" = 'role1') AND (a.""roleids"" @> ARRAY[b.""roleid""]::int4[]) + limit 1))", users5Select.ToSql()); + users5 = users5Select.ToList(); + Assert.Equal(4, users5.Count); + + var roles5Select = fsql.Select().Where(a => a.Users.Any(b => b.UserName == "user1")); + Assert.Equal(@"SELECT a.""roleid"", a.""rolename"" +FROM ""pgarray_tomany_role_observablecollection"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_user_observablecollection"" b + WHERE (b.""roleids"" @> ARRAY[a.""roleid""]::int4[]) AND (b.""username"" = 'user1') + limit 1))", roles5Select.ToSql()); + var roles5 = roles5Select.ToList(); + Assert.Equal(2, roles5.Count); + roles5Select = fsql.Select().Where(a => a.Users.AsSelect().Any(b => b.UserName == "user1")); + Assert.Equal(@"SELECT a.""roleid"", a.""rolename"" +FROM ""pgarray_tomany_role_observablecollection"" a +WHERE (exists(SELECT 1 + FROM ""pgarray_tomany_user_observablecollection"" b + WHERE (b.""username"" = 'user1') AND (b.""roleids"" @> ARRAY[a.""roleid""]::int4[]) + limit 1))", roles5Select.ToSql()); + roles5 = roles5Select.ToList(); + Assert.Equal(2, roles5.Count); + + + var users4 = fsql.Select().IncludeMany(a => a.Roles).ToList(a => new + { + user = a, + roles = a.Roles + }); + var roles4 = fsql.Select().IncludeMany(a => a.Users).ToList(a => new + { + role = a, + users = a.Users + }); + + + var users3 = fsql.Select().IncludeMany(a => a.Roles).ToList(); + Assert.Equal(6, users3.Count); + var users2 = users3; + Assert.Equal(11, users2[0].UserId); + Assert.Equal(12, users2[1].UserId); + Assert.Equal(13, users2[2].UserId); + Assert.Equal(14, users2[3].UserId); + Assert.Equal(15, users2[4].UserId); + Assert.Equal(16, users2[5].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("user5", users2[4].UserName); + Assert.Equal("user6", users2[5].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + Assert.Null(users2[4].RoleIds); + Assert.Empty(users2[5].RoleIds); + + var roles2 = users3[0].Roles.ToList(); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + + roles2 = users3[1].Roles.ToList(); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + roles2 = users3[2].Roles.ToList(); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(3, roles2[1].RoleId); + Assert.Equal("role3", roles2[1].RoleName); + + roles2 = users3[3].Roles.ToList(); + Assert.Equal(3, roles2[0].RoleId); + Assert.Equal("role3", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(1, roles2[2].RoleId); + Assert.Equal("role1", roles2[2].RoleName); + + Assert.Null(users3[4].Roles); + Assert.Empty(users3[5].Roles); + + var roles3 = fsql.Select().IncludeMany(a => a.Users).ToList(); + Assert.Equal(3, roles3.Count); + roles2 = roles3.ToList(); + Assert.Equal(1, roles2[0].RoleId); + Assert.Equal("role1", roles2[0].RoleName); + Assert.Equal(2, roles2[1].RoleId); + Assert.Equal("role2", roles2[1].RoleName); + Assert.Equal(3, roles2[2].RoleId); + Assert.Equal("role3", roles2[2].RoleName); + + users2 = roles2[0].Users.ToList(); + Assert.Equal(4, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(13, users2[2].UserId); + Assert.Equal("user3", users2[2].UserName); + Assert.Equal("1,3", string.Join(",", users2[2].RoleIds)); + Assert.Equal(14, users2[3].UserId); + Assert.Equal("user4", users2[3].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[3].RoleIds)); + + users2 = roles2[1].Users.ToList(); + Assert.Equal(3, users2.Count); + Assert.Equal(11, users2[0].UserId); + Assert.Equal("user1", users2[0].UserName); + Assert.Equal("1,2", string.Join(",", users2[0].RoleIds)); + Assert.Equal(12, users2[1].UserId); + Assert.Equal("user2", users2[1].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(14, users2[2].UserId); + Assert.Equal("user4", users2[2].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[2].RoleIds)); + + users2 = roles2[2].Users.ToList(); + Assert.Equal(3, users2.Count); + Assert.Equal(12, users2[0].UserId); + Assert.Equal("user2", users2[0].UserName); + Assert.Equal("1,2,3", string.Join(",", users2[0].RoleIds)); + Assert.Equal(13, users2[1].UserId); + Assert.Equal("user3", users2[1].UserName); + Assert.Equal("1,3", string.Join(",", users2[1].RoleIds)); + Assert.Equal(14, users2[2].UserId); + Assert.Equal("user4", users2[2].UserName); + Assert.Equal("3,2,1", string.Join(",", users2[2].RoleIds)); + + var role = fsql.Select().Where(a => a.RoleId == 1).First(); + Assert.IsType(role); + + var user = fsql.Select().Where(a => a.UserId == 11).First(); + Assert.IsType(user); + } + } +} diff --git a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs index e0ffb9c1a..9124dd68f 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs @@ -48,7 +48,7 @@ public static partial class FreeSqlGlobalExtensions }); public static bool IsIntegerType(this Type that) => that == null ? false : (_dicIsNumberType.Value.TryGetValue(that, out var tryval) ? tryval : false); public static bool IsNumberType(this Type that) => that == null ? false : _dicIsNumberType.Value.ContainsKey(that); - public static bool IsNullableType(this Type that) => that.IsArray == false && that?.FullName.StartsWith("System.Nullable`1[") == true; + public static bool IsNullableType(this Type that) => that == null ? false : (that.IsArray == false && that.FullName.StartsWith("System.Nullable`1[") == true); public static bool IsAnonymousType(this Type that) => that == null ? false : (that.FullName.StartsWith("<>f__AnonymousType") || that.FullName.StartsWith("VB$AnonymousType")); public static bool IsArrayOrList(this Type that) => that == null ? false : (that.IsArray || typeof(IList).IsAssignableFrom(that)); public static Type NullableTypeOrThis(this Type that) => that?.IsNullableType() == true ? that.GetGenericArguments().First() : that; diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 3866a3677..434ec7a25 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -3303,6 +3303,177 @@ + + + 测试数据库是否连接正确,本方法执行如下命令: + MySql/SqlServer/PostgreSQL/达梦/人大金仓/神通: SELECT 1 + Oracle: SELECT 1 FROM dual + + 命令超时设置(秒) + + true: 成功, false: 失败 + + + + 查询,若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + + + + + + + + + + 查询,ExecuteReaderAsync(dr => {}, "select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteArrayAsync("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteDataSetAsync("select * from user where age > @age; select 2", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteDataTableAsync("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 在【主库】执行 + + + + + + + + + 在【主库】执行,ExecuteNonQueryAsync("delete from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 在【主库】执行 + + + + + + + + + 在【主库】执行,ExecuteScalarAsync("select 1 from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > @age", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + + 执行SQL返回对象集合,Query<User>("select * from user where age > @age; select * from address", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + + + 执行SQL返回对象集合,Query<User, Address>("select * from user where age > @age; select * from address", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + 可自定义解析表达式 @@ -4269,6 +4440,23 @@ 中间表,多对多 + + + PostgreSQL 数组类型专属功能 + 方式一:select * from Role where Id in (RoleIds) + class User { + ____public int[] RoleIds { get; set; } + ____[Navigate(nameof(RoleIds))] + ____public List<Role> Roles { get; set; } + } + 方式二:select * from User where RoleIds @> Id + class Role { + ____public int Id { get; set; } + ____[Navigate(nameof(User.RoleIds))] + ____public List<User> Users { get; set; } + } + + 是否可用 @@ -4309,6 +4497,12 @@ 超时 + + + 获取资源 + + + 使用完毕后,归还资源 @@ -4384,6 +4578,12 @@ 资源对象 + + + 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 + + 资源对象 + 归还对象给对象池的时候触发 diff --git a/FreeSql/Internal/CommonExpression.cs b/FreeSql/Internal/CommonExpression.cs index c806edd36..9b22e35d0 100644 --- a/FreeSql/Internal/CommonExpression.cs +++ b/FreeSql/Internal/CommonExpression.cs @@ -154,6 +154,7 @@ public bool ReadAnonymousField(List _tables, StringBuilder fiel { case TableRefType.ManyToMany: case TableRefType.OneToMany: + case TableRefType.PgArrayToMany: continue; } if (_tables.Any(a => a.Alias == $"{map.First().Table.Alias}__{memProp.Name}") == false) continue; @@ -1194,162 +1195,186 @@ public string ExpressionLambdaToSql(Expression exp, ExpTSC tsc) if (fsql != null) { if (asSelectParentExp != null) - { //执行 AsSelect() 的关联,OneToMany,ManyToMany + { + //执行 AsSelect() 的关联,OneToMany,ManyToMany,PgArrayToMany if (fsqltables[0].Parameter == null) { fsqltables[0].Alias = $"tb_{fsqltables.Count}"; fsqltables[0].Parameter = Expression.Parameter(asSelectEntityType, fsqltables[0].Alias); } - var fsqlWhere = _dicExpressionLambdaToSqlAsSelectWhereMethodInfo.GetOrAdd(asSelectEntityType, asSelectEntityType3 => - typeof(ISelect<>).MakeGenericType(asSelectEntityType3).GetMethod("Where", new[] { - typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(asSelectEntityType3, typeof(bool))) - })); + var parm123Tb = _common.GetTableByEntity(asSelectParentExp.Type); var parm123Ref = parm123Tb.GetTableRef(asSelectParentExp1.Member.Name, true); if (parm123Ref != null) { - var fsqlWhereParam = fsqltables.First().Parameter; //Expression.Parameter(asSelectEntityType); - Expression fsqlWhereExp = null; - if (parm123Ref.RefType == TableRefType.ManyToMany) + if (parm123Ref.RefType == TableRefType.PgArrayToMany) { - //g.mysql.Select().Where(a => g.mysql.Select().Where(b => b.Tag_id == a.Id && b.Song_id == 1).Any()); - var manyTb = _common.GetTableByEntity(parm123Ref.RefMiddleEntityType); - var manySubSelectWhere = _dicExpressionLambdaToSqlAsSelectWhereMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => - typeof(ISelect<>).MakeGenericType(refMiddleEntityType3).GetMethod("Where", new[] { - typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(refMiddleEntityType3, typeof(bool))) - })); - var manySubSelectWhereSql = _dicExpressionLambdaToSqlAsSelectWhereSqlMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => - typeof(ISelect0<,>).MakeGenericType(typeof(ISelect<>).MakeGenericType(refMiddleEntityType3), refMiddleEntityType3).GetMethod("Where", new[] { typeof(string), typeof(object) })); - var manySubSelectAsSelectExp = _dicFreeSqlGlobalExtensionsAsSelectExpression.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => - Expression.Call( - typeof(FreeSqlGlobalExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(mfil => mfil.Name == "AsSelect" && mfil.GetParameters().Length == 1).FirstOrDefault()?.MakeGenericMethod(refMiddleEntityType3), - Expression.Constant(Activator.CreateInstance(typeof(List<>).MakeGenericType(refMiddleEntityType3))) - )); - var manyMainParam = tsc._tables[0].Parameter; - var manySubSelectWhereParam = Expression.Parameter(parm123Ref.RefMiddleEntityType, $"M{fsqlWhereParam.Name}_M{asSelectParentExp.ToString().Replace(".", "__")}");//, $"{fsqlWhereParam.Name}__"); - Expression manySubSelectWhereExp = null; - for (var mn = 0; mn < parm123Ref.Columns.Count; mn++) + var amtReftbname = ExpressionLambdaToSql(Expression.MakeMemberAccess(asSelectParentExp, parm123Tb.Properties[parm123Tb.ColumnsByPosition[0].CsName]), tsc); + amtReftbname = amtReftbname.Substring(0, amtReftbname.Length - _common.QuoteSqlName(parm123Tb.ColumnsByPosition[0].Attribute.Name).Length - 1); + if (parm123Ref.RefColumns[0] == fsqltables[0].Table.Primarys[0]) { - var col1 = parm123Ref.MiddleColumns[mn]; - var col2 = parm123Ref.Columns[mn]; - var pexp1 = Expression.Property(manySubSelectWhereParam, col1.CsName); - var pexp2 = Expression.Property(asSelectParentExp, col2.CsName); - if (col1.CsType != col2.CsType) + var dbinfo = _common._orm.CodeFirst.GetDbInfo(parm123Ref.Columns[0].CsType); + (fsql as Select0Provider)._where.Append(" AND (").Append($"{amtReftbname}.{_common.QuoteSqlName(parm123Ref.Columns[0].Attribute.Name)} @> ARRAY[{fsqltables[0].Alias}.{_common.QuoteSqlName(parm123Ref.RefColumns[0].Attribute.Name)}]::{dbinfo?.dbtype}").Append(")"); + } + else if (parm123Ref.Columns[0] == parm123Tb.Primarys[0]) + { + var dbinfo = _common._orm.CodeFirst.GetDbInfo(parm123Ref.RefColumns[0].CsType); + (fsql as Select0Provider)._where.Append(" AND (").Append($"{fsqltables[0].Alias}.{_common.QuoteSqlName(parm123Ref.RefColumns[0].Attribute.Name)} @> ARRAY[{amtReftbname}.{_common.QuoteSqlName(parm123Ref.Columns[0].Attribute.Name)}]::{dbinfo?.dbtype}").Append(")"); + } + else + { + ; + } + } + else + { + var fsqlWhere = _dicExpressionLambdaToSqlAsSelectWhereMethodInfo.GetOrAdd(asSelectEntityType, asSelectEntityType3 => + typeof(ISelect<>).MakeGenericType(asSelectEntityType3).GetMethod("Where", new[] { + typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(asSelectEntityType3, typeof(bool))) + })); + var fsqlWhereParam = fsqltables.First().Parameter; //Expression.Parameter(asSelectEntityType); + Expression fsqlWhereExp = null; + if (parm123Ref.RefType == TableRefType.ManyToMany) + { + //g.mysql.Select().Where(a => g.mysql.Select().Where(b => b.Tag_id == a.Id && b.Song_id == 1).Any()); + var manyTb = _common.GetTableByEntity(parm123Ref.RefMiddleEntityType); + var manySubSelectWhere = _dicExpressionLambdaToSqlAsSelectWhereMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => + typeof(ISelect<>).MakeGenericType(refMiddleEntityType3).GetMethod("Where", new[] { + typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(refMiddleEntityType3, typeof(bool))) + })); + var manySubSelectWhereSql = _dicExpressionLambdaToSqlAsSelectWhereSqlMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => + typeof(ISelect0<,>).MakeGenericType(typeof(ISelect<>).MakeGenericType(refMiddleEntityType3), refMiddleEntityType3).GetMethod("Where", new[] { typeof(string), typeof(object) })); + var manySubSelectAsSelectExp = _dicFreeSqlGlobalExtensionsAsSelectExpression.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => + Expression.Call( + typeof(FreeSqlGlobalExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(mfil => mfil.Name == "AsSelect" && mfil.GetParameters().Length == 1).FirstOrDefault()?.MakeGenericMethod(refMiddleEntityType3), + Expression.Constant(Activator.CreateInstance(typeof(List<>).MakeGenericType(refMiddleEntityType3))) + )); + var manyMainParam = tsc._tables[0].Parameter; + var manySubSelectWhereParam = Expression.Parameter(parm123Ref.RefMiddleEntityType, $"M{fsqlWhereParam.Name}_M{asSelectParentExp.ToString().Replace(".", "__")}");//, $"{fsqlWhereParam.Name}__"); + Expression manySubSelectWhereExp = null; + for (var mn = 0; mn < parm123Ref.Columns.Count; mn++) { - if (col1.CsType.IsNullableType()) pexp1 = Expression.Property(pexp1, _dicNullableValueProperty.GetOrAdd(col1.CsType, ct1 => ct1.GetProperty("Value"))); - if (col2.CsType.IsNullableType()) pexp2 = Expression.Property(pexp2, _dicNullableValueProperty.GetOrAdd(col2.CsType, ct2 => ct2.GetProperty("Value"))); + var col1 = parm123Ref.MiddleColumns[mn]; + var col2 = parm123Ref.Columns[mn]; + var pexp1 = Expression.Property(manySubSelectWhereParam, col1.CsName); + var pexp2 = Expression.Property(asSelectParentExp, col2.CsName); + if (col1.CsType != col2.CsType) + { + if (col1.CsType.IsNullableType()) pexp1 = Expression.Property(pexp1, _dicNullableValueProperty.GetOrAdd(col1.CsType, ct1 => ct1.GetProperty("Value"))); + if (col2.CsType.IsNullableType()) pexp2 = Expression.Property(pexp2, _dicNullableValueProperty.GetOrAdd(col2.CsType, ct2 => ct2.GetProperty("Value"))); + } + var tmpExp = Expression.Equal(pexp1, pexp2); + if (mn == 0) manySubSelectWhereExp = tmpExp; + else manySubSelectWhereExp = Expression.AndAlso(manySubSelectWhereExp, tmpExp); } - var tmpExp = Expression.Equal(pexp1, pexp2); - if (mn == 0) manySubSelectWhereExp = tmpExp; - else manySubSelectWhereExp = Expression.AndAlso(manySubSelectWhereExp, tmpExp); + var manySubSelectExpBoy = Expression.Call( + manySubSelectAsSelectExp, + manySubSelectWhere, + Expression.Lambda( + manySubSelectWhereExp, + manySubSelectWhereParam + ) + ); + Expression fsqlManyWhereExp = null; + for (var mn = 0; mn < parm123Ref.RefColumns.Count; mn++) + { + var col1 = parm123Ref.RefColumns[mn]; + var col2 = parm123Ref.MiddleColumns[mn + parm123Ref.Columns.Count + mn]; + var pexp1 = Expression.Property(fsqlWhereParam, col1.CsName); + var pexp2 = Expression.Property(manySubSelectWhereParam, col2.CsName); + if (col1.CsType != col2.CsType) + { + if (col1.CsType.IsNullableType()) pexp1 = Expression.Property(pexp1, _dicNullableValueProperty.GetOrAdd(col1.CsType, ct1 => ct1.GetProperty("Value"))); + if (col2.CsType.IsNullableType()) pexp2 = Expression.Property(pexp2, _dicNullableValueProperty.GetOrAdd(col2.CsType, ct2 => ct2.GetProperty("Value"))); + } + var tmpExp = Expression.Equal(pexp1, pexp2); + if (mn == 0) fsqlManyWhereExp = tmpExp; + else fsqlManyWhereExp = Expression.AndAlso(fsqlManyWhereExp, tmpExp); + } + MethodInfo manySubSelectAggMethod = null; + switch (exp3.Method.Name) //https://github.com/dotnetcore/FreeSql/issues/362 + { + case "Any": + case "Count": + fsqltables.Add(new SelectTableInfo { Alias = manySubSelectWhereParam.Name, Parameter = manySubSelectWhereParam, Table = manyTb, Type = SelectTableInfoType.Parent }); + fsqlWhere.Invoke(fsql, new object[] { Expression.Lambda(fsqlManyWhereExp, fsqlWhereParam) }); + var sql2 = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { "1" })?.ToString(); + if (string.IsNullOrEmpty(sql2) == false) + manySubSelectExpBoy = Expression.Call(manySubSelectExpBoy, manySubSelectWhereSql, Expression.Constant($"exists({sql2.Replace(" \r\n", " \r\n ")})"), Expression.Constant(null)); + manySubSelectAggMethod = _dicExpressionLambdaToSqlAsSelectAggMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, _ => new ConcurrentDictionary()).GetOrAdd(exp3.Method.Name, exp3MethodName => + typeof(ISelect0<,>).MakeGenericType(typeof(ISelect<>).MakeGenericType(parm123Ref.RefMiddleEntityType), parm123Ref.RefMiddleEntityType).GetMethod(exp3MethodName, new Type[0])); + manySubSelectExpBoy = Expression.Call(manySubSelectExpBoy, manySubSelectAggMethod); + break; + case "Sum": + case "Min": + case "Max": + case "Avg": + case "ToList": + case "ToOne": + case "First": + //解析:string.Join(",", w.Roles.AsSelect().ToList(b => b.RoleName) + var exp3Args0 = (exp3.Arguments[0] as UnaryExpression)?.Operand as LambdaExpression; + manySubSelectAggMethod = _dicSelectMethodToSql.GetOrAdd(fsqlType, fsqlType2 => + fsqlType2.GetMethods().Where(a => a.Name == "ToSql" && a.GetParameters().Length == 2 && a.GetParameters()[1].ParameterType == typeof(FieldAliasOptions) && a.GetGenericArguments().Length == 1).FirstOrDefault()); + if (manySubSelectAggMethod == null || exp3Args0 == null) throw new ArgumentException(CoreStrings.ManyToMany_AsSelect_NotSupport_Sum_Avg_etc); + manySubSelectAggMethod = manySubSelectAggMethod.MakeGenericMethod(exp3Args0.ReturnType); + var fsqls0p = fsql as Select0Provider; + var fsqls0pWhere = fsqls0p._where.ToString(); + fsqls0p._where.Clear(); + var fsqltablesLast = new SelectTableInfo { Alias = manySubSelectWhereParam.Name, Parameter = manySubSelectWhereParam, Table = manyTb, Type = SelectTableInfoType.InnerJoin }; + fsqltables.Add(fsqltablesLast); + fsqlWhere.Invoke(fsql, new object[] { Expression.Lambda(fsqlManyWhereExp, fsqlWhereParam) }); + fsqltablesLast.NavigateCondition = fsqls0p._where.ToString(); + if (fsqltablesLast.NavigateCondition.StartsWith(" AND (")) fsqltablesLast.NavigateCondition = fsqltablesLast.NavigateCondition.Substring(6, fsqltablesLast.NavigateCondition.Length - 7); + fsqls0p._where.Clear().Append(fsqls0pWhere); + var tsc3 = tsc.CloneDisableDiyParse(); + tsc3._tables = tsc._tables.ToList(); + var where2 = ExpressionLambdaToSql(Expression.Lambda(manySubSelectWhereExp, manySubSelectWhereParam), tsc3); + if (string.IsNullOrEmpty(where2) == false) fsqls0p._where.Append(" AND (").Append(where2).Append(")"); + + switch (exp3.Method.Name) + { + case "Sum": + case "Min": + case "Max": + case "Avg": + var map = new ReadAnonymousTypeInfo(); + var field = new StringBuilder(); + var index = -1; + + for (var a = 0; a < exp3Args0.Parameters.Count; a++) fsqls0p._tables[a].Parameter = exp3Args0.Parameters[a]; + ReadAnonymousField(fsqls0p._tables, field, map, ref index, exp3Args0, null, null, null, null, null, false); + var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null; + + var sql4 = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { $"{exp3.Method.Name.ToLower()}({fieldSql})" })?.ToString(); + asSelectBefores.Clear(); + return _common.IsNull($"({sql4.Replace(" \r\n", " \r\n ")})", formatSql(exp3.Method.ReturnType.CreateInstanceGetDefaultValue(), exp3.Method.ReturnType, null, null)); + } + + var sql3 = manySubSelectAggMethod.Invoke(fsql, new object[] { exp3Args0, FieldAliasOptions.AsProperty }) as string; + asSelectBefores.Clear(); + return $"({sql3.Replace(" \r\n", " \r\n ")})"; + } + asSelectBefores.Clear(); + return ExpressionLambdaToSql(manySubSelectExpBoy, tsc); } - var manySubSelectExpBoy = Expression.Call( - manySubSelectAsSelectExp, - manySubSelectWhere, - Expression.Lambda( - manySubSelectWhereExp, - manySubSelectWhereParam - ) - ); - Expression fsqlManyWhereExp = null; - for (var mn = 0; mn < parm123Ref.RefColumns.Count; mn++) + for (var mn = 0; mn < parm123Ref.Columns.Count; mn++) { var col1 = parm123Ref.RefColumns[mn]; - var col2 = parm123Ref.MiddleColumns[mn + parm123Ref.Columns.Count + mn]; + var col2 = parm123Ref.Columns[mn]; var pexp1 = Expression.Property(fsqlWhereParam, col1.CsName); - var pexp2 = Expression.Property(manySubSelectWhereParam, col2.CsName); + var pexp2 = Expression.Property(asSelectParentExp, col2.CsName); if (col1.CsType != col2.CsType) { if (col1.CsType.IsNullableType()) pexp1 = Expression.Property(pexp1, _dicNullableValueProperty.GetOrAdd(col1.CsType, ct1 => ct1.GetProperty("Value"))); if (col2.CsType.IsNullableType()) pexp2 = Expression.Property(pexp2, _dicNullableValueProperty.GetOrAdd(col2.CsType, ct2 => ct2.GetProperty("Value"))); } var tmpExp = Expression.Equal(pexp1, pexp2); - if (mn == 0) fsqlManyWhereExp = tmpExp; - else fsqlManyWhereExp = Expression.AndAlso(fsqlManyWhereExp, tmpExp); - } - MethodInfo manySubSelectAggMethod = null; - switch (exp3.Method.Name) //https://github.com/dotnetcore/FreeSql/issues/362 - { - case "Any": - case "Count": - fsqltables.Add(new SelectTableInfo { Alias = manySubSelectWhereParam.Name, Parameter = manySubSelectWhereParam, Table = manyTb, Type = SelectTableInfoType.Parent }); - fsqlWhere.Invoke(fsql, new object[] { Expression.Lambda(fsqlManyWhereExp, fsqlWhereParam) }); - var sql2 = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { "1" })?.ToString(); - if (string.IsNullOrEmpty(sql2) == false) - manySubSelectExpBoy = Expression.Call(manySubSelectExpBoy, manySubSelectWhereSql, Expression.Constant($"exists({sql2.Replace(" \r\n", " \r\n ")})"), Expression.Constant(null)); - manySubSelectAggMethod = _dicExpressionLambdaToSqlAsSelectAggMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, _ => new ConcurrentDictionary()).GetOrAdd(exp3.Method.Name, exp3MethodName => - typeof(ISelect0<,>).MakeGenericType(typeof(ISelect<>).MakeGenericType(parm123Ref.RefMiddleEntityType), parm123Ref.RefMiddleEntityType).GetMethod(exp3MethodName, new Type[0])); - manySubSelectExpBoy = Expression.Call(manySubSelectExpBoy, manySubSelectAggMethod); - break; - case "Sum": - case "Min": - case "Max": - case "Avg": - case "ToList": - case "ToOne": - case "First": - //解析:string.Join(",", w.Roles.AsSelect().ToList(b => b.RoleName) - var exp3Args0 = (exp3.Arguments[0] as UnaryExpression)?.Operand as LambdaExpression; - manySubSelectAggMethod = _dicSelectMethodToSql.GetOrAdd(fsqlType, fsqlType2 => - fsqlType2.GetMethods().Where(a => a.Name == "ToSql" && a.GetParameters().Length == 2 && a.GetParameters()[1].ParameterType == typeof(FieldAliasOptions) && a.GetGenericArguments().Length == 1).FirstOrDefault()); - if (manySubSelectAggMethod == null || exp3Args0 == null) throw new ArgumentException(CoreStrings.ManyToMany_AsSelect_NotSupport_Sum_Avg_etc); - manySubSelectAggMethod = manySubSelectAggMethod.MakeGenericMethod(exp3Args0.ReturnType); - var fsqls0p = fsql as Select0Provider; - var fsqls0pWhere = fsqls0p._where.ToString(); - fsqls0p._where.Clear(); - var fsqltablesLast = new SelectTableInfo { Alias = manySubSelectWhereParam.Name, Parameter = manySubSelectWhereParam, Table = manyTb, Type = SelectTableInfoType.InnerJoin }; - fsqltables.Add(fsqltablesLast); - fsqlWhere.Invoke(fsql, new object[] { Expression.Lambda(fsqlManyWhereExp, fsqlWhereParam) }); - fsqltablesLast.NavigateCondition = fsqls0p._where.ToString(); - if (fsqltablesLast.NavigateCondition.StartsWith(" AND (")) fsqltablesLast.NavigateCondition = fsqltablesLast.NavigateCondition.Substring(6, fsqltablesLast.NavigateCondition.Length - 7); - fsqls0p._where.Clear().Append(fsqls0pWhere); - var tsc3 = tsc.CloneDisableDiyParse(); - tsc3._tables = tsc._tables.ToList(); - var where2 = ExpressionLambdaToSql(Expression.Lambda(manySubSelectWhereExp, manySubSelectWhereParam), tsc3); - if (string.IsNullOrEmpty(where2) == false) fsqls0p._where.Append(" AND (").Append(where2).Append(")"); - - switch (exp3.Method.Name) - { - case "Sum": - case "Min": - case "Max": - case "Avg": - var map = new ReadAnonymousTypeInfo(); - var field = new StringBuilder(); - var index = -1; - - for (var a = 0; a < exp3Args0.Parameters.Count; a++) fsqls0p._tables[a].Parameter = exp3Args0.Parameters[a]; - ReadAnonymousField(fsqls0p._tables, field, map, ref index, exp3Args0, null, null, null, null, null, false); - var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null; - - var sql4 = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { $"{exp3.Method.Name.ToLower()}({fieldSql})" })?.ToString(); - asSelectBefores.Clear(); - return _common.IsNull($"({sql4.Replace(" \r\n", " \r\n ")})", formatSql(exp3.Method.ReturnType.CreateInstanceGetDefaultValue(), exp3.Method.ReturnType, null, null)); - } - - var sql3 = manySubSelectAggMethod.Invoke(fsql, new object[] { exp3Args0, FieldAliasOptions.AsProperty }) as string; - asSelectBefores.Clear(); - return $"({sql3.Replace(" \r\n", " \r\n ")})"; - } - asSelectBefores.Clear(); - return ExpressionLambdaToSql(manySubSelectExpBoy, tsc); - } - for (var mn = 0; mn < parm123Ref.Columns.Count; mn++) - { - var col1 = parm123Ref.RefColumns[mn]; - var col2 = parm123Ref.Columns[mn]; - var pexp1 = Expression.Property(fsqlWhereParam, col1.CsName); - var pexp2 = Expression.Property(asSelectParentExp, col2.CsName); - if (col1.CsType != col2.CsType) - { - if (col1.CsType.IsNullableType()) pexp1 = Expression.Property(pexp1, _dicNullableValueProperty.GetOrAdd(col1.CsType, ct1 => ct1.GetProperty("Value"))); - if (col2.CsType.IsNullableType()) pexp2 = Expression.Property(pexp2, _dicNullableValueProperty.GetOrAdd(col2.CsType, ct2 => ct2.GetProperty("Value"))); + if (mn == 0) fsqlWhereExp = tmpExp; + else fsqlWhereExp = Expression.AndAlso(fsqlWhereExp, tmpExp); } - var tmpExp = Expression.Equal(pexp1, pexp2); - if (mn == 0) fsqlWhereExp = tmpExp; - else fsqlWhereExp = Expression.AndAlso(fsqlWhereExp, tmpExp); + fsqlWhere.Invoke(fsql, new object[] { Expression.Lambda(fsqlWhereExp, fsqlWhereParam) }); } - fsqlWhere.Invoke(fsql, new object[] { Expression.Lambda(fsqlWhereExp, fsqlWhereParam) }); } } asSelectBefores.Clear(); @@ -2186,7 +2211,7 @@ void LocalSetSelectProviderAlias(string alias) midSelect.Where($"{midSelect._tables[0].Alias}.{commonExp._common.QuoteSqlName(memberTbref.MiddleColumns[memberTbref.Columns.Count + tidx].Attribute.Name)} = {select._tables[0].Alias}.{commonExp._common.QuoteSqlName(memberTbref.RefColumns[tidx].Attribute.Name)}"); for (var tidx = 0; tidx < memberTbref.Columns.Count; tidx++) midSelect.Where($"{midSelect._tables[0].Alias}.{commonExp._common.QuoteSqlName(memberTbref.MiddleColumns[tidx].Attribute.Name)} = {mtmReftbname}.{commonExp._common.QuoteSqlName(memberTbref.Columns[tidx].Attribute.Name)}"); - select.Where($"exists({midSelect.ToSql("1").Replace(" \r\n", " \r\n ")})"); + select._where.Append($" AND exists({midSelect.ToSql("1").Replace(" \r\n", " \r\n ")})"); break; case TableRefType.OneToMany: var omtReftbname = e.FreeParse(Expression.MakeMemberAccess(memberExp.Expression, exp3Tb.Properties[exp3Tb.ColumnsByPosition[0].CsName])); @@ -2194,6 +2219,24 @@ void LocalSetSelectProviderAlias(string alias) for (var tidx = 0; tidx < memberTbref.Columns.Count; tidx++) select.Where($"{select._tables[0].Alias}.{commonExp._common.QuoteSqlName(memberTbref.RefColumns[tidx].Attribute.Name)} = {omtReftbname}.{commonExp._common.QuoteSqlName(memberTbref.Columns[tidx].Attribute.Name)}"); break; + case TableRefType.PgArrayToMany: + var amtReftbname = e.FreeParse(Expression.MakeMemberAccess(memberExp.Expression, exp3Tb.Properties[exp3Tb.ColumnsByPosition[0].CsName])); + amtReftbname = amtReftbname.Substring(0, amtReftbname.Length - commonExp._common.QuoteSqlName(exp3Tb.ColumnsByPosition[0].Attribute.Name).Length - 1); + if (memberTbref.RefColumns[0] == select._tables[0].Table.Primarys[0]) + { + var dbinfo = commonExp._common._orm.CodeFirst.GetDbInfo(memberTbref.Columns[0].CsType); + select.Where($"{amtReftbname}.{commonExp._common.QuoteSqlName(memberTbref.Columns[0].Attribute.Name)} @> ARRAY[{select._tables[0].Alias}.{commonExp._common.QuoteSqlName(memberTbref.RefColumns[0].Attribute.Name)}]::{dbinfo?.dbtype}"); + } + else if (memberTbref.Columns[0] == exp3Tb.Primarys[0]) + { + var dbinfo = commonExp._common._orm.CodeFirst.GetDbInfo(memberTbref.RefColumns[0].CsType); + select.Where($"{select._tables[0].Alias}.{commonExp._common.QuoteSqlName(memberTbref.RefColumns[0].Attribute.Name)} @> ARRAY[{amtReftbname}.{commonExp._common.QuoteSqlName(memberTbref.Columns[0].Attribute.Name)}]::{dbinfo?.dbtype}"); + } + else + { + ; + } + break; } } @@ -2241,6 +2284,10 @@ void LocalInitSelectProvider() if (select != null) return; LocalInitSelectProvider(); continue; + case TableRefType.PgArrayToMany: + if (select != null) return; + LocalInitSelectProvider(); + continue; } } if (exp4.NodeType == ExpressionType.Call) diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs index e28b43d1f..7f8a81d49 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs @@ -222,6 +222,17 @@ MemberInitExpression GetIncludeManyNewInitExpression(IncludeManyNewInit imni) var bindings = new List(); if (imni.IsOutputPrimary) bindings.AddRange(imni.Table.Primarys.Select(a => Expression.Bind(imni.Table.Properties[a.CsName], Expression.MakeMemberAccess(imni.CurrentExpression, imni.Table.Properties[a.CsName])))); if (imni.Childs.Any()) bindings.AddRange(imni.Childs.Select(a => Expression.Bind(imni.Table.Properties[a.Key], GetIncludeManyNewInitExpression(a.Value)))); + var pgarrayToManys = imni.Table.GetAllTableRef().Select(tr => + { + if (tr.Value.RefType != TableRefType.PgArrayToMany) return null; + var reftb = _orm.CodeFirst.GetTableByEntity(tr.Value.RefEntityType); + if (tr.Value.RefColumns[0] == reftb.Primarys[0]) + { + bindings.Add(Expression.Bind(imni.Table.Properties[tr.Value.Columns[0].CsName], Expression.MakeMemberAccess(imni.CurrentExpression, imni.Table.Properties[tr.Value.Columns[0].CsName]))); + return tr.Key; + } + return null; + }).ToList(); return Expression.MemberInit(imni.Table.Type.InternalNewExpression(), bindings); } @@ -418,6 +429,7 @@ public ISelect IncludeByPropertyName(string property) { case TableRefType.ManyToMany: case TableRefType.OneToMany: + case TableRefType.PgArrayToMany: var funcType = typeof(Func<,>).MakeGenericType(_tables[0].Table.Type, typeof(IEnumerable<>).MakeGenericType(parTbref.RefEntityType)); var navigateSelector = Expression.Lambda(funcType, exp, _tables[0].Parameter); var incMethod = this.GetType().GetMethod("IncludeMany"); @@ -668,10 +680,11 @@ public ISelect IncludeMany(Expression); var listValueExp = Expression.Parameter(typeof(List), "listValue"); var setListValue = membersExpNotNull == null ? Expression.Lambda>>( - collMem.Type == typeof(ObservableCollection) ? + isObservableCollection ? (Expression)Expression.IfThen( Expression.NotEqual(listValueExp, Expression.Constant(null, typeof(List))), Expression.Assign(Expression.MakeMemberAccess(membersExp, collMem.Member), Expression.New(typeof(ObservableCollection).GetConstructor(new[] { typeof(List) }), listValueExp)) @@ -679,7 +692,7 @@ public ISelect IncludeMany(Expression>>(Expression.IfThen(membersExpNotNull, - collMem.Type == typeof(ObservableCollection) ? + isObservableCollection ? (Expression)Expression.IfThen( Expression.NotEqual(listValueExp, Expression.Constant(null, typeof(List))), Expression.Assign(Expression.MakeMemberAccess(membersExp, collMem.Member), Expression.New(typeof(ObservableCollection).GetConstructor(new[] { typeof(List) }), listValueExp)) @@ -1172,6 +1185,128 @@ public ISelect IncludeMany(Expression(); + var tbref2 = _commonUtils.GetTableByEntity(tbref.RefEntityType); + if (tbref.RefColumns[0] == tbref2.Primarys[0]) + { + var listKeys = list.Select(a => + { + var arrVal = getListValue(a, tbref.Columns[0].CsName, 0) as Array; + if (arrVal == null) return null; + var arrObjVal = new object[arrVal.Length]; + arrVal.CopyTo(arrObjVal, 0); + return arrObjVal; + }).ToArray(); + var arrExp = Expression.NewArrayInit(tbref.RefColumns[0].CsType, listKeys.Where(a => a != null).SelectMany(a => a).Distinct() + .Select(a => Expression.Constant(Utils.GetDataReaderValue(tbref.RefColumns[0].CsType, a), tbref.RefColumns[0].CsType)).ToArray()); + var otmExpParm1 = Expression.Parameter(typeof(TNavigate), "a"); + var containsMethod = _dicTypeMethod.GetOrAdd(tbref.RefColumns[0].CsType, et => new ConcurrentDictionary()).GetOrAdd("Contains", mn => + typeof(Enumerable).GetMethods().Where(a => a.Name == mn).First()).MakeGenericMethod(tbref.RefColumns[0].CsType); + var refCol = Expression.MakeMemberAccess(otmExpParm1, tbref2.Properties[tbref.RefColumns[0].CsName]); + subSelect.Where(Expression.Lambda>( + Expression.Call(null, containsMethod, arrExp, refCol), otmExpParm1)); + + if (isAsync) + { +#if net40 +#else + if (selectExp == null) subList = await subSelect.ToListAsync(true, cancellationToken); + else subList = await subSelect.ToListAsync(selectExp, cancellationToken); +#endif + } + else + { + if (selectExp == null) subList = subSelect.ToList(true); + else subList = subSelect.ToList(selectExp); + } + + if (subList.Any() == false) + { + foreach (var item in list) + setListValue(item, new List()); + return; + } + var dicSubList = subList.ToDictionary(a => EntityUtilExtensions.GetEntityValueWithPropertyName(_orm, tbref.RefEntityType, a, tbref.RefColumns[0].CsName)?.ToString(), a => a); + + var parentNavs = new List(); + foreach (var navProp in tbref2.Properties) + { + if (tbref2.ColumnsByCs.ContainsKey(navProp.Key)) continue; + if (tbref2.ColumnsByCsIgnore.ContainsKey(navProp.Key)) continue; + var tr2ref = tbref2.GetTableRef(navProp.Key, false); + if (tr2ref == null) continue; + if (tr2ref.RefType != TableRefType.ManyToOne) continue; + if (tr2ref.RefEntityType != tb.Type) continue; + if (string.Join(",", tr2ref.Columns.Select(a => a.CsName).OrderBy(a => a)) != string.Join(",", tbref.RefColumns.Select(a => a.CsName).OrderBy(a => a))) continue; //- 修复 IncludeMany 只填充子属性中双向关系的 ManyToOne 对象值;防止把 ManyToOne 多个相同类型的导航属性值都填充了 + parentNavs.Add(navProp.Key); + } + for (var y = 0; y < list.Count; y++) + { + var item = list[y]; + var dicListKeys = listKeys[y]; + if (dicListKeys == null) continue; + var navs = new List(); + foreach (var dlk in dicListKeys) + { + if (dlk == null) + { + navs.Add(null); + continue; + } + var dicListKey = dlk.ToString(); + dicSubList.TryGetValue(dicListKey, out var nav); + navs.Add(nav); + } + setListValue(item, navs); + } + dicSubList.Clear(); + subList.Clear(); + } + else if (tbref.Columns[0] == tb.Primarys[0]) + { + var listKeys = list.Select(a => getListValue(a, tbref.Columns[0].CsName, 0)).Distinct() + .Select(a => Utils.GetDataReaderValue(tbref.RefColumns[0].CsType.GetElementType(), a)).ToArray(); + var listKeysSql = _commonUtils.GetNoneParamaterSqlValue(subSelect._params, "arrtm", tbref.RefColumns[0], tbref.RefColumns[0].CsType, listKeys); + subSelect.Where($"{subSelectT1Alias}.{_commonUtils.QuoteSqlName(tbref.RefColumns[0].Attribute.Name)} && {listKeysSql}"); + + if (isAsync) + { +#if net40 +#else + if (selectExp == null) subList = await subSelect.ToListAsync(true, cancellationToken); + else subList = await subSelect.ToListAsync(selectExp, cancellationToken); +#endif + } + else + { + if (selectExp == null) subList = subSelect.ToList(true); + else subList = subSelect.ToList(selectExp); + } + + if (subList.Any() == false) + { + foreach (var item in list) + setListValue(item, new List()); + return; + } + var subListDic = subList.Select(a => { + var arrVal = EntityUtilExtensions.GetEntityValueWithPropertyName(_orm, tbref2.Type, a, tbref.RefColumns[0].CsName) as Array; + var arrObjVal = new object[arrVal.Length]; + arrVal.CopyTo(arrObjVal, 0); + return arrObjVal.Select(b => NativeTuple.Create(a, b?.ToString())); + }).SelectMany(a => a).GroupBy(a => a.Item2).ToDictionary(a => a.Key, a => a.Select(b => b.Item1).ToList()); + foreach (var item in list) + { + var itemKey = getListValue(item, tbref.Columns[0].CsName, 0)?.ToString(); + subListDic.TryGetValue(itemKey, out var navs); + setListValue(item, navs); + } + } + } + break; } }; diff --git a/FreeSql/Internal/Model/TableInfo.cs b/FreeSql/Internal/Model/TableInfo.cs index 7de852161..13555dfd2 100644 --- a/FreeSql/Internal/Model/TableInfo.cs +++ b/FreeSql/Internal/Model/TableInfo.cs @@ -48,6 +48,7 @@ public TableRef GetTableRef(string propertyName, bool isThrowException) } return tryref; } + public IEnumerable> GetAllTableRef() => _refs; //public void CopyTo(TableInfo target) //{ @@ -79,7 +80,7 @@ public TableRef GetTableRef(string propertyName, bool isThrowException) // target.VersionColumn = getOrCloneColumn(this.VersionColumn); // foreach (var rf in this._refs) target._refs.TryAdd(rf.Key, new TableRef // { - + // }); @@ -150,6 +151,22 @@ public class TableRef } public enum TableRefType { - OneToOne, ManyToOne, OneToMany, ManyToMany + OneToOne, ManyToOne, OneToMany, ManyToMany, + /// + /// PostgreSQL 数组类型专属功能 + /// 方式一:select * from Role where Id in (RoleIds) + /// class User { + /// ____public int[] RoleIds { get; set; } + /// ____[Navigate(nameof(RoleIds))] + /// ____public List<Role> Roles { get; set; } + /// } + /// 方式二:select * from User where RoleIds @> Id + /// class Role { + /// ____public int Id { get; set; } + /// ____[Navigate(nameof(User.RoleIds))] + /// ____public List<User> Users { get; set; } + /// } + /// + PgArrayToMany } } \ No newline at end of file diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs index a6e0b71d4..23250bb11 100644 --- a/FreeSql/Internal/UtilsExpressionTree.cs +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Data; using System.Data.Common; using System.Linq; @@ -593,7 +594,7 @@ internal static TableInfo GetTableByEntity(Type entity, CommonUtils common) } catch (Exception ex) { - throw new Exception(CoreStrings.LazyLoading_CompilationError(trytbTypeName, ex.Message, cscode)); ; + throw new Exception(CoreStrings.LazyLoading_CompilationError(trytbTypeName, ex.Message, cscode)); } var type = assembly.GetExportedTypes()/*.DefinedTypes*/.Where(a => a.FullName.EndsWith(trytbTypeLazyName)).FirstOrDefault(); trytb.TypeLazy = type; @@ -623,6 +624,7 @@ public static void AddTableRef(CommonUtils common, TableInfo trytb, PropertyInfo //List 或 ICollection,一对多、多对多 var propElementType = pnv.PropertyType.GetGenericArguments().FirstOrDefault() ?? pnv.PropertyType.GetElementType(); + var propTypeIsObservableCollection = propElementType != null && pnv.PropertyType == typeof(ObservableCollection<>).MakeGenericType(propElementType); if (propElementType != null) { if (typeof(IEnumerable).IsAssignableFrom(pnv.PropertyType) == false) return; @@ -967,9 +969,10 @@ public static void AddTableRef(CommonUtils common, TableInfo trytb, PropertyInfo .Append(" if (base.").Append(pnv.Name).Append(" == null && __lazy__").Append(pnv.Name).AppendLine(" == false) {"); if (nvref.Exception == null) - cscode.Append(" base.").Append(pnv.Name).Append(" = __fsql_orm__.Select<").Append(propElementType.DisplayCsharp()) + cscode.Append(" var loc2 = __fsql_orm__.Select<").Append(propElementType.DisplayCsharp()) .Append(">().Where(a => __fsql_orm__.Select<").Append(tbmid.Type.DisplayCsharp()) .Append(">().Where(b => ").Append(lmbdWhere.ToString()).AppendLine(").Any()).ToList();") + .Append(" base.").Append(pnv.Name).Append(" = ").AppendLine(propTypeIsObservableCollection ? $"new ObservableCollection<{propElementType.DisplayCsharp()}>(loc2);" : "loc2;") .Append(" __lazy__").Append(pnv.Name).AppendLine(" = true;"); else cscode.Append(" throw new Exception(\"").Append(nvref.Exception.Message.Replace("\r\n", "\\r\\n").Replace("\"", "\\\"")).AppendLine("\");"); @@ -989,111 +992,235 @@ public static void AddTableRef(CommonUtils common, TableInfo trytb, PropertyInfo } } else - { //One To Many - List bindColumns = new List(); - if (pnvBind != null) + { + var isArrayToMany = false; + var lmbdWhere = isLazy ? new StringBuilder() : null; + var cscodeExtLogic1 = ""; + var cscodeExtLogic2 = ""; + //Pgsql Array[] To Many + if (common._orm.Ado.DataType == DataType.PostgreSQL) { - foreach (var bi in pnvBind) + //class User { + // public int[] RoleIds { get; set; } + // [Navigate(nameof(RoleIds))] + // public List Roles { get; set; } + //} + //class Role { + // [Navigate(nameof(User.RoleIds))] + // public List Users { get; set; } + //} + ColumnInfo trycol = null; + if (tbref.Primarys.Length == 1) { - if (tbref.ColumnsByCs.TryGetValue(bi, out var trybindcol) == false) + if (pnvBind?.Length == 1) + { + if (trytb.ColumnsByCs.TryGetValue(pnvBind[0], out trycol)) + { + if (trycol.CsType.IsArray == false) trycol = null; + else if (trycol != null && tbref.Primarys[0].CsType.NullableTypeOrThis() != trycol.CsType.GetElementType().NullableTypeOrThis()) + { + nvref.Exception = new Exception($"导航属性 {trytbTypeName}.{pnv.Name} 特性 [Navigate] 解析错误,{trytbTypeName}.{trycol.CsName} 数组元素 与 {tbrefTypeName}.{tbref.Primarys[0].CsName} 类型不符"); + trytb.AddOrUpdateTableRef(pnv.Name, nvref); + //if (isLazy) throw nvref.Exception; + } + } + } + if (pnvBind == null && trycol == null) { - nvref.Exception = new Exception(CoreStrings.Navigation_ParsingError_NotFound_Property(trytbTypeName, pnv.Name, tbrefTypeName, bi)); + var findtbrefPkCsName = tbref.Primarys[0].CsName.TrimStart('_'); + if (findtbrefPkCsName.StartsWith(tbref.Type.Name, StringComparison.CurrentCultureIgnoreCase)) findtbrefPkCsName = findtbrefPkCsName.Substring(trytb.Type.Name.Length).TrimStart('_'); + var findtrytb = pnv.Name; + if (findtrytb.EndsWith($"{tbref.CsName}s", StringComparison.CurrentCultureIgnoreCase)) findtrytb = findtrytb.Substring(0, findtrytb.Length - tbref.CsName.Length - 1); + findtrytb += tbref.CsName; + if ( + trytb.ColumnsByCs.TryGetValue($"{findtrytb}{findtbrefPkCsName}s", out trycol) == false && //骆峰命名 + trytb.ColumnsByCs.TryGetValue($"{findtrytb}_{findtbrefPkCsName}s", out trycol) == false //下划线命名 + ) + { + } + if (trycol != null && tbref.Primarys[0].CsType.NullableTypeOrThis() != trycol.CsType.GetElementType().NullableTypeOrThis()) + trycol = null; + } + isArrayToMany = trycol != null; + if (isArrayToMany) + { + if (isLazy) + { + cscodeExtLogic1 = $" if (this.{trycol.CsName} == null) return null; \r\nif (this.{trycol.CsName}.Any() == false) return new {(propTypeIsObservableCollection ? "ObservableCollection" : "List")}<{propElementType.DisplayCsharp()}>();\r\n"; + cscodeExtLogic2 = $" loc2 = this.{trycol.CsName}.Select(a => loc2.FirstOrDefault(b => b.{tbref.Primarys[0].CsName} == a)).ToList();"; + lmbdWhere.Append("this.").Append(trycol.CsName).Append(".Contains(a.").Append(tbref.Primarys[0].CsName); + if (trycol.CsType.GetElementType().IsNullableType() == false && tbref.Primarys[0].CsType.IsNullableType()) lmbdWhere.Append(".Value"); + lmbdWhere.Append(")"); + } + nvref.Columns.Add(trycol); + nvref.RefColumns.Add(tbref.Primarys[0]); + nvref.RefEntityType = tbref.Type; + nvref.RefType = TableRefType.PgArrayToMany; trytb.AddOrUpdateTableRef(pnv.Name, nvref); - //if (isLazy) throw nvref.Exception; - break; } - bindColumns.Add(trybindcol); - } - } - - PropertyInfo refprop = null; - var refcols = tbref.Properties.Where(z => z.Value.PropertyType == trytb.Type); - refprop = refcols.Count() == 1 ? refcols.First().Value : null; - var lmbdWhere = isLazy ? new StringBuilder() : null; - - if (nvref.Exception == null && bindColumns.Any() && bindColumns.Count != trytb.Primarys.Length) - { - nvref.Exception = new Exception(CoreStrings.Navigation_Bind_Number_Different(trytbTypeName, pnv.Name, bindColumns.Count, trytb.Primarys.Length)); - trytb.AddOrUpdateTableRef(pnv.Name, nvref); - //if (isLazy) throw nvref.Exception; - } - if (trytb.Primarys.Length > 1) - { - if (trytb.Primarys.Select(a => a.CsType.NullableTypeOrThis()).Distinct().Count() == trytb.Primarys.Length) - { - var pkList = trytb.Primarys.ToList(); - bindColumns.Sort((a, b) => pkList.FindIndex(c => c.CsType.NullableTypeOrThis() == a.CsType.NullableTypeOrThis()).CompareTo(pkList.FindIndex(c => c.CsType.NullableTypeOrThis() == b.CsType.NullableTypeOrThis()))); } - else if (string.Compare(string.Join(",", trytb.Primarys.Select(a => a.CsName).OrderBy(a => a)), string.Join(",", bindColumns.Select(a => a.CsName).OrderBy(a => a)), true) == 0) + + if (nvref.Exception == null && trytb.Primarys.Length == 1 && isArrayToMany == false) { - var pkList = trytb.Primarys.ToList(); - bindColumns.Sort((a, b) => pkList.FindIndex(c => string.Compare(c.CsName, a.CsName, true) == 0).CompareTo(pkList.FindIndex(c => string.Compare(c.CsName, b.CsName, true) == 0))); + if (pnvBind?.Length == 1) + { + if (tbref.ColumnsByCs.TryGetValue(pnvBind[0], out trycol)) + { + if (trycol.CsType.IsArray == false) trycol = null; + else if (trytb.Primarys[0].CsType.NullableTypeOrThis() != trycol.CsType.GetElementType().NullableTypeOrThis()) + { + nvref.Exception = new Exception($"导航属性 {trytbTypeName}.{pnv.Name} 特性 [Navigate] 解析错误,{trytbTypeName}.{trytb.Primarys[0].CsName} 与 {tbrefTypeName}.{trycol.CsName} 数组元素类型不符"); + trytb.AddOrUpdateTableRef(pnv.Name, nvref); + //if (isLazy) throw nvref.Exception; + } + } + } + if (pnvBind != null && trycol == null) + { + var findtrytbPkCsName = trytb.Primarys[0].CsName.TrimStart('_'); + if (findtrytbPkCsName.StartsWith(trytb.Type.Name, StringComparison.CurrentCultureIgnoreCase)) findtrytbPkCsName = findtrytbPkCsName.Substring(trytb.Type.Name.Length).TrimStart('_'); + var findtrytb = pnv.Name; + if (findtrytb.EndsWith($"{trytb.CsName}s", StringComparison.CurrentCultureIgnoreCase)) findtrytb = findtrytb.Substring(0, findtrytb.Length - trytb.CsName.Length - 1); + findtrytb += trytb.CsName; + if ( + tbref.ColumnsByCs.TryGetValue($"{findtrytb}{findtrytbPkCsName}s", out trycol) == false && //骆峰命名 + tbref.ColumnsByCs.TryGetValue($"{findtrytb}_{findtrytbPkCsName}s", out trycol) == false //下划线命名 + ) + { + } + if (trycol != null && trytb.Primarys[0].CsType.NullableTypeOrThis() != trycol.CsType.GetElementType().NullableTypeOrThis()) + trycol = null; + } + isArrayToMany = trycol != null; + if (isArrayToMany) + { + if (isLazy) + { + lmbdWhere.Append("a.").Append(trycol.CsName).Append(".Contains(this.").Append(trytb.Primarys[0].CsName); + if (trycol.CsType.GetElementType().IsNullableType() == false && trytb.Primarys[0].CsType.IsNullableType()) + { + lmbdWhere.Append(".Value"); + cscodeExtLogic1 = $" if (this.{trytb.Primarys[0].CsName} == null) return null;\r\n"; + } + lmbdWhere.Append(")"); + } + nvref.Columns.Add(trytb.Primarys[0]); + nvref.RefColumns.Add(trycol); + nvref.RefEntityType = tbref.Type; + nvref.RefType = TableRefType.PgArrayToMany; + trytb.AddOrUpdateTableRef(pnv.Name, nvref); + } } + } - for (var a = 0; nvref.Exception == null && a < trytb.Primarys.Length; a++) + + PropertyInfo refprop = null; + if (isArrayToMany == false) { - var findtrytbPkCsName = trytb.Primarys[a].CsName.TrimStart('_'); - if (findtrytbPkCsName.StartsWith(trytb.Type.Name, StringComparison.CurrentCultureIgnoreCase)) findtrytbPkCsName = findtrytbPkCsName.Substring(trytb.Type.Name.Length).TrimStart('_'); - var findtrytb = pnv.Name; - if (findtrytb.EndsWith($"{tbref.CsName}s", StringComparison.CurrentCultureIgnoreCase)) findtrytb = findtrytb.Substring(0, findtrytb.Length - tbref.CsName.Length - 1); - findtrytb += trytb.CsName; - - var trycol = bindColumns.Any() ? bindColumns[a] : null; - if (trycol == null && - tbref.ColumnsByCs.TryGetValue($"{findtrytb}{findtrytbPkCsName}", out trycol) == false && //骆峰命名 - tbref.ColumnsByCs.TryGetValue($"{findtrytb}_{findtrytbPkCsName}", out trycol) == false //下划线命名 - ) + List bindColumns = new List(); + //One To Many + if (pnvBind != null) { - if (refprop != null && - tbref.ColumnsByCs.TryGetValue($"{refprop.Name}{findtrytbPkCsName}", out trycol) == false && //骆峰命名 - tbref.ColumnsByCs.TryGetValue($"{refprop.Name}_{findtrytbPkCsName}", out trycol) == false) //下划线命名 + foreach (var bi in pnvBind) { - + if (tbref.ColumnsByCs.TryGetValue(bi, out var trybindcol) == false) + { + nvref.Exception = new Exception(CoreStrings.Navigation_ParsingError_NotFound_Property(trytbTypeName, pnv.Name, tbrefTypeName, bi)); + trytb.AddOrUpdateTableRef(pnv.Name, nvref); + //if (isLazy) throw nvref.Exception; + break; + } + bindColumns.Add(trybindcol); } } - if (trycol != null && trycol.CsType.NullableTypeOrThis() != trytb.Primarys[a].CsType.NullableTypeOrThis()) + + var refcols = tbref.Properties.Where(z => z.Value.PropertyType == trytb.Type); + refprop = refcols.Count() == 1 ? refcols.First().Value : null; + + if (nvref.Exception == null && bindColumns.Any() && bindColumns.Count != trytb.Primarys.Length) { - nvref.Exception = new Exception(CoreStrings.OneToMany_ParsingError_InconsistentType(trytbTypeName, pnv.Name, trytb.CsName, trytb.Primarys[a].CsName, tbref.CsName, trycol.CsName)); + nvref.Exception = new Exception(CoreStrings.Navigation_Bind_Number_Different(trytbTypeName, pnv.Name, bindColumns.Count, trytb.Primarys.Length)); trytb.AddOrUpdateTableRef(pnv.Name, nvref); //if (isLazy) throw nvref.Exception; - break; } - if (trycol == null) + if (trytb.Primarys.Length > 1) { - nvref.Exception = new Exception(CoreStrings.OneToMany_NotFound_CorrespondingField(trytbTypeName, pnv.Name, tbref.CsName, findtrytb, findtrytbPkCsName) - + (refprop == null ? "" : CoreStrings.OneToMany_UseNavigate(refprop.Name, findtrytbPkCsName))); - trytb.AddOrUpdateTableRef(pnv.Name, nvref); - //if (isLazy) throw nvref.Exception; - break; + if (trytb.Primarys.Select(a => a.CsType.NullableTypeOrThis()).Distinct().Count() == trytb.Primarys.Length) + { + var pkList = trytb.Primarys.ToList(); + bindColumns.Sort((a, b) => pkList.FindIndex(c => c.CsType.NullableTypeOrThis() == a.CsType.NullableTypeOrThis()).CompareTo(pkList.FindIndex(c => c.CsType.NullableTypeOrThis() == b.CsType.NullableTypeOrThis()))); + } + else if (string.Compare(string.Join(",", trytb.Primarys.Select(a => a.CsName).OrderBy(a => a)), string.Join(",", bindColumns.Select(a => a.CsName).OrderBy(a => a)), true) == 0) + { + var pkList = trytb.Primarys.ToList(); + bindColumns.Sort((a, b) => pkList.FindIndex(c => string.Compare(c.CsName, a.CsName, true) == 0).CompareTo(pkList.FindIndex(c => string.Compare(c.CsName, b.CsName, true) == 0))); + } } + for (var a = 0; nvref.Exception == null && a < trytb.Primarys.Length; a++) + { + var findtrytbPkCsName = trytb.Primarys[a].CsName.TrimStart('_'); + if (findtrytbPkCsName.StartsWith(trytb.Type.Name, StringComparison.CurrentCultureIgnoreCase)) findtrytbPkCsName = findtrytbPkCsName.Substring(trytb.Type.Name.Length).TrimStart('_'); + var findtrytb = pnv.Name; + if (findtrytb.EndsWith($"{tbref.CsName}s", StringComparison.CurrentCultureIgnoreCase)) findtrytb = findtrytb.Substring(0, findtrytb.Length - tbref.CsName.Length - 1); + findtrytb += trytb.CsName; + + var trycol = bindColumns.Any() ? bindColumns[a] : null; + if (trycol == null && + tbref.ColumnsByCs.TryGetValue($"{findtrytb}{findtrytbPkCsName}", out trycol) == false && //骆峰命名 + tbref.ColumnsByCs.TryGetValue($"{findtrytb}_{findtrytbPkCsName}", out trycol) == false //下划线命名 + ) + { + if (refprop != null && + tbref.ColumnsByCs.TryGetValue($"{refprop.Name}{findtrytbPkCsName}", out trycol) == false && //骆峰命名 + tbref.ColumnsByCs.TryGetValue($"{refprop.Name}_{findtrytbPkCsName}", out trycol) == false) //下划线命名 + { + + } + } + if (trycol != null && trycol.CsType.NullableTypeOrThis() != trytb.Primarys[a].CsType.NullableTypeOrThis()) + { + nvref.Exception = new Exception(CoreStrings.OneToMany_ParsingError_InconsistentType(trytbTypeName, pnv.Name, trytb.CsName, trytb.Primarys[a].CsName, tbref.CsName, trycol.CsName)); + trytb.AddOrUpdateTableRef(pnv.Name, nvref); + //if (isLazy) throw nvref.Exception; + break; + } + if (trycol == null) + { + nvref.Exception = new Exception(CoreStrings.OneToMany_NotFound_CorrespondingField(trytbTypeName, pnv.Name, tbref.CsName, findtrytb, findtrytbPkCsName) + + (refprop == null ? "" : CoreStrings.OneToMany_UseNavigate(refprop.Name, findtrytbPkCsName))); + trytb.AddOrUpdateTableRef(pnv.Name, nvref); + //if (isLazy) throw nvref.Exception; + break; + } - nvref.Columns.Add(trytb.Primarys[a]); - nvref.RefColumns.Add(trycol); + nvref.Columns.Add(trytb.Primarys[a]); + nvref.RefColumns.Add(trycol); - if (isLazy && nvref.Exception == null) - { - if (a > 0) lmbdWhere.Append(" && "); - lmbdWhere.Append("a.").Append(trycol.CsName).Append(" == this.").Append(trytb.Primarys[a].CsName); + if (isLazy && nvref.Exception == null) + { + if (a > 0) lmbdWhere.Append(" && "); + lmbdWhere.Append("a.").Append(trycol.CsName).Append(" == this.").Append(trytb.Primarys[a].CsName); - if (refprop == null) - { //加载成功后,把列表对应的导航属性值设置为 this,比如 Select().ToOne().Topics 下的 TopicType 属性值全部为 this - var findtrytbName = trycol.CsName; - if (findtrytbName.EndsWith(trytb.Primarys.First().CsName)) - { - findtrytbName = findtrytbName.Remove(findtrytbName.Length - trytb.Primarys.First().CsName.Length).TrimEnd('_'); - if (tbref.Properties.TryGetValue(findtrytbName, out refprop) && refprop.PropertyType != trytb.Type) - refprop = null; + if (refprop == null) + { //加载成功后,把列表对应的导航属性值设置为 this,比如 Select().ToOne().Topics 下的 TopicType 属性值全部为 this + var findtrytbName = trycol.CsName; + if (findtrytbName.EndsWith(trytb.Primarys.First().CsName)) + { + findtrytbName = findtrytbName.Remove(findtrytbName.Length - trytb.Primarys.First().CsName.Length).TrimEnd('_'); + if (tbref.Properties.TryGetValue(findtrytbName, out refprop) && refprop.PropertyType != trytb.Type) + refprop = null; + } } } } - } - if (nvref.Columns.Count > 0 && nvref.RefColumns.Count > 0) - { - nvref.RefEntityType = tbref.Type; - nvref.RefType = TableRefType.OneToMany; - trytb.AddOrUpdateTableRef(pnv.Name, nvref); - } + if (nvref.Columns.Count > 0 && nvref.RefColumns.Count > 0) + { + nvref.RefEntityType = tbref.Type; + nvref.RefType = TableRefType.OneToMany; + trytb.AddOrUpdateTableRef(pnv.Name, nvref); + } + } if (isLazy) { cscode.Append(" private bool __lazy__").Append(pnv.Name).AppendLine(" = false;") @@ -1101,11 +1228,14 @@ public static void AddTableRef(CommonUtils common, TableInfo trytb, PropertyInfo if (vp?.Item2 == true) { //get 重写 cscode.Append(" ").Append(propGetModification).Append(" get {\r\n") + .Append(cscodeExtLogic1) .Append(" if (base.").Append(pnv.Name).Append(" == null && __lazy__").Append(pnv.Name).AppendLine(" == false) {"); if (nvref.Exception == null) { - cscode.Append(" base.").Append(pnv.Name).Append(" = __fsql_orm__.Select<").Append(propElementType.DisplayCsharp()).Append(">().Where(a => ").Append(lmbdWhere.ToString()).AppendLine(").ToList();"); + cscode.Append(" var loc2 = __fsql_orm__.Select<").Append(propElementType.DisplayCsharp()).Append(">().Where(a => ").Append(lmbdWhere.ToString()).AppendLine(").ToList();") + .Append(cscodeExtLogic2) + .Append(" base.").Append(pnv.Name).Append(" = ").AppendLine(propTypeIsObservableCollection ? $"new ObservableCollection<{propElementType.DisplayCsharp()}>(loc2);" : "loc2;"); if (refprop != null) { cscode.Append(" foreach (var loc1 in base.").Append(pnv.Name).AppendLine(")") @@ -1133,7 +1263,8 @@ public static void AddTableRef(CommonUtils common, TableInfo trytb, PropertyInfo } } else - { //一对一、多对一 + { + //一对一、多对一 var tbref = pnv.PropertyType == trytb.Type ? trytb : GetTableByEntity(pnv.PropertyType, common); //可能是父子关系 if (tbref == null) return; if (tbref.Primarys.Any() == false) @@ -1261,7 +1392,8 @@ public static void AddTableRef(CommonUtils common, TableInfo trytb, PropertyInfo .Append(" if (base.").Append(pnv.Name).Append(" == null && __lazy__").Append(pnv.Name).AppendLine(" == false) {"); if (nvref.Exception == null) - cscode.Append(" base.").Append(pnv.Name).Append(" = __fsql_orm__.Select<").Append(propTypeName).Append(">().Where(a => ").Append(lmbdWhere.ToString()).AppendLine(").ToOne();") + cscode.Append(" var loc3 = __fsql_orm__.Select<").Append(propTypeName).Append(">().Where(a => ").Append(lmbdWhere.ToString()).AppendLine(").ToOne();") + .Append(" base.").Append(pnv.Name).AppendLine(" = loc3;") .Append(" __lazy__").Append(pnv.Name).AppendLine(" = true;"); else cscode.Append(" throw new Exception(\"").Append(nvref.Exception.Message.Replace("\r\n", "\\r\\n").Replace("\"", "\\\"")).AppendLine("\");");