fix json+ld date and other attribs

This commit is contained in:
wgroeneveld 2020-06-09 12:16:37 +02:00
parent 7ae0debe5a
commit eb8c80a39c
5 changed files with 182 additions and 154 deletions

View File

@ -9,8 +9,6 @@ subtitle: Decoupling your integrated database environment from your development.
tags: [ 'unit testing', 'sql', 'CSharp', 'sqlite' ]
---
This article is based on the notes I've collected on [My Wiki](http://brainbaking.com/wiki/code/db/sqlite).
On previous projects I've worked on, development PCs came with a local version of the database scheme. Each DB change also got rolled out to those computers, which enabled us developers to fool around without breaking anything on the development (or test) environment. This is another step closer to happiness, at least for our proxy customers who didn't have to reinsert their test data every time we flushed something from a table. Sometimes though, there's some lame excuse for not having a local database installed:
- We have a lot of stored procedures and it's too hard to duplicate them locally
@ -25,50 +23,54 @@ Installing an Oracle XE runtime on your machine might include working around som
Simply use [System.data.SQLite](http://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki). For each OleDb object, there's an equivalent SQLite one in the correct namespace. The only problem is, some of them don't share an abstract object so you'll have to come up with an anti-corruption layer yourself. Create a connection using this connection string:
private SQLiteConnection SqLiteDbConnection()
```c#
private SQLiteConnection SqLiteDbConnection()
{
return new SQLiteConnection()
{
return new SQLiteConnection()
{
ConnectionString = "Data Source=:memory:;Version=3;New=True;DateTimeFormat=Ticks",
Flags = SQLiteConnectionFlags.LogAll
};
}
public void SetupDb()
{
using (var connection = SqLiteDbConnection())
ConnectionString = "Data Source=:memory:;Version=3;New=True;DateTimeFormat=Ticks",
Flags = SQLiteConnectionFlags.LogAll
};
}
public void SetupDb()
{
using (var connection = SqLiteDbConnection())
{
connection.Open();
var transaction = connection.BeginTransaction();
var sqLiteCommand = new SQLiteCommand()
{
connection.Open();
var transaction = connection.BeginTransaction();
var sqLiteCommand = new SQLiteCommand()
{
Connection = (SQLiteConnection) connection,
CommandType = CommandType.Text,
CommandText = GetSchemaCreateSql()
};
sqLiteCommand.ExecuteNonQuery();
transaction.Commit();
}
}
Connection = (SQLiteConnection) connection,
CommandType = CommandType.Text,
CommandText = GetSchemaCreateSql()
};
sqLiteCommand.ExecuteNonQuery();
transaction.Commit();
}
}
```
You need to pay attention to the `DateTimeFormat` substring in the connection string as SQLite is "dynamically typed", compared to Oracle. This means it stores dates exactly the same as chars, otherwise you might encounter an error like `"string was not recognized as a valid DateTime"` when executing a select statement.
**Watch out with closing the DB Connection** using an in-memory DB; as this completely resets everything. As soon as you open a connection, you can execute create table commands (read your stored DDL file and do it in bulk).
Your anti-corruption layer between the abstract DB Connection and SQLite/OleDB should expose a few methods. It should be able to query (with or without parameters or providing a `DbCommand`) and possibly stored procedures. This is what I've come up with:
public interface IdbConnection
{
object QueryProcedure(string procedure, IDictionary<string, object> parameters, string outputParameter);
DbParameter CreateParameter(string field, object value);
DbCommand CreateCommand(string query);
DataSet Query(DbCommand command);
DataSet Query(string query);
}
```C#
public interface IdbConnection
{
object QueryProcedure(string procedure, IDictionary<string, object> parameters, string outputParameter);
DbParameter CreateParameter(string field, object value);
DbCommand CreateCommand(string query);
DataSet Query(DbCommand command);
DataSet Query(string query);
}
```
Depending on the implementation, it'll return an `SQLiteCommand` or an `OleDbCommand` instance.
### Creating integration tests, using Record objects
@ -77,51 +79,55 @@ To be able to quickly insert junk in an in-memory table, I came up with a simple
Create an object for each table in your unit test project, extending `DatabaseInsertable`:
public abstract class DatabaseInsertable
```C#
public abstract class DatabaseInsertable
{
protected abstract string GetTable();
public override string ToString()
{
protected abstract string GetTable();
public override string ToString()
{
var fieldDict = FieldDictionary();
var fields = "(" + string.Join(",", fieldDict.Keys) + ")";
var values = "(" + string.Join(",", fieldDict.Values) + ")";
return "insert into " + GetTable() + fields + " values " + values;
}
public void Save()
{
DbConnection.Instance.CreateCommand(ToString()).ExecuteNonQuery();
}
private Dictionary<string, string> FieldDictionary()
{
var dictionary = new Dictionary<string, string>();
foreach (var info in this.GetType().GetFields())
{
if (info.GetValue(this) != null)
{
dictionary.Add(info.Name, "'" + info.GetValue(this).ToString() + "'");
}
}
return dictionary;
}
var fieldDict = FieldDictionary();
var fields = "(" + string.Join(",", fieldDict.Keys) + ")";
var values = "(" + string.Join(",", fieldDict.Values) + ")";
return "insert into " + GetTable() + fields + " values " + values;
}
public void Save()
{
DbConnection.Instance.CreateCommand(ToString()).ExecuteNonQuery();
}
private Dictionary<string, string> FieldDictionary()
{
var dictionary = new Dictionary<string, string>();
foreach (var info in this.GetType().GetFields())
{
if (info.GetValue(this) != null)
{
dictionary.Add(info.Name, "'" + info.GetValue(this).ToString() + "'");
}
}
return dictionary;
}
}
```
For instance:
internal class UnitRecord : DatabaseInsertable
```C#
internal class UnitRecord : DatabaseInsertable
{
public string creator;
public string guid;
protected override string GetTable()
{
public string creator;
public string guid;
protected override string GetTable()
{
return "UNIT";
}
return "UNIT";
}
}
```
Now you can simply issue `new UnitRecord() { creator = "bla"; guid = "lala"; }.Save();` and it's saved into the unit table, yay!

View File

@ -13,46 +13,50 @@ We were looking for a few alternatives to our big ExtJS 4 application. Since it'
The application right now uses Extjs as UI and C# as backend, and lets ext do the loading of the views/controllers (living in app.js like most ext applications). There's no ecosystem set up like modern javascript applications - build systems like Grunt, Gulp, node package managers, Browserify, ... are all not used. We do use sencha command to minify stuff. To be able to develop new modules without having to worry about extjs, one of the possibilities would be to use iframes. That enables us to (scenario) test the module using it's own routing. It's wrapped inside an Extjs view with an iframe:
Ext.define('App.view.utilities.desktop.ReactWindow', {
extend: 'Ext.form.Panel',
alias: 'widget.App_view_utilities_desktop_ReactWindow',
```js
Ext.define('App.view.utilities.desktop.ReactWindow', {
extend: 'Ext.form.Panel',
alias: 'widget.App_view_utilities_desktop_ReactWindow',
bodyPadding: 5,
width: 600,
bodyPadding: 5,
width: 600,
layout: {
type: 'vbox',
align: 'stretch'
},
layout: {
type: 'vbox',
align: 'stretch'
},
initComponent: function() {
var me = this;
initComponent: function() {
var me = this;
var dynamicPanel = new Ext.Component({
autoEl: {
tag: 'iframe',
style: 'border: none',
src: me.url
},
flex: 1
});
var dynamicPanel = new Ext.Component({
autoEl: {
tag: 'iframe',
style: 'border: none',
src: me.url
},
flex: 1
});
Ext.apply(me, {
title: 'React',
defaults: {
labelWidth: 120
},
items: [dynamicPanel]
});
me.callParent();
}
});
Ext.apply(me, {
title: 'React',
defaults: {
labelWidth: 120
},
items: [dynamicPanel]
});
me.callParent();
}
});
```
When the module is triggered in the main app, we simply add the panel to the desktop:
this.addPanel(Ext.create('App.view.utilities.desktop.ReactWindow', {
url: 'react/mod/someurl/'
}));
```js
this.addPanel(Ext.create('App.view.utilities.desktop.ReactWindow', {
url: 'react/mod/someurl/'
}));
```
Our app structure in the GUI folder would be something like this:
@ -64,45 +68,47 @@ Our app structure in the GUI folder would be something like this:
That's simple enough. But how would one be able to open new Ext panels from within the React sub-application? That would be done via custom events thrown to the parent window. Catching these is just a matter of adding this to some controller in Extjs:
window.addEventListener('react', function(e) {
me.onReactEvent(e.detail, e);
});
```js
window.addEventListener('react', function(e) {
me.onReactEvent(e.detail, e);
});
```
The `detail` property is part of a custom event, thrown in a react component. This below might be some cell component, taken from the [fixed-data-table](https://facebook.github.io/fixed-data-table/) example:
class MyLinkCell extends React.Component {
clicked(e) {
const el = e.target;
const eventObj = {
'detail': {
'type': 'downloadlink',
'url': 'react/some/detail/url'
}
};
console.log('clicked - "react" event thrown:');
console.dir(eventObj);
if(window.parent) {
window.parent.dispatchEvent(new CustomEvent('react', eventObj));
```js
class MyLinkCell extends React.Component {
clicked(e) {
const el = e.target;
const eventObj = {
'detail': {
'type': 'downloadlink',
'url': 'react/some/detail/url'
}
}
};
render() {
const {rowIndex, field, data} = this.props;
const link = data[rowIndex][field];
return (
<Cell>
<a onClick={this.clicked} href='#'>{link}</a>
</Cell>
);
}
console.log('clicked - "react" event thrown:');
console.dir(eventObj);
if(window.parent) {
window.parent.dispatchEvent(new CustomEvent('react', eventObj));
}
}
render() {
const {rowIndex, field, data} = this.props;
const link = data[rowIndex][field];
return (
<Cell>
<a onClick={this.clicked} href='#'>{link}</a>
</Cell>
);
}
}
```
Of course this is more or less the same when for instance using Angular2 instead of React, the custom event is part of the JS standard, see [Creating and triggering events](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events) from MDN.
To be able to use source maps in conjunction with Browserify/Watchify, I had to tweak some parameters in package.json:
`watchify index.js --verbose -d -t babelify --sourceMapRelative . --outfile=bundle.js`
To be able to use source maps in conjunction with Browserify/Watchify, I had to tweak some parameters in package.json: `watchify index.js --verbose -d -t babelify --sourceMapRelative . --outfile=bundle.js`
Things we still need to research:

View File

@ -147,3 +147,8 @@ kbd
outline-color: transparent
-webkit-transition: left .1s ease-in
transition: left .1s ease-in
.sl-counter
font-size: 1.2rem !important
font-weight: bold

View File

@ -1,6 +1,6 @@
.commento-root
textarea
font-size: 12pt !important
font-size: 1rem !important
.commento-round-check input[type=checkbox]:checked+label:before, .commento-round-check input[type=radio]:checked+label:before
background: var(--accent) !important
@ -12,7 +12,7 @@
.commento-card
.commento-body
p
font-size: 12pt !important
font-size: 1rem !important
.commento-button
color: white !important
@ -26,7 +26,7 @@
color: var(--accent) !important
.commento-name
font-size: 14pt !important
font-size: 1.1rem !important
.commento-root *,
.commento-root-font *

View File

@ -49,13 +49,13 @@
"@id": "{{ .Site.BaseURL }}"
},
"articleSection" : "{{ .Section }}",
"name" : "{{ .Title }}",
"name" : "{{ .Title | safeJS }}",
{{ if .Params.subtitle }}
"headline" : "{{ .Params.subtitle }}",
"headline" : "{{ .Params.subtitle | safeJS }}",
{{ else }}
"headline" : "{{ .Title }}",
"headline" : "{{ .Title | safeJS }}",
{{ end }}
"description" : "{{ if .Description }}{{ .Description }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ end }}{{ end }}",
"description" : "{{ if .Description }}{{ .Description | safeJS }}{{ else }}{{if .IsPage}}{{ .Summary | safeJS }}{{ end }}{{ end }}",
{{ if isset .Params "language" }}
"inLanguage" : "{{ .Params.language }}",
{{ else if eq "essays" .Section }}
@ -65,8 +65,9 @@
{{ else }}
"inLanguage" : "en-US",
{{ end }}
"isFamilyFriendly": "true",
{{ if .Params.bigimg }}
"image": "{{.Site.BaseURL}}{{ .Params.bigimg }}",
"image": "{{.Site.BaseURL}}bigimg/{{ .Params.bigimg }}",
{{ else }}
"image": "{{.Site.BaseURL}}img/avatar-icon.png",
{{ end }}
@ -74,22 +75,32 @@
"@type": "Person",
"name": "{{ .Site.Author.name }}"
},
"creator" : {
"@type": "Person",
"name": "{{ .Site.Author.name }}"
},
"publisher": {
"@type": "Organization",
"name": "{{ .Site.Title }}",
"url": {{ .Site.BaseURL }},
"logo": {
"@type": "ImageObject",
"url": "{{.Site.BaseURL}}img/avatar-icon.png"
"url": "{{.Site.BaseURL}}img/avatar-icon.png",
"width":"32",
"height":"32"
}
},
"accountablePerson" : "{{ .Site.Author.name }}",
"copyrightHolder" : "{{ .Site.Author.name }}",
"copyrightHolder" : "{{ .Site.Title }}",
"copyrightYear" : "{{ .Date.Format "2006" }}",
"datePublished": "{{ if isset .Params "date" }}{{ .Date }}{{ else }}{{ .Lastmod }}{{ end }}",
"dateModified" : "{{ .Lastmod }}",
"dateCreated": "{{ .Date.Format "2006-01-02T15:04:05.00Z" | safeHTML }}",
"datePublished": "{{ .PublishDate.Format "2006-01-02T15:04:05.00Z" | safeHTML }}",
"dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05.00Z" | safeHTML }}",
"url" : "{{ .Permalink }}",
"wordCount" : "{{ .WordCount }}",
"keywords" : [ {{ if isset .Params "tags" }}{{ range .Params.tags }}"{{ . }}",{{ end }}{{ end }}"{{ .Title }}", "{{ .Section }}" ]
"keywords" : [ {{ if isset .Params "tags" }}{{ range .Params.tags }}"{{ . }}",{{ end }}{{ end }}"{{ .Title }}", "{{ .Section }}" ],
"genre" : [ {{ if isset .Params "tags" }}{{ range .Params.tags }}"{{ . }}",{{ end }}{{ end }}"{{ .Title }}", "{{ .Section }}" ],
"articleBody": "{{ .Content | safeJS }}"
}
</script>