|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Linq; |
| 4 | +using NHibernate.Cfg.MappingSchema; |
| 5 | +using NHibernate.Mapping.ByCode; |
| 6 | +using NUnit.Framework; |
| 7 | + |
| 8 | +namespace NHibernate.Test.NHSpecificTest.GH3306 |
| 9 | +{ |
| 10 | + /// <summary> |
| 11 | + /// Fixture using 'by code' mappings |
| 12 | + /// </summary> |
| 13 | + /// <remarks> |
| 14 | + /// This fixture is identical to <see cref="Fixture" /> except the <see cref="Entity" /> mapping is performed |
| 15 | + /// by code in the GetMappings method, and does not require the <c>Mappings.hbm.xml</c> file. Use this approach |
| 16 | + /// if you prefer. |
| 17 | + /// </remarks> |
| 18 | + [TestFixture] |
| 19 | + public class ByCodeFixture : TestCaseMappingByCode |
| 20 | + { |
| 21 | + protected override HbmMapping GetMappings() |
| 22 | + { |
| 23 | + // Mapping copied from https://github.com/craigfowler/NHibernateInvalidSql then adjusted. |
| 24 | + var mapper = new ModelMapper(); |
| 25 | + mapper.Class<UEntity>(rc => |
| 26 | + { |
| 27 | + rc.ComponentAsId(x => x.Id, m => |
| 28 | + { |
| 29 | + m.ManyToOne(x => x.PEntity, p => p.Column("p_id")); |
| 30 | + m.Property(x => x.Id, p => p.Column("id")); |
| 31 | + }); |
| 32 | + |
| 33 | + rc.Set(x => x.LEntities, |
| 34 | + m => |
| 35 | + { |
| 36 | + m.Inverse(true); |
| 37 | + m.Key(k => |
| 38 | + { |
| 39 | + k.Columns(c => c.Name("p_id"), c => c.Name("u_id")); |
| 40 | + k.NotNullable(true); |
| 41 | + }); |
| 42 | + }, |
| 43 | + a => a.OneToMany()); |
| 44 | + }); |
| 45 | + |
| 46 | + mapper.Class<PEntity>(rc => |
| 47 | + { |
| 48 | + rc.Id(x => x.Id, p => p.Column("id")); |
| 49 | + rc.Property(x => x.PStatus, p => p.Column("p_status")); |
| 50 | + }); |
| 51 | + |
| 52 | + mapper.Class<DEntity>(rc => |
| 53 | + { |
| 54 | + rc.Id(x => x.Id, p => p.Column("id")); |
| 55 | + rc.Property(x => x.LinkedEntityId, p => p.Column("linked_entity_id")); |
| 56 | + rc.Property(x => x.LinkedEntityType, p => p.Column("linked_entity_type")); |
| 57 | + |
| 58 | + rc.ManyToOne(x => x.LinkedTEntity, |
| 59 | + m => |
| 60 | + { |
| 61 | + m.Cascade(Mapping.ByCode.Cascade.None); |
| 62 | + m.Update(false); |
| 63 | + m.Insert(false); |
| 64 | + m.NotFound(NotFoundMode.Ignore); |
| 65 | + m.NotNullable(false); |
| 66 | + m.Fetch(FetchKind.Join); |
| 67 | + m.Formula("CASE WHEN linked_entity_type = 'T' THEN linked_entity_id ELSE NULL END"); |
| 68 | + }); |
| 69 | + }); |
| 70 | + |
| 71 | + mapper.Class<LEntity>(rc => |
| 72 | + { |
| 73 | + rc.Id(x => x.Id, p => p.Column("id")); |
| 74 | + rc.Property(x => x.TStatus, p => p.Column("t_status")); |
| 75 | + rc.ManyToOne(x => x.TEntity, m => m.Column("t_id")); |
| 76 | + rc.ManyToOne(x => x.UEntity, m => m.Columns(c => c.Name("p_id"), c => c.Name("u_id"))); |
| 77 | + }); |
| 78 | + |
| 79 | + mapper.Class<TEntity>(rc => |
| 80 | + { |
| 81 | + rc.Id(x => x.Id, p => p.Column("id")); |
| 82 | + }); |
| 83 | + |
| 84 | + return mapper.CompileMappingForAllExplicitlyAddedEntities(); |
| 85 | + } |
| 86 | + |
| 87 | + protected override void OnSetUp() |
| 88 | + { |
| 89 | + using (var session = OpenSession()) |
| 90 | + using (var transaction = session.BeginTransaction()) |
| 91 | + { |
| 92 | + /*INSERT INTO t_entity(id) VALUES(1), (2), (3); |
| 93 | + INSERT INTO p_entity(id, p_status) VALUES(1, 'Good PStatus'), (2, 'Bad PStatus'); |
| 94 | + INSERT INTO u_entity(p_id, id) VALUES(1, 1), (1, 2), (1, 3), (2, 1); |
| 95 | + INSERT INTO l_entity(id, t_status, t_id, p_id, u_id) VALUES |
| 96 | + (1, 'Good TStatus', 1, 1, 1), |
| 97 | + (2, 'Bad TStatus', 1, 3, 2), |
| 98 | + (3, 'Good TStatus', 1, 2, 2), |
| 99 | + (4, 'Good TStatus', 2, 2, 3); |
| 100 | + INSERT INTO d_entity(id, linked_entity_id, linked_entity_type) VALUES |
| 101 | + (1, 1, 'T'), |
| 102 | + (2, 2, 'T'), |
| 103 | + (3, 3, 'T'), |
| 104 | + (4, 0, 'Z');*/ |
| 105 | + // Adapted from the SQL provided by https://github.com/craigfowler/NHibernateInvalidSql. |
| 106 | + var t1 = new TEntity { Id = 1 }; |
| 107 | + var t2 = new TEntity { Id = 2 }; |
| 108 | + var t3 = new TEntity { Id = 3 }; |
| 109 | + session.Save(t1); |
| 110 | + session.Save(t2); |
| 111 | + session.Save(t3); |
| 112 | + |
| 113 | + var goodP = new PEntity { Id = 1, PStatus = "Good PStatus" }; |
| 114 | + var badP = new PEntity { Id = 2, PStatus = "Bad PStatus" }; |
| 115 | + session.Save(goodP); |
| 116 | + session.Save(badP); |
| 117 | + |
| 118 | + var u1 = new UEntity { Id = new UKey { PEntity = goodP, Id = 1 } }; |
| 119 | + var u2 = new UEntity { Id = new UKey { PEntity = goodP, Id = 2 } }; |
| 120 | + var u3 = new UEntity { Id = new UKey { PEntity = goodP, Id = 3 } }; |
| 121 | + var u4 = new UEntity { Id = new UKey { PEntity = badP, Id = 1 } }; |
| 122 | + session.Save(u1); |
| 123 | + session.Save(u2); |
| 124 | + session.Save(u3); |
| 125 | + session.Save(u4); |
| 126 | + |
| 127 | + session.Save(new LEntity { Id = 1, TStatus = "Good TStatus", TEntity = t1, UEntity = u1 }); |
| 128 | + session.Save(new LEntity { Id = 2, TStatus = "Bad TStatus", TEntity = t1, UEntity = u3 }); |
| 129 | + session.Save(new LEntity { Id = 3, TStatus = "Good TStatus", TEntity = t1, UEntity = u4 }); |
| 130 | + session.Save(new LEntity { Id = 4, TStatus = "Good TStatus", TEntity = t1, UEntity = u3 }); |
| 131 | + |
| 132 | + session.Save(new DEntity { Id = 1, LinkedEntityType = "T", LinkedEntityId = 1 }); |
| 133 | + session.Save(new DEntity { Id = 2, LinkedEntityType = "T", LinkedEntityId = 2 }); |
| 134 | + session.Save(new DEntity { Id = 3, LinkedEntityType = "T", LinkedEntityId = 3 }); |
| 135 | + session.Save(new DEntity { Id = 4, LinkedEntityType = "Z", LinkedEntityId = 0 }); |
| 136 | + |
| 137 | + transaction.Commit(); |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + protected override void OnTearDown() |
| 142 | + { |
| 143 | + using (var session = OpenSession()) |
| 144 | + using (var transaction = session.BeginTransaction()) |
| 145 | + { |
| 146 | + // The HQL delete does all the job inside the database without loading the entities, but it does |
| 147 | + // not handle delete order for avoiding violating constraints if any. Use |
| 148 | + // session.Delete("from System.Object"); |
| 149 | + // instead if in need of having NHbernate ordering the deletes, but this will cause |
| 150 | + // loading the entities in the session. |
| 151 | + session.CreateQuery("delete from DEntity").ExecuteUpdate(); |
| 152 | + session.CreateQuery("delete from LEntity").ExecuteUpdate(); |
| 153 | + session.CreateQuery("delete from UEntity").ExecuteUpdate(); |
| 154 | + session.CreateQuery("delete from System.Object").ExecuteUpdate(); |
| 155 | + |
| 156 | + transaction.Commit(); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + [Test] |
| 161 | + public void InvalidSql() |
| 162 | + { |
| 163 | + using (var session = OpenSession()) |
| 164 | + using (var transaction = session.BeginTransaction()) |
| 165 | + { |
| 166 | + // Test copied from https://github.com/craigfowler/NHibernateInvalidSql then adjusted. |
| 167 | + var pEntities = session.Query<PEntity>().Where(p => p.PStatus == "Good PStatus"); |
| 168 | + var dEntities = session.Query<DEntity>().Where(d => d.Id == 1); |
| 169 | + |
| 170 | + // The reason for the three separate .Where clauses is that in our own impl, these three expressions come from three |
| 171 | + // separate & independent specification classes. Each represents an independent business rule. |
| 172 | + var query = session |
| 173 | + .Query<UEntity>() |
| 174 | + .Where(u => pEntities.Any(p => u.Id.PEntity == p)) |
| 175 | + .Where(u => dEntities.Any(d => u.LEntities.Any(l => l.TEntity == d.LinkedTEntity))) |
| 176 | + // The part of this query which states !u.LEntities.Any() is not strictly necessary, but works |
| 177 | + // around some other suboptimal SQL in other scenarios. |
| 178 | + .Where(u => !u.LEntities.Any() || u.LEntities.All(l => l.TStatus != "Bad TStatus")); |
| 179 | + |
| 180 | + List<UEntity> uEntities = null; |
| 181 | + Assert.That( |
| 182 | + () => |
| 183 | + { |
| 184 | + uEntities = query.ToList(); |
| 185 | + }, Throws.Nothing); |
| 186 | + Assert.That(uEntities, Has.Count.EqualTo(1)); |
| 187 | + Assert.That(uEntities[0].Id, Has.Property(nameof(UKey.Id)).EqualTo(1).And.Property(nameof(UKey.PEntity)).Property(nameof(PEntity.Id)).EqualTo(1)); |
| 188 | + |
| 189 | + transaction.Commit(); |
| 190 | + } |
| 191 | + } |
| 192 | + } |
| 193 | +} |
0 commit comments