From 10723a98f19e3e39ef30f23ccb16eda1229e34bd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 17 Jul 2026 04:52:02 +0000
Subject: [PATCH 1/3] Initial plan
From 7a2f72e99b8b852a141bee15358a1138757588db Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 17 Jul 2026 04:57:59 +0000
Subject: [PATCH 2/3] Fix BuildMap() generating orphaned [Map] attribute when
no display field available
When Map attribute specifies only "Table@Id" (no display field) and the mapped
table has no Master column or "Name" column, the BuildMap() method was writing
a [Map(...)] attribute with no following property declaration, resulting in
invalid C# code.
Fix 1: In the `else` branch (mapTable is null), guard ss[2] access with
`else if (ss.Length > 2)` to avoid IndexOutOfRangeException.
Fix 2: Add `(mapName != null || (ss.Length > 2 && ss[2] == "$"))` to the
condition for generating the string extension property, so [Map(...)] and
the property are only generated when a display field is available.
Closes #88
---
XCode/Code/EntityBuilder.cs | 7 ++-
XUnitTest.XCode/Code/EntityBuilderTests.cs | 66 ++++++++++++++++++++++
2 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/XCode/Code/EntityBuilder.cs b/XCode/Code/EntityBuilder.cs
index 5d71456ed..1485b884e 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,9 @@ protected virtual void BuildMap()
if (mapName != null && mapName.Name != "$") myName += mapName.Name;
}
- // 扩展属性有可能恰巧跟已有字段同名
- if (!myName.IsNullOrEmpty() && !Table.Columns.Any(e => e.Name.EqualIgnoreCase(myName)))
+ // 扩展属性有可能恰巧跟已有字段同名,且需要有显示字段才能生成扩展属性
+ if (!myName.IsNullOrEmpty() && !Table.Columns.Any(e => e.Name.EqualIgnoreCase(myName))
+ && (mapName != null || (ss.Length > 2 && ss[2] == "$")))
{
var type = Option.Nullable ? "String?" : "String";
diff --git a/XUnitTest.XCode/Code/EntityBuilderTests.cs b/XUnitTest.XCode/Code/EntityBuilderTests.cs
index 854821751..bf0b187af 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对象)
+ Assert.Contains("ProductRelease", code);
+ }
+
[Fact(Skip = "跳过")]
public void Merge()
{
From a5b8a07b65a6c1ea71ac599d61d26aa4e99d5a75 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 17 Jul 2026 05:02:11 +0000
Subject: [PATCH 3/3] Refactor: extract useToString variable and improve test
assertions
- Extract `ss.Length > 2 && ss[2] == "$"` into `useToString` variable to eliminate duplication
- Improve test assertion to verify the exact Extends.Get property declaration is generated
---
XCode/Code/EntityBuilder.cs | 7 +++++--
XUnitTest.XCode/Code/EntityBuilderTests.cs | 4 ++--
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/XCode/Code/EntityBuilder.cs b/XCode/Code/EntityBuilder.cs
index 1485b884e..f1910cc50 100644
--- a/XCode/Code/EntityBuilder.cs
+++ b/XCode/Code/EntityBuilder.cs
@@ -1205,9 +1205,12 @@ protected virtual void BuildMap()
if (mapName != null && mapName.Name != "$") myName += mapName.Name;
}
+ // $表示用ToString()替代显示字段
+ var useToString = ss.Length > 2 && ss[2] == "$";
+
// 扩展属性有可能恰巧跟已有字段同名,且需要有显示字段才能生成扩展属性
if (!myName.IsNullOrEmpty() && !Table.Columns.Any(e => e.Name.EqualIgnoreCase(myName))
- && (mapName != null || (ss.Length > 2 && ss[2] == "$")))
+ && (mapName != null || useToString))
{
var type = Option.Nullable ? "String?" : "String";
@@ -1216,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 bf0b187af..0dab41b40 100644
--- a/XUnitTest.XCode/Code/EntityBuilderTests.cs
+++ b/XUnitTest.XCode/Code/EntityBuilderTests.cs
@@ -462,8 +462,8 @@ public void BuildMap_NoDisplayField_NoOrphanedMapAttribute()
// 验证不存在孤立的[Map(...)]特性(即[Map后面紧跟空行或#endregion)
Assert.DoesNotContain("[Map(nameof(ReleaseId)", code);
- // 验证仍然生成了对象引用属性(ProductRelease对象)
- Assert.Contains("ProductRelease", code);
+ // 验证仍然生成了对象引用属性(ProductRelease对象),且使用 Extends.Get 模式
+ Assert.Contains("public ProductRelease? Release => Extends.Get(nameof(Release), k => ProductRelease.FindById(ReleaseId));", code);
}
[Fact(Skip = "跳过")]