I am experimenting with the json_value. The goal is to create dynamic model in the database which can contain any type of data using json.
So this is the model:
public class DynamicModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public string TableName { get; set; } = "MyTable";
public Guid? Container { get; set; } = null; //Parent who hold this data for many-one relation
public string Content { get; set; } = ""; //JSON content
public bool DeleteOnCascade { get; set; } = false;
public DateTime DateCreated { get; set; } = DateTime.UtcNow;
public DateTime LastEdited { get; set; } = DateTime.UtcNow;
}
So for example if the Content contains:
"{ \"name\":\"John\", \"age\":30, \"cars\":[] }"
The problem if I put the cars into the same row, the data will be too big and not efficient for accessing the data.
So I figure it will be better if I separate the nested list of the car into different row:
var car = new DynamicModel() {
Id = ...,
TableName = "Car",
Container = //the parent ID of the above example (one - many relation),
DeleteOnCascade = true, //Gets deleted if the parent gets deleted,
...
}
So here is how I modify the entity framework to be able to access json_value:
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Conventions.Add(new RegisterJsonValueFunctionConvention());
}
then
public static class JH
{
// Than define your function
[DbFunction("CodeFirstDatabaseSchema", "JSON_VALUE")]
public static string JsonValue(string expression, string path)
{
throw new NotSupportedException();
}
}
then
public class RegisterJsonValueFunctionConvention : IStoreModelConvention<EdmModel>
{
public void Apply(EdmModel item, DbModel model)
{
var expressionParameter = FunctionParameter.Create("expression", GetStorePrimitiveType(model, PrimitiveTypeKind.String), ParameterMode.In);
var pathParameter = FunctionParameter.Create("path", GetStorePrimitiveType(model, PrimitiveTypeKind.String), ParameterMode.In);
var returnValue = FunctionParameter.Create("result", GetStorePrimitiveType(model, PrimitiveTypeKind.String), ParameterMode.ReturnValue);
CreateAndAddFunction(item, "JSON_VALUE", new[] { expressionParameter, pathParameter }, new[] { returnValue });
}
protected EdmFunction CreateAndAddFunction(EdmModel item, string name, IList<FunctionParameter> parameters, IList<FunctionParameter> returnValues)
{
var payload = new EdmFunctionPayload { StoreFunctionName = name, Parameters = parameters, ReturnParameters = returnValues, Schema = GetDefaultSchema(item), IsBuiltIn = true };
var function = EdmFunction.Create(name, GetDefaultNamespace(item), item.DataSpace, payload, null);
item.AddItem(function);
return function;
}
protected EdmType GetStorePrimitiveType(DbModel model, PrimitiveTypeKind typeKind)
{
return model.ProviderManifest.GetStoreType(TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(typeKind))).EdmType;
}
protected string GetDefaultNamespace(EdmModel layerModel)
{
return layerModel.GlobalItems.OfType<EdmType>().Select(t => t.NamespaceName).Distinct().Single();
}
protected string GetDefaultSchema(EdmModel layerModel)
{
return layerModel.Container.EntitySets.Select(s => s.Schema).Distinct().SingleOrDefault();
}
}
Finally this is how I access the value:
var x = db.DynamicData
.Where(m => JH.JsonValue(m.Content, "$.name").Equals("John"))
.Select(m => JH.JsonValue(m.Content, "$.age"))
.FirstOrDefault();
I wonder if there is a convenient way to access the cars with the Linq query?
Right now I have to get the x value first, then look for the cars which has the Container value as the x.ID, get the list, then append to the cars:[] which really takes time and memory to process it.
So what do you think guys? Is there a convenient way to achieve this in a simpler way?