使用C#动态更改类型

时间:2018-02-16 08:51:21

标签: c# servicestack

我是C#和ServiceStack的新手,我正在开发一个小项目,该项目包括调用第三方API并通过ServiceStack的ORMLite将我从API返回的数据加载到关系数据库中。 / p>

我们的想法是让API的每个端点都有一个可重用的模型,该模型确定如何在API的响应中接收它,以及如何将其插入到数据库中。

所以我有以下内容:

    [Route("/api/{ApiEndpoint}", "POST")]
    public class ApiRequest : IReturn<ApiResponse>
    {
        public Int32 OrderId { get; set; }
        public DateTime PurchaseDate { get; set; }
        public String ApiEndpoint { get; set; }
    }

    public class ApiResponse
    {
        public Endpoint1[] Data { get; set; }
        public String ErrorCode { get; set; }
        public Int32 ErrorNumber { get; set; }
        public String ErrorDesc { get; set; }
    }

    public class Endpoint1
    {
        [AutoIncrement] 
        public Int32 Id { get; set; }

        [CustomField("DATETIME2(7)")]
        public String PurchaseDate { get; set; }

        [CustomField("NVARCHAR(50)")]
        public String Customer { get; set; }

        [CustomField("NVARCHAR(20)")]
        public String PhoneNumber { get; set; }

        public Int32 Amount { get; set; }
    }

我的第一个类用其路由表示API的请求,第二个类表示API的响应。 API的响应对于所有端点都是相同的,但唯一不同的是从该端点返回的Data字段的结构。我已经在我的Endpoint1类中定义了我的一个端点的结构,我在API的响应类中使用它。如您所见,我还在Endpoint1类中定义了一些属性,以帮助ORM在插入数据后做出更好的决策。

好的,问题是我有大约15个端点,当我知道唯一改变的是第一个ApiResponse字段时,我不想创建15个Data类上课。

所以我做了这样的事情:

public class DataModels
    {
        public Type getModel(String endpoint)
        {
            Dictionary<String, Type> models = new Dictionary<String, Type>();

            models.Add("Endpoint1", typeof(Endpoint1));
            // models.Add("Endpoint2", typeof(Endpoint2));
            // models.Add("Endpoint3", typeof(Endpoint3));
            // and so forth...

            return models[endpoint];  
        }

    }

我希望在发出请求时调用getModel(),以便我可以传入ApiEndpoint类中的ApiRequest字段并存储我想要的类型{ {1}}字段,以便我可以在Data类中动态更改它。

此外,还有ORM部分,我遍历每个端点并使用每个端点的模型/类型创建不同的表。像这样:

ApiResponse

但同样,我希望能够在此处调用endpoints.ForEach( (endpoint) => { db.CreateTableIfNotExists<Endpoint1>(); // inserting data, doing other work etc } ); ,然后定义我正在迭代的特定端点的模型。

我曾尝试在这两个地方拨打getModel(),但我总是收到错误,如getModel()和其他人......所以我肯定做错了。

随意为cannot use variable as a type建议一种不同的方法。这正是我想出的,但我可能忽略了一种更为简单的方法。

2 个答案:

答案 0 :(得分:3)

当我正确理解你时,你有不同的API调用,它们都返回相同的对象。唯一的区别是,字段“Data”可以有不同的类型。

然后您可以简单地将数据类型更改为object:

public object Data { get; set; }

之后只需将其转换为所需的对象:

var data1=(Endpoint1[]) response.Data;

答案 1 :(得分:1)

您将非常难以动态地动态创建.NET类型,这需要高级使用Reflection.Emit。尝试使用ServiceStack动态创建Request DTO是一种自我挫败,因为客户端和元数据服务需要具体的类型才能使用Typed API调用Service。

我不能真正按照你的例子,但我最初的方法是你是否可以使用单一服务(即不是试图动态创建多个服务)。与OrmLite一样,如果POCO的Schema相同,听起来您可以展平DataModel并使用单个数据库表。

AutoQuery是一个example of a feature dynamically creates Service Implementations来自一个具体的Request DTO,它实际上是您需要的最小类型。

因此,虽然它是highly recommended to have explict DTOs for each Service,但您可以使用继承来重用公共属性,例如:

[Route("/api/{ApiEndpoint}/1", "POST")]
public ApiRequest1 : ApiRequestBase<Endpoint1> {}

[Route("/api/{ApiEndpoint}/2", "POST")]
public ApiRequest2 : ApiRequestBase<Endpoint1> {}

public abstract class ApiRequestBase<T> : IReturn<ApiResponse<T>>
{
    public int OrderId { get; set; }
    public DateTime PurchaseDate { get; set; }
    public string ApiEndpoint { get; set; }
}

您的服务可以返回相同的通用响应DTO:

public class ApiResponse<T>
{
    public T[] Data { get; set; }
    public String ErrorCode { get; set; }
    public Int32 ErrorNumber { get; set; }
    public String ErrorDesc { get; set; }
}

我无法真正了解您尝试做的目的,因此API设计需要根据您的用例进行修改。

你会遇到与OrmLite类似的问题,这是一个Typed代码优先的POCO ORM,你会遇到摩擦试图使用在运行时不存在的动态类型,你可能会有更容易的时间executing Dynamic SQL因为生成字符串比.NET类型容易得多。

据说GenericTableExpressions.cs显示了一个更改OrmLite在运行时保存POCO的表名的示例:

const string tableName = "Entity1";
using (var db = OpenDbConnection())
{
    db.DropAndCreateTable<GenericEntity>(tableName);

    db.Insert(tableName, new GenericEntity { Id = 1, ColumnA = "A" });

    var rows = db.Select(tableName, db.From<GenericEntity>()
        .Where(x => x.ColumnA == "A"));

    Assert.That(rows.Count, Is.EqualTo(1));

    db.Update(tableName, new GenericEntity { ColumnA = "B" },
        where: q => q.ColumnA == "A");

    rows = db.Select(tableName, db.From<GenericEntity>()
        .Where(x => x.ColumnA == "B"));

    Assert.That(rows.Count, Is.EqualTo(1));
}

使用这些扩展方法:

public static class GenericTableExtensions
{
    static object ExecWithAlias<T>(string table, Func<object> fn)
    {
        var modelDef = typeof(T).GetModelMetadata();
        lock (modelDef)
        {
            var hold = modelDef.Alias;
            try
            {
                modelDef.Alias = table;
                return fn();
            }
            finally
            {
                modelDef.Alias = hold;
            }
        }
    }

    public static void DropAndCreateTable<T>(this IDbConnection db, string table)
    {
        ExecWithAlias<T>(table, () => { 
            db.DropAndCreateTable<T>();
            return null;
        });
    }

    public static long Insert<T>(this IDbConnection db, string table, T obj, bool selectIdentity = false)
    {
        return (long)ExecWithAlias<T>(table, () => db.Insert(obj, selectIdentity));
    }

    public static List<T> Select<T>(this IDbConnection db, string table, SqlExpression<T> expression)
    {
        return (List<T>)ExecWithAlias<T>(table, () => db.Select(expression));
    }

    public static int Update<T>(this IDbConnection db, string table, T item, Expression<Func<T, bool>> where)
    {
        return (int)ExecWithAlias<T>(table, () => db.Update(item, where));
    }
}

但这不是我个人采取的方法,如果我绝对需要(并且我正在努力想到基于表的多租户或分片之外的有效用例)来保存多个表中的相同模式我再次使用继承,例如:

public class Table1 : TableBase {}
public class Table2 : TableBase {}
public class Table3 : TableBase {}