类型T

时间:2019-07-29 15:12:43

标签: c# mongodb asp.net-core

问题

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

背景

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

由于我已经设法完成了上面针对数据库和集合实例化所述的操作,因此我要尝试做的一件事就是还要生成索引。我的想法是在类上有一个静态方法,该方法将返回其对索引模型的特定定义。这样,每个类将负责自己的Mongo索引,而不是根据传入的T类型在我的Provider中使用一些复杂的switch-case语句。

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

示例代码

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);
}

1 个答案:

答案 0 :(得分:0)

由于我似乎无法获得答案,因此我认为我将为以后对该问题的搜索提供解决方案。基本上,我在基础对象类中添加了一个扩展方法,并使用反射来确定我所寻找的方法是否在那里。从那里,根据是否找到该方法,我返回了一个truefalse的值,并以传统的TryGet模式将返回值输出到一个参数。

未来读者注意事项

我不推荐这种方法。这就是我解决访问类型为T的方法的问题的方式。理想情况下,将实现一个实例方法,并在一个公用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
}