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()
{