diff --git a/XCode/Code/EntityBuilder.cs b/XCode/Code/EntityBuilder.cs index 5d71456ed..f1910cc50 100644 --- a/XCode/Code/EntityBuilder.cs +++ b/XCode/Code/EntityBuilder.cs @@ -1176,7 +1176,7 @@ protected virtual void BuildMap() mapName ??= mapTable.Columns.FirstOrDefault(e => e.Master); mapName ??= mapTable.GetColumn("Name"); } - else + else if (ss.Length > 2) { // 默认字符串类型 mapName = new XField { Name = ss[2], DataType = typeof(String) }; @@ -1205,8 +1205,12 @@ protected virtual void BuildMap() if (mapName != null && mapName.Name != "$") myName += mapName.Name; } - // 扩展属性有可能恰巧跟已有字段同名 - if (!myName.IsNullOrEmpty() && !Table.Columns.Any(e => e.Name.EqualIgnoreCase(myName))) + // $表示用ToString()替代显示字段 + var useToString = ss.Length > 2 && ss[2] == "$"; + + // 扩展属性有可能恰巧跟已有字段同名,且需要有显示字段才能生成扩展属性 + if (!myName.IsNullOrEmpty() && !Table.Columns.Any(e => e.Name.EqualIgnoreCase(myName)) + && (mapName != null || useToString)) { var type = Option.Nullable ? "String?" : "String"; @@ -1215,7 +1219,7 @@ protected virtual void BuildMap() WriteLine("[Map(nameof({0}), typeof({1}), \"{2}\")]", column.Name, fullName, mapIdName); if (column.Properties.TryGetValue("Category", out var att) && !att.IsNullOrEmpty()) WriteLine("[Category(\"{0}\")]", att); - if (ss.Length > 2 && ss[2] == "$") + if (useToString) WriteLine("public {2} {0} => {1}?.ToString();", myName, name, type); else if (mapName != null) { diff --git a/XUnitTest.XCode/Code/EntityBuilderTests.cs b/XUnitTest.XCode/Code/EntityBuilderTests.cs index 854821751..0dab41b40 100644 --- a/XUnitTest.XCode/Code/EntityBuilderTests.cs +++ b/XUnitTest.XCode/Code/EntityBuilderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using NewLife.Log; @@ -400,6 +401,71 @@ public void FixModelFile() Assert.Contains("Name", xml); } + /// + /// 验证:Map特性只有表@主键(无显示字段),且被映射表无Master/Name字段时, + /// 不应生成孤立的[Map(...)]特性(缺少属性声明) + /// + [Fact] + public void BuildMap_NoDisplayField_NoOrphanedMapAttribute() + { + // 构造 ProductRelease 表(无 Master 字段,无 Name 字段) + var releaseTable = new XTable("ProductRelease") { Description = "产品版本" }; + var releaseId = (XField)releaseTable.CreateColumn(); + releaseId.Name = "Id"; + releaseId.DataType = typeof(Int32); + releaseId.Identity = true; + releaseId.PrimaryKey = true; + releaseTable.Columns.Add(releaseId); + + var releaseVersion = (XField)releaseTable.CreateColumn(); + releaseVersion.Name = "Version"; + releaseVersion.DataType = typeof(String); + releaseTable.Columns.Add(releaseVersion); + + // 构造 ProductPackage 表,ReleaseId 的 Map 只有 "ProductRelease@Id"(无显示字段) + var packageTable = new XTable("ProductPackage") { Description = "产品发布包" }; + var pkgId = (XField)packageTable.CreateColumn(); + pkgId.Name = "Id"; + pkgId.DataType = typeof(Int32); + pkgId.Identity = true; + pkgId.PrimaryKey = true; + packageTable.Columns.Add(pkgId); + + var releaseIdCol = (XField)packageTable.CreateColumn(); + releaseIdCol.Name = "ReleaseId"; + releaseIdCol.DataType = typeof(Int32); + releaseIdCol.Map = "ProductRelease@Id"; + releaseIdCol.Description = "发布版本。所属发布版本"; + packageTable.Columns.Add(releaseIdCol); + + var allTables = new List { releaseTable, packageTable }; + + var option = new EntityBuilderOption + { + ConnName = "Test", + Namespace = "Test", + Nullable = true, + }; + + var builder = new EntityBuilder + { + Table = packageTable, + AllTables = allTables, + Option = option, + }; + + builder.Execute(); + var code = builder.ToString(); + + Assert.NotEmpty(code); + + // 验证不存在孤立的[Map(...)]特性(即[Map后面紧跟空行或#endregion) + Assert.DoesNotContain("[Map(nameof(ReleaseId)", code); + + // 验证仍然生成了对象引用属性(ProductRelease对象),且使用 Extends.Get 模式 + Assert.Contains("public ProductRelease? Release => Extends.Get(nameof(Release), k => ProductRelease.FindById(ReleaseId));", code); + } + [Fact(Skip = "跳过")] public void Merge() {