首页 > 解决方案 > 类型 T 的方法推断

问题描述

问题

如何定义传入的 T 型约束,允许我调用(T 型)类上的静态方法以获取预期的 IndexModel 对象以传递给 Mongo?

背景

我目前正在尝试编写一个 Mongo Provider 类,它允许我在对它们进行任何操作之前确保我的特定数据库和集合存在,因为它所在的容器或服务器有可能被破坏并重新创建任何时候,我都希望在代码中采用一种安全的方式来确保存在外部依赖项(实例超出了我的控制范围,因此我必须相信存在某些东西)。

我正在尝试做的一件事是生成索引,因为我已经成功地完成了上述数据库和集合实例化的操作。我的想法是在类上有一个静态方法,该方法将返回它们对索引模型的特定定义。这样,每个类将负责他们自己的 Mongo 索引,而不是我的 Provider 中基于传入类型 T 的一些复杂的 switch-case 语句。

我的第一个想法是拥有一个共享此方法的接口,但接口不允许您声明静态方法。同样,我尝试了一个抽象基类,发现静态实现将调用定义该方法的基类,而不是继承器中的任何覆盖。

示例代码

public class MyClass
{
    public DateTime DateValue { get; set; }
    public int GroupId { get; set; }
    public string DataType { get; set; }

    public static IEnumerable<CreateIndexModel<MyClass>> GetIndexModel(IndexKeysDefinitionBuilder<MyClass> builder)
    {
        yield return new CreateIndexModel<MyClass>(
            builder.Combine(
                builder.Descending(entry => entry.DateValue), 
                builder.Ascending(entry => entry.GroupId), 
                builder.Ascending(entry => entry.DataType)
                )
            );
    }
}

编辑

我想我可能应该包含我的 Mongo Provider 类的外壳。见下文:

编辑 #2由于有关这如何无法解决我的问题的问题,我正在更新 MongoProvider 以获取有问题的代码。注意:一旦包含此方法,该类将不再编译,因为鉴于我迄今为止所做的一切,这是不可能的。

public class MongoProvider
{
    private readonly IMongoClient _client;

    private MongoPrivder(ILookup<string, string> lookup, IMongoClient client)
    {
        _client = client;

        foreach(var database in lookup)
            foreach(var collection in database)
                Initialize(database.Key, collection);
    }

    public MongoProvider(IConfiguration config) :this(config.GetMongoObjects(), config.GetMongoClient())
    {}

    public MongoProvider(IConfiguration config, IMongoClient client) : this(config.GetMongoObjects(), client)
    {}

    private void Initialize(string database, string collection)
    {
        var db = _client.GetDatabase(database);
        if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
            db.CreateCollection(collection);
    }
// The Problem
    private void InitializeIndex<T>(string database, string collection)
    {
        IEnumerable<CreateIndexModel<T>> models;

        switch (T)
        {
            case MyClass:
                model = MyClass.GetIndexModel();
                break;
            default:
                break;
        }

        await _client.GetDatabase(database)
            .GetCollection<T>(collection)
            .Indexes
            .CreateManyAsync(models);
    }
}

编辑#3

作为权宜之计,我已经做了一些可怕的事情(不确定它是否会起作用),我将提供示例,以便您了解我迄今为止的最佳解决方案。

public static class Extensions
{
    #region Object Methods

    public static T TryCallMethod<T>(this object obj, string methodName, params object[] args) where T : class
    {
        var method = obj.GetType().GetMethod(methodName);

        if (method != null)
        {
            return method.Invoke(obj, args) as T;
        }

        return default;
    }

    #endregion
}

这允许我执行以下操作(在 MongoProvider 内部)

private async void InitializeIndex<T>(string database, string collection) where T : new()
{
    var models = new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel");

    await _client.GetDatabase(database)
        .GetCollection<T>(collection)
        .Indexes
        .CreateManyAsync(models);
}

标签: c#mongodbasp.net-core

解决方案


因为看起来我不会得到这个答案,所以我想我会为这个问题的未来搜索提供我的解决方案。基本上,我向基对象类添加了一个扩展方法,并使用反射来确定我正在寻找的方法是否存在。从那里,我返回一个trueor的值false,这取决于是否找到了该方法,并以传统的 TryGet 模式将返回值输出到一个参数。

给未来读者的注意事项

我不推荐这种方法。这就是我解决访问 T 类型方法的问题的方法。理想情况下,将实现一个实例方法,并在 common 中定义一个签名Interface,但这不适用于我的用例。

我的答案

public static class Extensions
{
    #region Object Methods

    public static bool TryCallMethod<T>(this object obj, string methodName, out T result, params object[] args) where T : class
    {
        result = null;
        var method = obj.GetType().GetMethod(methodName);

        if (method == null)            
            return false;

        result = method.Invoke(obj, args) as T;
        return true;
    }

    #endregion
}

我的数据类看起来像这样(从实际使用中混淆)

[BsonDiscriminator("data")]
public class DataClass
{
    #region Private Fields

    private const string MongoCollectionName = "Data";

    #endregion

    #region Public Properties

    public string CollectionName => MongoCollectionName;
    [BsonId]
    public ObjectId Id { get; set; }
    [BsonElement("date_value")]
    public DateTime DateValue { get; set; }
    [BsonElement("group_id")]
    public int GroupId { get; set; }
    [BsonElement("data_type")]
    public string DataType { get; set; }
    [BsonElement("summary_count")]
    public long SummaryCount { get; set; }
    [BsonElement("flagged_count")]
    public long FlaggedCount { get; set;  }
    [BsonElement("error_count")]
    public long ErrorCount { get; set;  }

    #endregion

    #region Constructor

    public DataClass()
    {

    }

    public DataClass(int groupId, string dataType = null, long summaryCount = 0, long flaggedCount = 0, long errorCount = 0)
    {
        Id = ObjectId.GenerateNewId();
        DateValue = DateTime.UtcNow;
        GroupId = groupId;
        DocCount = summaryCount;
        DataType = dataType ?? "default_name";
        FlaggedCount = flaggedCount;
        ErrorCount = errorCount;
    }

    #endregion

    #region Public Methods

    public static IEnumerable<CreateIndexModel<AuditEntry>> GetIndexModel(IndexKeysDefinitionBuilder<AuditEntry> builder)
    {
        yield return new CreateIndexModel<AuditEntry>(
            builder.Combine(
                builder.Descending(entry => entry.DateValue), 
                builder.Ascending(entry => entry.GroupId), 
                builder.Ascending(entry => entry.DataType)
                )
            );
    }

    #endregion
}

然后,我将在 MongoProvider 类中以下列方式调用该方法。出现省略号是为了标识类中存在更多代码。

public class MongoProvider : IMongoProvider
{
    #region Private Fields

    private readonly IMongoClient _client;

    #endregion


    #region Constructor

    ...

    #endregion

    #region Private Methods

    private void Initialize(string database, string collection)
    {
        var db = _client.GetDatabase(database);
        if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
            db.CreateCollection(collection);
    }

    private async Task InitializeIndex<T>(string database, string collection) where T : new()
    {
        if(new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel", out var models, new IndexKeysDefinitionBuilder<T>()))
            await _client.GetDatabase(database)
                .GetCollection<T>(collection)
                .Indexes
                .CreateManyAsync(models);
    }

    private static void ValidateOptions<T>(ref FindOptions<T, T> options)
    {
        if(options != null)
            return;

        options = new FindOptions<T, T>
        {
            AllowPartialResults = null,
            BatchSize = null,
            Collation = null,
            Comment = "AspNetWebService",
            CursorType = CursorType.NonTailable,
            MaxAwaitTime = TimeSpan.FromSeconds(10),
            MaxTime = TimeSpan.FromSeconds(10),
            Modifiers = null,
            NoCursorTimeout = false,
            OplogReplay = null
        };
    }

    private static FilterDefinition<T> GetFilterDefinition<T>(Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders)
    {
        if(builders.Length == 0)
            builders = new Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] {b => b.Empty};

        return new FilterDefinitionBuilder<T>()
            .And(builders
                .Select(b => b(new FilterDefinitionBuilder<T>()))
            );
    }

    #endregion

    #region Public Methods

    public async Task<IReadOnlyCollection<T>> SelectManyAsync<T>(string database, string collection, FindOptions<T, T> options = null, params Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders) where T : new()
    {
        ValidateOptions(ref options);
        await InitializeIndex<T>(database, collection);
        var filter = GetFilterDefinition(builders);

        var find = await _client.GetDatabase(database)
            .GetCollection<T>(collection)
            .FindAsync(filter, options);

        return await find.ToListAsync();
    }

    ...

    #endregion
}

推荐阅读