在 Excel 中打开 OData 服务时出错,EDMX 元数据不正确
Error when opening an OData service in Excel, incorrect EDMX metadata
我正在尝试编写一个 RESTful API 来发布一些数据集。在 Python 中执行此操作并希望遵循 OData 标准。目标是能够从 Excel(数据 > 新查询 > OData)中打开数据。
第一次尝试似乎很有希望:我制作了一个连接到 mysql 数据库和 returns 所需行的 Flask api。
不幸的是,从 Excel 开始还不起作用。它在元数据描述符上出错,说它遇到了意外的 属性 'Article name' 名称(我的数据集的字母第一列),它只期望 'name' 和 'url' 在服务文件。
我似乎无法弄清楚正在生成的元数据有什么问题,因此非常感谢您的帮助。
app.py:
from flask import Flask, jsonify, request, make_response
from flask_restful import Resource, Api, reqparse
import pandas as pd
import mysql.connector as sql
import ast
import xml.etree.ElementTree as ET
app = Flask(__name__)
api = Api(app, default_mediatype='application/json')
config = {
'host': '127.0.0.1',
'port': '3306',
'user': '***',
'passwd': '***',
'database': '***',
'charset': 'utf8mb4',
'raise_on_warnings': True
}
class FinancieleInstrumenten(Resource):
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('begrotingsjaar', required=False)
parser.add_argument('begrotingshoofdstuk', required=False)
args = parser.parse_args() # parse arguments to dictionary
try:
cnx = sql.connect(**config)
# Build query depending on input variables
if not args['begrotingsjaar'] and not args['begrotingshoofdstuk']:
qry = "SELECT * FROM FinancieleInstrumenten LIMIT 25;"
res = pd.read_sql(qry, cnx)
elif not args['begrotingshoofdstuk']:
qry = """SELECT * FROM FinancieleInstrumenten WHERE Begrotingsjaar = {} LIMIT 25;"""
res = pd.read_sql_query(qry.format(args['begrotingsjaar']), cnx)
elif not args['begrotingsjaar']:
qry = """SELECT * FROM FinancieleInstrumenten WHERE Begrotingshoofdstuk = {} LIMIT 25;"""
res = pd.read_sql_query(qry.format(args['begrotingshoofdstuk']), cnx)
else:
qry = """SELECT * FROM FinancieleInstrumenten WHERE Begrotingsjaar = {} AND Begrotingshoofdstuk = {} LIMIT 25;"""
res = pd.read_sql_query(qry.format(args['begrotingsjaar'], args['begrotingshoofdstuk']), cnx)
# Build response with OData header
resp = make_response({
'@odata.context': 'https://stukkenparser.gitlab-minfin.nl/financiele-instrumenten/$metadata',
'value': res.to_dict('records')
})
resp.headers['OData-Version'] = '4.0'
return resp
except Exception as e:
print(str(e))
finally:
if cnx.is_connected():
cnx.close()
def put(self):
return {'message': 'Only method GET allowed.'}, 405
def post(self):
return {'message': 'Only method GET allowed.'}, 405
def patch(self):
return {'message': 'Only method GET allowed.'}, 405
def delete(self):
return {'message': 'Only method GET allowed.'}, 405
api.add_resource(FinancieleInstrumenten, '/financiele-instrumenten')
@app.route("/financiele-instrumenten/$metadata", methods=['GET'])
def index():
if request.method=='GET':
root = ET.parse('financiele-instrumenten.metadata.xml').getroot()
return app.response_class(ET.tostring(root), mimetype='application/xml')
if __name__ == '__main__':
app.run(debug=True, host='127.0.0.1', port=1337)
金融-instrumenten.metadata.xml:
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="NL.MinFin.OData.FinancieleInstrumenten">
<EntityContainer Name="FinancieleInstrumentenEntities">
<EntitySet Name="FinancieleInstrumentenSet" EntityType="NL.MinFin.OData.FinancieleInstrumenten.FinancieleInstrumentenType" />
</EntityContainer>
<EntityType Name="FinancieleInstrumentenType">
<Property Name="Begrotingsjaar" Type="Edm.String" Nullable="false" />
<Property Name="Begrotingshoofdstuk" Type="Edm.String" Nullable="false" />
<Property Name="Begrotingsnaam" Type="Edm.String" Nullable="false" />
<Property Name="Artikelnummer" Type="Edm.String" Nullable="true" />
<Property Name="Artikelnaam" Type="Edm.String" Nullable="true" />
<Property Name="Artikelonderdeel" Type="Edm.String" Nullable="true" />
<Property Name="Instrument" Type="Edm.String" Nullable="true" />
<Property Name="Regeling" Type="Edm.String" Nullable="true" />
<Property Name="Ontvanger" Type="Edm.String" Nullable="true" />
<Property Name="KvK-nummer" Type="Edm.String" Nullable="true" />
<Property Name="Bedrag" Type="Edm.Int64" Nullable="false" />
</EntityType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
简答:
您的元数据不正确,因为您的实体类型缺少一个键
长答案:
来自:https://docs.microsoft.com/en-us/odata/concepts/data-model:
实体类型
实体类型被命名为带有键的结构化类型。它们定义实体的命名属性和关系。实体类型可以通过从其他实体类型的单一继承派生。
实体类型的键由实体类型的原始属性(例如 CustomerId、OrderId、LineId 等)的子集构成。
--
因此,实体的本质是,您可以唯一地标识它,以便从我们作为导航属性来源的外部访问它。因此,您必须定义一个实体键,这样,例如,当 ODATA 想要导航到其中一个时,它可以做到这一点,例如 /FinancieleInstrumentenEntities(key)/
.
你的 edmx 应该看起来像这样(我猜测了密钥,希望它是独一无二的):
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="NL.MinFin.OData.FinancieleInstrumenten">
<EntityContainer Name="FinancieleInstrumentenEntities">
<EntitySet Name="FinancieleInstrumentenSet" EntityType="NL.MinFin.OData.FinancieleInstrumenten.FinancieleInstrumentenType" />
</EntityContainer>
<EntityType Name="FinancieleInstrumentenType">
<Key>
<PropertyRef Name='Begrotingsjaar'/>
<PropertyRef Name='Begrotingshoofdstuk'/>
<PropertyRef Name='Begrotingsnaam'/>
</Key>
<Property Name="Begrotingsjaar" Type="Edm.String" Nullable="false" />
<Property Name="Begrotingshoofdstuk" Type="Edm.String" Nullable="false" />
<Property Name="Begrotingsnaam" Type="Edm.String" Nullable="false" />
<Property Name="Artikelnummer" Type="Edm.String" Nullable="true" />
<Property Name="Artikelnaam" Type="Edm.String" Nullable="true" />
<Property Name="Artikelonderdeel" Type="Edm.String" Nullable="true" />
<Property Name="Instrument" Type="Edm.String" Nullable="true" />
<Property Name="Regeling" Type="Edm.String" Nullable="true" />
<Property Name="Ontvanger" Type="Edm.String" Nullable="true" />
<Property Name="KvK-nummer" Type="Edm.String" Nullable="true" />
<Property Name="Bedrag" Type="Edm.Int64" Nullable="false" />
</EntityType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
如您所见,对于复合键,您可以添加多个 属性 引用。你至少需要一个 .
您用作键的属性必须不能为空,并且组合是唯一的很重要。 (在那种情况下我不确定)
如果您没有可唯一识别的项目,您可以尝试使用复杂类型而不是实体,将其作为 属性 附加到根实体(仍然需要密钥)。
我正在尝试编写一个 RESTful API 来发布一些数据集。在 Python 中执行此操作并希望遵循 OData 标准。目标是能够从 Excel(数据 > 新查询 > OData)中打开数据。
第一次尝试似乎很有希望:我制作了一个连接到 mysql 数据库和 returns 所需行的 Flask api。 不幸的是,从 Excel 开始还不起作用。它在元数据描述符上出错,说它遇到了意外的 属性 'Article name' 名称(我的数据集的字母第一列),它只期望 'name' 和 'url' 在服务文件。
我似乎无法弄清楚正在生成的元数据有什么问题,因此非常感谢您的帮助。
app.py:
from flask import Flask, jsonify, request, make_response
from flask_restful import Resource, Api, reqparse
import pandas as pd
import mysql.connector as sql
import ast
import xml.etree.ElementTree as ET
app = Flask(__name__)
api = Api(app, default_mediatype='application/json')
config = {
'host': '127.0.0.1',
'port': '3306',
'user': '***',
'passwd': '***',
'database': '***',
'charset': 'utf8mb4',
'raise_on_warnings': True
}
class FinancieleInstrumenten(Resource):
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('begrotingsjaar', required=False)
parser.add_argument('begrotingshoofdstuk', required=False)
args = parser.parse_args() # parse arguments to dictionary
try:
cnx = sql.connect(**config)
# Build query depending on input variables
if not args['begrotingsjaar'] and not args['begrotingshoofdstuk']:
qry = "SELECT * FROM FinancieleInstrumenten LIMIT 25;"
res = pd.read_sql(qry, cnx)
elif not args['begrotingshoofdstuk']:
qry = """SELECT * FROM FinancieleInstrumenten WHERE Begrotingsjaar = {} LIMIT 25;"""
res = pd.read_sql_query(qry.format(args['begrotingsjaar']), cnx)
elif not args['begrotingsjaar']:
qry = """SELECT * FROM FinancieleInstrumenten WHERE Begrotingshoofdstuk = {} LIMIT 25;"""
res = pd.read_sql_query(qry.format(args['begrotingshoofdstuk']), cnx)
else:
qry = """SELECT * FROM FinancieleInstrumenten WHERE Begrotingsjaar = {} AND Begrotingshoofdstuk = {} LIMIT 25;"""
res = pd.read_sql_query(qry.format(args['begrotingsjaar'], args['begrotingshoofdstuk']), cnx)
# Build response with OData header
resp = make_response({
'@odata.context': 'https://stukkenparser.gitlab-minfin.nl/financiele-instrumenten/$metadata',
'value': res.to_dict('records')
})
resp.headers['OData-Version'] = '4.0'
return resp
except Exception as e:
print(str(e))
finally:
if cnx.is_connected():
cnx.close()
def put(self):
return {'message': 'Only method GET allowed.'}, 405
def post(self):
return {'message': 'Only method GET allowed.'}, 405
def patch(self):
return {'message': 'Only method GET allowed.'}, 405
def delete(self):
return {'message': 'Only method GET allowed.'}, 405
api.add_resource(FinancieleInstrumenten, '/financiele-instrumenten')
@app.route("/financiele-instrumenten/$metadata", methods=['GET'])
def index():
if request.method=='GET':
root = ET.parse('financiele-instrumenten.metadata.xml').getroot()
return app.response_class(ET.tostring(root), mimetype='application/xml')
if __name__ == '__main__':
app.run(debug=True, host='127.0.0.1', port=1337)
金融-instrumenten.metadata.xml:
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="NL.MinFin.OData.FinancieleInstrumenten">
<EntityContainer Name="FinancieleInstrumentenEntities">
<EntitySet Name="FinancieleInstrumentenSet" EntityType="NL.MinFin.OData.FinancieleInstrumenten.FinancieleInstrumentenType" />
</EntityContainer>
<EntityType Name="FinancieleInstrumentenType">
<Property Name="Begrotingsjaar" Type="Edm.String" Nullable="false" />
<Property Name="Begrotingshoofdstuk" Type="Edm.String" Nullable="false" />
<Property Name="Begrotingsnaam" Type="Edm.String" Nullable="false" />
<Property Name="Artikelnummer" Type="Edm.String" Nullable="true" />
<Property Name="Artikelnaam" Type="Edm.String" Nullable="true" />
<Property Name="Artikelonderdeel" Type="Edm.String" Nullable="true" />
<Property Name="Instrument" Type="Edm.String" Nullable="true" />
<Property Name="Regeling" Type="Edm.String" Nullable="true" />
<Property Name="Ontvanger" Type="Edm.String" Nullable="true" />
<Property Name="KvK-nummer" Type="Edm.String" Nullable="true" />
<Property Name="Bedrag" Type="Edm.Int64" Nullable="false" />
</EntityType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
简答:
您的元数据不正确,因为您的实体类型缺少一个键
长答案:
来自:https://docs.microsoft.com/en-us/odata/concepts/data-model:
实体类型 实体类型被命名为带有键的结构化类型。它们定义实体的命名属性和关系。实体类型可以通过从其他实体类型的单一继承派生。
实体类型的键由实体类型的原始属性(例如 CustomerId、OrderId、LineId 等)的子集构成。
--
因此,实体的本质是,您可以唯一地标识它,以便从我们作为导航属性来源的外部访问它。因此,您必须定义一个实体键,这样,例如,当 ODATA 想要导航到其中一个时,它可以做到这一点,例如 /FinancieleInstrumentenEntities(key)/
.
你的 edmx 应该看起来像这样(我猜测了密钥,希望它是独一无二的):
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="NL.MinFin.OData.FinancieleInstrumenten">
<EntityContainer Name="FinancieleInstrumentenEntities">
<EntitySet Name="FinancieleInstrumentenSet" EntityType="NL.MinFin.OData.FinancieleInstrumenten.FinancieleInstrumentenType" />
</EntityContainer>
<EntityType Name="FinancieleInstrumentenType">
<Key>
<PropertyRef Name='Begrotingsjaar'/>
<PropertyRef Name='Begrotingshoofdstuk'/>
<PropertyRef Name='Begrotingsnaam'/>
</Key>
<Property Name="Begrotingsjaar" Type="Edm.String" Nullable="false" />
<Property Name="Begrotingshoofdstuk" Type="Edm.String" Nullable="false" />
<Property Name="Begrotingsnaam" Type="Edm.String" Nullable="false" />
<Property Name="Artikelnummer" Type="Edm.String" Nullable="true" />
<Property Name="Artikelnaam" Type="Edm.String" Nullable="true" />
<Property Name="Artikelonderdeel" Type="Edm.String" Nullable="true" />
<Property Name="Instrument" Type="Edm.String" Nullable="true" />
<Property Name="Regeling" Type="Edm.String" Nullable="true" />
<Property Name="Ontvanger" Type="Edm.String" Nullable="true" />
<Property Name="KvK-nummer" Type="Edm.String" Nullable="true" />
<Property Name="Bedrag" Type="Edm.Int64" Nullable="false" />
</EntityType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
如您所见,对于复合键,您可以添加多个 属性 引用。你至少需要一个 .
您用作键的属性必须不能为空,并且组合是唯一的很重要。 (在那种情况下我不确定)
如果您没有可唯一识别的项目,您可以尝试使用复杂类型而不是实体,将其作为 属性 附加到根实体(仍然需要密钥)。