来自运行时数据层的 C# 中的动态 odata 服务
Dynamic odata service in C# from runtime data layer
我正在尝试从我的 table 中的 table 创建动态 odata 服务,直到运行时才知道。因此,在我的 Web 应用程序开始时,用户选择一个数据库,然后在 C# 中我找到该数据库中的所有 table。
现在最困难的部分是我想为数据库中的每个 table 创建 odata 服务端点,并在我的 Web 应用程序中相应地使用它们。问题是我不知道如何动态地做到这一点。有很多编译时间已知数据库 tables 的例子,但在这种情况下,我不会在我的用户第一次使用我的应用程序之前拥有它们。
1.你试过用字典吗?我不知道它是否适用于 OData,只是作为各种数据出现的一个想法 connection/deserialisers 我使用过的也使用过字典。
2. 我更喜欢的想法是从数据源中检索 class,我在这里找到了一些可能对您有所帮助的东西:Class to DataSet / DataSet to class,也许如果没有更多的障碍存在,使用返回的数据构建一个结构来将数据放入...
抱歉,我对答案不太确定,只是想到了一些想法,我对 OData 了解不多。希望对你有帮助。
此处有一个不需要预定义 class 的示例:ODataUntypedSample,但它确实需要预定义控制器。
我在它的基础上构建了另一个控制台应用程序示例,以便能够使用 OData 查询任何 SQL 服务器数据库。我使用这个 nuget 包来读取数据库模式和数据:DatabaseSchemaReader。您需要以下 nuget 包才能构建它(加上依赖项):
- Microsoft.Owin.Hosting
- Microsoft.Owin.Host.HttpListener
- Microsoft.AspNet.WebApi.Owin
- Microsoft.AspNet.OData
主程序略有修改,因此它声明来自 tables 的 Edm(用于 OData)实体。我已经测试了标准样本 Adventure Works 2014 但它应该适用于任何 table 希望:
class Program
{
private static HttpClient client = new HttpClient();
private static TableControllerSelector selector;
private const string ServiceUrl = "http://localhost:12345";
private const string connectionString = @"Server=MYSQLSERVER;Database=AdventureWorks2014;Integrated Security=SSPI";
static void Main(string[] args)
{
using (WebApp.Start(ServiceUrl, Configuration))
{
Console.WriteLine("Server is listening at {0}", ServiceUrl);
RunSample();
Console.WriteLine("Press any key to continue . . .");
Console.ReadKey();
}
}
public static void Configuration(IAppBuilder builder)
{
HttpConfiguration configuration = new HttpConfiguration();
// create a special dynamic controller selector
selector = new TableControllerSelector(configuration);
IEdmModel model = TableController.GetEdmModel(connectionString, selector);
configuration.Services.Replace(typeof(IHttpControllerSelector), selector);
configuration.MapODataServiceRoute("odata", "odata", model); // needs using System.Web.OData.Extensions
builder.UseWebApi(configuration);
}
public static void RunSample()
{
Console.WriteLine("1. Get Metadata.");
GetMetadata();
Console.WriteLine("\n2. Get Entity Set.");
using (var dbReader = new DatabaseReader(connectionString, "System.Data.SqlClient"))
{
foreach (var table in dbReader.AllTables())
{
Console.WriteLine("\n 2.1 Get Entity Set '" + table.Name + "'.");
GetEntitySet(table.Name);
}
}
}
public static void GetMetadata()
{
HttpResponseMessage response = client.GetAsync(ServiceUrl + "/odata/$metadata").Result;
PrintResponse(response);
}
public static void GetEntitySet(string tableName)
{
HttpResponseMessage response = client.GetAsync(ServiceUrl + "/odata/" + tableName + "?$filter=Id eq 1").Result;
PrintResponse(response);
}
public static void PrintResponse(HttpResponseMessage response)
{
response.EnsureSuccessStatusCode();
Console.WriteLine("Response:");
Console.WriteLine(response);
if (response.Content != null)
{
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
}
}
以及特殊的 TableController 和 TableControllerSelector classes,它们允许从任何 SQL 服务器数据库创建 Edm 模型,并从该模型中的 Edm 实体动态创建控制器:
public class TableControllerSelector : DefaultHttpControllerSelector
{
private Dictionary<string, HttpControllerDescriptor> _tables = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
public TableControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
Configuration = configuration;
}
public HttpConfiguration Configuration { get; private set; }
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
string name = GetControllerName(request);
if (name != null) // is it a known table name?
{
HttpControllerDescriptor desc;
if (_tables.TryGetValue(name, out desc))
return desc;
}
return base.SelectController(request);
}
public void AddTable(string connectionString, DatabaseTable table)
{
if (connectionString == null)
throw new ArgumentNullException("connectionString");
if (table == null)
throw new ArgumentNullException("table");
// create a descriptor with extra properties that the controller needs
var desc = new HttpControllerDescriptor(Configuration, table.Name, typeof(TableController));
desc.Properties["table"] = table;
desc.Properties["connectionString"] = connectionString;
_tables[table.Name] = desc;
}
}
public class TableController : ODataController
{
// this will be called for standard OData access to collection
public EdmEntityObjectCollection Get()
{
// get Edm type from request
ODataPath path = Request.ODataProperties().Path; // ODataProperties() needs using System.Web.OData.Extensions
IEdmType edmType = path.EdmType;
IEdmCollectionType collectionType = (IEdmCollectionType)edmType;
IEdmEntityType entityType = (IEdmEntityType)collectionType.ElementType.Definition;
IEdmModel model = Request.ODataProperties().Model;
ODataQueryContext queryContext = new ODataQueryContext(model, entityType, path);
ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, Request);
// TODO: apply the query option on the IQueryable here.
// read all rows from table (could be optimized using query context)
var table = (DatabaseTable)ControllerContext.ControllerDescriptor.Properties["table"];
var cnx = (string)ControllerContext.ControllerDescriptor.Properties["connectionString"];
return new EdmEntityObjectCollection(new EdmCollectionTypeReference(collectionType), ReadData(entityType, table, cnx));
}
public static IList<IEdmEntityObject> ReadData(IEdmEntityType type, DatabaseTable table, string connectionString)
{
List<IEdmEntityObject> list = new List<IEdmEntityObject>();
// https://www.nuget.org/packages/DatabaseSchemaReader/
Reader reader = new Reader(table, connectionString, "System.Data.SqlClient");
reader.Read((r) =>
{
EdmEntityObject obj = new EdmEntityObject(type);
foreach (var prop in type.DeclaredProperties)
{
int index = r.GetOrdinal(prop.Name);
object value = r.GetValue(index);
if (Convert.IsDBNull(value))
{
value = null;
}
obj.TrySetPropertyValue(prop.Name, value);
}
list.Add(obj);
// uncomment these 2 lines if you're just testing maximum 10 rows on a table
//if (list.Count == 10)
// return false;
return true;
});
return list;
}
public static IEdmModel GetEdmModel(string connectionString, TableControllerSelector selector)
{
EdmModel model = new EdmModel();
// create and add entity container
EdmEntityContainer container = new EdmEntityContainer("NS", "DefaultContainer");
model.AddElement(container);
// https://www.nuget.org/packages/DatabaseSchemaReader/
using (var dbReader = new DatabaseReader(connectionString, "System.Data.SqlClient"))
{
var schema = dbReader.ReadAll();
foreach (var table in schema.Tables)
{
EdmEntityType tableType = new EdmEntityType("NS", table.Name);
foreach (var col in table.Columns)
{
var kind = GetKind(col);
if (!kind.HasValue) // don't map this
continue;
var prop = tableType.AddStructuralProperty(col.Name, kind.Value, col.Nullable);
if (col.IsPrimaryKey)
{
tableType.AddKeys(prop);
}
}
model.AddElement(tableType);
EdmEntitySet products = container.AddEntitySet(table.Name, tableType);
selector.AddTable(connectionString, table);
}
}
return model;
}
// determine Edm kind from column type
private static EdmPrimitiveTypeKind? GetKind(DatabaseColumn col)
{
var dt = col.DataType;
if (col.DataType == null)
return null;
Type type = col.DataType.GetNetType();
if (type == null)
return null;
if (type == typeof(string))
return EdmPrimitiveTypeKind.String;
if (type == typeof(short))
return EdmPrimitiveTypeKind.Int16;
if (type == typeof(int))
return EdmPrimitiveTypeKind.Int32;
if (type == typeof(long))
return EdmPrimitiveTypeKind.Int64;
if (type == typeof(bool))
return EdmPrimitiveTypeKind.Boolean;
if (type == typeof(Guid))
return EdmPrimitiveTypeKind.Guid;
if (type == typeof(DateTime))
return EdmPrimitiveTypeKind.DateTimeOffset;
if (type == typeof(TimeSpan))
return EdmPrimitiveTypeKind.Duration;
if (type == typeof(decimal))
return EdmPrimitiveTypeKind.Decimal;
if (type == typeof(byte) || type == typeof(sbyte))
return EdmPrimitiveTypeKind.Byte;
if (type == typeof(byte[]))
return EdmPrimitiveTypeKind.Binary;
if (type == typeof(double))
return EdmPrimitiveTypeKind.Double;
if (type == typeof(float))
return EdmPrimitiveTypeKind.Single;
return null;
}
}
我正在尝试从我的 table 中的 table 创建动态 odata 服务,直到运行时才知道。因此,在我的 Web 应用程序开始时,用户选择一个数据库,然后在 C# 中我找到该数据库中的所有 table。
现在最困难的部分是我想为数据库中的每个 table 创建 odata 服务端点,并在我的 Web 应用程序中相应地使用它们。问题是我不知道如何动态地做到这一点。有很多编译时间已知数据库 tables 的例子,但在这种情况下,我不会在我的用户第一次使用我的应用程序之前拥有它们。
1.你试过用字典吗?我不知道它是否适用于 OData,只是作为各种数据出现的一个想法 connection/deserialisers 我使用过的也使用过字典。
2. 我更喜欢的想法是从数据源中检索 class,我在这里找到了一些可能对您有所帮助的东西:Class to DataSet / DataSet to class,也许如果没有更多的障碍存在,使用返回的数据构建一个结构来将数据放入...
抱歉,我对答案不太确定,只是想到了一些想法,我对 OData 了解不多。希望对你有帮助。
此处有一个不需要预定义 class 的示例:ODataUntypedSample,但它确实需要预定义控制器。
我在它的基础上构建了另一个控制台应用程序示例,以便能够使用 OData 查询任何 SQL 服务器数据库。我使用这个 nuget 包来读取数据库模式和数据:DatabaseSchemaReader。您需要以下 nuget 包才能构建它(加上依赖项):
- Microsoft.Owin.Hosting
- Microsoft.Owin.Host.HttpListener
- Microsoft.AspNet.WebApi.Owin
- Microsoft.AspNet.OData
主程序略有修改,因此它声明来自 tables 的 Edm(用于 OData)实体。我已经测试了标准样本 Adventure Works 2014 但它应该适用于任何 table 希望:
class Program
{
private static HttpClient client = new HttpClient();
private static TableControllerSelector selector;
private const string ServiceUrl = "http://localhost:12345";
private const string connectionString = @"Server=MYSQLSERVER;Database=AdventureWorks2014;Integrated Security=SSPI";
static void Main(string[] args)
{
using (WebApp.Start(ServiceUrl, Configuration))
{
Console.WriteLine("Server is listening at {0}", ServiceUrl);
RunSample();
Console.WriteLine("Press any key to continue . . .");
Console.ReadKey();
}
}
public static void Configuration(IAppBuilder builder)
{
HttpConfiguration configuration = new HttpConfiguration();
// create a special dynamic controller selector
selector = new TableControllerSelector(configuration);
IEdmModel model = TableController.GetEdmModel(connectionString, selector);
configuration.Services.Replace(typeof(IHttpControllerSelector), selector);
configuration.MapODataServiceRoute("odata", "odata", model); // needs using System.Web.OData.Extensions
builder.UseWebApi(configuration);
}
public static void RunSample()
{
Console.WriteLine("1. Get Metadata.");
GetMetadata();
Console.WriteLine("\n2. Get Entity Set.");
using (var dbReader = new DatabaseReader(connectionString, "System.Data.SqlClient"))
{
foreach (var table in dbReader.AllTables())
{
Console.WriteLine("\n 2.1 Get Entity Set '" + table.Name + "'.");
GetEntitySet(table.Name);
}
}
}
public static void GetMetadata()
{
HttpResponseMessage response = client.GetAsync(ServiceUrl + "/odata/$metadata").Result;
PrintResponse(response);
}
public static void GetEntitySet(string tableName)
{
HttpResponseMessage response = client.GetAsync(ServiceUrl + "/odata/" + tableName + "?$filter=Id eq 1").Result;
PrintResponse(response);
}
public static void PrintResponse(HttpResponseMessage response)
{
response.EnsureSuccessStatusCode();
Console.WriteLine("Response:");
Console.WriteLine(response);
if (response.Content != null)
{
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
}
}
以及特殊的 TableController 和 TableControllerSelector classes,它们允许从任何 SQL 服务器数据库创建 Edm 模型,并从该模型中的 Edm 实体动态创建控制器:
public class TableControllerSelector : DefaultHttpControllerSelector
{
private Dictionary<string, HttpControllerDescriptor> _tables = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
public TableControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
Configuration = configuration;
}
public HttpConfiguration Configuration { get; private set; }
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
string name = GetControllerName(request);
if (name != null) // is it a known table name?
{
HttpControllerDescriptor desc;
if (_tables.TryGetValue(name, out desc))
return desc;
}
return base.SelectController(request);
}
public void AddTable(string connectionString, DatabaseTable table)
{
if (connectionString == null)
throw new ArgumentNullException("connectionString");
if (table == null)
throw new ArgumentNullException("table");
// create a descriptor with extra properties that the controller needs
var desc = new HttpControllerDescriptor(Configuration, table.Name, typeof(TableController));
desc.Properties["table"] = table;
desc.Properties["connectionString"] = connectionString;
_tables[table.Name] = desc;
}
}
public class TableController : ODataController
{
// this will be called for standard OData access to collection
public EdmEntityObjectCollection Get()
{
// get Edm type from request
ODataPath path = Request.ODataProperties().Path; // ODataProperties() needs using System.Web.OData.Extensions
IEdmType edmType = path.EdmType;
IEdmCollectionType collectionType = (IEdmCollectionType)edmType;
IEdmEntityType entityType = (IEdmEntityType)collectionType.ElementType.Definition;
IEdmModel model = Request.ODataProperties().Model;
ODataQueryContext queryContext = new ODataQueryContext(model, entityType, path);
ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, Request);
// TODO: apply the query option on the IQueryable here.
// read all rows from table (could be optimized using query context)
var table = (DatabaseTable)ControllerContext.ControllerDescriptor.Properties["table"];
var cnx = (string)ControllerContext.ControllerDescriptor.Properties["connectionString"];
return new EdmEntityObjectCollection(new EdmCollectionTypeReference(collectionType), ReadData(entityType, table, cnx));
}
public static IList<IEdmEntityObject> ReadData(IEdmEntityType type, DatabaseTable table, string connectionString)
{
List<IEdmEntityObject> list = new List<IEdmEntityObject>();
// https://www.nuget.org/packages/DatabaseSchemaReader/
Reader reader = new Reader(table, connectionString, "System.Data.SqlClient");
reader.Read((r) =>
{
EdmEntityObject obj = new EdmEntityObject(type);
foreach (var prop in type.DeclaredProperties)
{
int index = r.GetOrdinal(prop.Name);
object value = r.GetValue(index);
if (Convert.IsDBNull(value))
{
value = null;
}
obj.TrySetPropertyValue(prop.Name, value);
}
list.Add(obj);
// uncomment these 2 lines if you're just testing maximum 10 rows on a table
//if (list.Count == 10)
// return false;
return true;
});
return list;
}
public static IEdmModel GetEdmModel(string connectionString, TableControllerSelector selector)
{
EdmModel model = new EdmModel();
// create and add entity container
EdmEntityContainer container = new EdmEntityContainer("NS", "DefaultContainer");
model.AddElement(container);
// https://www.nuget.org/packages/DatabaseSchemaReader/
using (var dbReader = new DatabaseReader(connectionString, "System.Data.SqlClient"))
{
var schema = dbReader.ReadAll();
foreach (var table in schema.Tables)
{
EdmEntityType tableType = new EdmEntityType("NS", table.Name);
foreach (var col in table.Columns)
{
var kind = GetKind(col);
if (!kind.HasValue) // don't map this
continue;
var prop = tableType.AddStructuralProperty(col.Name, kind.Value, col.Nullable);
if (col.IsPrimaryKey)
{
tableType.AddKeys(prop);
}
}
model.AddElement(tableType);
EdmEntitySet products = container.AddEntitySet(table.Name, tableType);
selector.AddTable(connectionString, table);
}
}
return model;
}
// determine Edm kind from column type
private static EdmPrimitiveTypeKind? GetKind(DatabaseColumn col)
{
var dt = col.DataType;
if (col.DataType == null)
return null;
Type type = col.DataType.GetNetType();
if (type == null)
return null;
if (type == typeof(string))
return EdmPrimitiveTypeKind.String;
if (type == typeof(short))
return EdmPrimitiveTypeKind.Int16;
if (type == typeof(int))
return EdmPrimitiveTypeKind.Int32;
if (type == typeof(long))
return EdmPrimitiveTypeKind.Int64;
if (type == typeof(bool))
return EdmPrimitiveTypeKind.Boolean;
if (type == typeof(Guid))
return EdmPrimitiveTypeKind.Guid;
if (type == typeof(DateTime))
return EdmPrimitiveTypeKind.DateTimeOffset;
if (type == typeof(TimeSpan))
return EdmPrimitiveTypeKind.Duration;
if (type == typeof(decimal))
return EdmPrimitiveTypeKind.Decimal;
if (type == typeof(byte) || type == typeof(sbyte))
return EdmPrimitiveTypeKind.Byte;
if (type == typeof(byte[]))
return EdmPrimitiveTypeKind.Binary;
if (type == typeof(double))
return EdmPrimitiveTypeKind.Double;
if (type == typeof(float))
return EdmPrimitiveTypeKind.Single;
return null;
}
}