You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Marten中自定义值类型ReferenceNumber的查询问题及IMemberSource实现报错求助

Marten中自定义值类型ReferenceNumber的查询问题及IMemberSource实现报错求助

我最近在使用Marten做文档数据库查询时,碰到了自定义值类型的查询支持问题,折腾了好一阵还是没搞定,来求助大家。

先说说我的场景:
我定义了一个只读记录结构体ReferenceNumber用来封装字符串,作为Root类的一个属性:

public class Root 
{ 
    public ReferenceNumber Ref { get; set; } 
} 

public readonly record struct ReferenceNumber 
{ 
    private readonly string _referenceNumber; 

    public ReferenceNumber(in string referenceNumber) 
    { 
        _referenceNumber = referenceNumber.Trim(); 
    } 

    public static implicit operator ReferenceNumber(in string value) => new(value); 
    public static implicit operator string(in ReferenceNumber reference) => reference._referenceNumber; 
    public override string ToString() => _referenceNumber; 
}

然后我尝试用LINQ查询Root实体,匹配ReferenceNumber:

await using IDocumentSession session = _readDocumentStore.LightweightSession(); 
return await session.Query<Root>() 
    .FirstOrDefaultAsync(x => x.Ref == referenceNumber);

不出意外报错了,Marten提示不支持LINQ表达式里的自定义值类型:

Marten cannot support custom value types in LINQ expression. Please query on either simple properties of the value type, or register a custom IMemberSource for this value type.

按照提示,我需要要么查值类型的简单属性,要么实现自定义的IMemberSource。但我找了一圈,Google上关于IMemberSource的例子只有3条结果,官方文档也没详细说明,只说去Discord讨论。无奈之下我借助Copilot生成了一个实现:

public class QueryableMember : IQueryableMember 
{ 
    public string JsonPathSegment { get; set; } 
    public string MemberName { get; set; } 
    public Type MemberType { get; set; } 
    public Type DeclaringType { get; set; } 
    public string Name { get; set; } 
    public bool IsAsync { get; set; } 
    public MemberInfo MemberInfo { get; set; } 
    public IQueryableMember Parent { get; set; } 

    public string TypedLocator => $"CAST({RawLocator} as text)"; 
    public string RawLocator => $"d.data -> {SqlFragment()}"; 
    public string JSONBLocator => $"d.data ->> {SqlFragment()}"; 

    public IQueryableMember[] Ancestors => Parent == null ? [] : [.. Parent.Ancestors, Parent]; 
    public string LocatorForIncludedDocumentId => $"d.data ->> {SqlFragment()}"; 
    public string NullTestLocator => $"d.data -> {SqlFragment()} IS NULL"; 

    private string SqlFragment() 
    { 
        return $"'{JsonPathSegment}'"; 
    } 

    public void Apply(ICommandBuilder builder) 
    { 
        builder.Append(JSONBLocator); 
    } 

    public string BuildOrderingExpression(Ordering ordering, CasingRule casingRule) 
    { 
        string direction = ordering.Direction == OrderingDirection.Asc ? "asc" : "desc"; 
        return $"{JSONBLocator} {direction}"; 
    } 

    public Dictionary<string, object> FindOrPlaceChildDictionaryForContainment(Dictionary<string, object> dict) 
    { 
        if (!dict.TryGetValue(JsonPathSegment, out object? value)) 
        { 
            value = new Dictionary<string, object>(); 
            dict[JsonPathSegment] = value; 
        } 
        return value as Dictionary<string, object>; 
    } 

    public void PlaceValueInDictionaryForContainment(Dictionary<string, object> dict, ConstantExpression constant) 
    { 
        // For ReferenceNumber, convert to string using ToString() 
        string value = constant.Value?.ToString() ?? string.Empty; 
        dict[JsonPathSegment] = value; 
    } 

    public string SelectorForDuplication(string pgType) 
    { 
        return $"{RawLocator} {pgType}"; 
    } 
} 

public class ReferenceNumberMemberSource : IMemberSource 
{ 
    public bool TryResolve( 
        IQueryableMember parent, StoreOptions options, MemberInfo memberInfo, Type memberType, 
        [NotNullWhen(true)] out IQueryableMember? member) 
    { 
        member = new QueryableMember 
        { 
            Parent = parent, 
            DeclaringType = memberInfo.DeclaringType!, 
            MemberType = memberType, 
            Name = memberInfo.Name, 
            MemberName = memberInfo.Name, 
            MemberInfo = memberInfo, 
            IsAsync = false, 
            JsonPathSegment = $"$.{memberInfo.Name}", 
        }; 
        return true; 
    } 
}

然后我把这个ReferenceNumberMemberSource注册到Marten的配置里,结果启动应用又报了新的错误:

Id of type System.Guid cannot be used as a Duplicated Field by Marten (Parameter 'members')

我完全搞不懂这个错误是怎么来的,明明我只处理ReferenceNumber类型啊。

其实我的需求特别简单:ReferenceNumber本质就是封装了一个字符串,我已经通过JsonConverter把它在数据库里存成了普通字符串(不是嵌套对象),我只是希望在LINQ查询里能把x.Ref == referenceNumber当成普通的字符串比较来处理,就像直接用string类型的属性一样。

有没有大佬能指点一下:

  • 有没有比实现IMemberSource更简单的方案来满足我的需求?
  • 如果必须用IMemberSource,我当前的实现哪里有问题,怎么解决那个Guid相关的报错?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 11:14:41