如何使用 ValueConversions 检索 Entity Framework Core 的数据库模型

How to retrieve a database model for Entity Framework Core with ValueConversions

我有一个 asp.net 核心网络 api 项目,我在其中使用 Entity Framework。我从 Web 获取 DTO 并将其转换(使用 AutoMapper)为数据库实体。

此时我可以在服务器上对实体进行一些 post 处理,然后再访问数据库。由于 Entity Framework 中的数据库结构和限制,我需要将此实体传递给存储过程。此时我想获得应用了转换的数据库模型。

基本上,这个流程...

控制器接受 DTO -> AutoMapper to Entity -> 允许我在保存之前处理对象和做事 -> 保存,但使用存储过程。

我的模型有一个转换,那么我如何在执行查询时获得我想要的数据库表示?

问题是我将 "false" 作为控制器中的参数,它在实体模型中被转换为布尔值,当我想要时它被转换为字符串 ("false")保存它,如何应用 entity framework 模型中定义的转换,以便我可以按预期保存 "Y" 或 "N"?

下面的简化示例...

为了澄清问题,我需要能够在调用存储过程之前获取模型的数据库表示,使用下面的代码,将调用 ToString,因此我将得到 "false"不是 "N"。在使用 ValueConversions(即数据库 -> 模型)检索数据时,我有一种方法可以做到这一点。如果我使用 SaveChanges,EF Core 会负责转换(模型 -> 数据库),但是当使用原始 SQL(在存储过程的情况下)时,我如何获取数据库表示。在这一点上,如果我的模型有一个布尔值 属性,我想将 "Y" 或 "N" 作为参数传递给原始 SQL ......这是否更清楚?

public class TodoDto
{
    [ReadOnly(true)]
    public long Id{ get; set; }

    public string Item { get; set; }

    public bool Done { get; set; }

    public DateTime? DateStamp { get; set; }
    // other properties, model is more complex, but removed to keep it simple
}

public class TodoEFCoreModel
{
    [Column("TodoId"), ReadOnly(true)]
    public long Id { get; set; }

    [Column("TodoItem")]
    public string Item { get; set; }

    public bool? Done { get; set; }

    public DateTime? DateStamp { get; set; }
    // other properties
}    

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    var yesNoConversion = new YesNoToBoolConverter();

    modelBuilder
        .Entity<TodoEFCoreModel>()
        .Property(x => x.Done)
        .HasConversion(yesNoConversion);
}

public ActionResult PostToDo(TodoDto todo)
{
    // code is then roughly
    var databaseTodoEntity = _mapper.Map<TodoDto, TodoEFCoreModel>(todo);

    // here I can check databaseTodoEntity boolean property
    // and/or manipulate the model

    // when it comes to saving I need to use a stored procedure, I can do this using ExecuteSqlCommandAsync...
    await _dbContext.Database.ExecuteSqlCommandAsync("begin CreateTodo(Item => :p0, Done => :p1, DateStamp => :p2); end;", parameters: new[]
        {
            new OracleParameter("p0", OracleDbType.VarChar2, databaseTodoEntity.Item, ParameterDirection.Input),

            // The problem is here, with this code I get "false", instead of the conversion that Entity Framework would apply if I were to be able to call "SaveChanges" on the db context...
            new OracleParameter("p1", OracleDbType.Varchar2, databaseTodoEntity.Done, ParameterDirection.Input),

            new OracleParameter("p2", OracleDbType.Date, databaseTodoEntity.DateStamp, ParameterDirection.Input)
        });
}

EF Core 内部使用 RelationalTypeMapping class 个实例,其中

Represents the mapping between a .NET type and a database type.

并且对于特定实体属性可以通过FindRelationalMapping扩展方法获得。

注意这个方法被认为是"internal"API的一部分,所以你需要

using Microsoft.EntityFrameworkCore.Internal;

并接受典型的警告

This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. This API may change or be removed in future releases.

现在,连同Converter and other useful properties, you'd also get an access to some useful methods like CreateParameter,可以直接在您的场景中使用。它将像 EF Core 生成的命令一样进行所有必要的转换和参数准备。

例如:

var sql = "begin CreateTodo(Item => :p0, Done => :p1, DateStamp => :p2); end;";
var entityType = _dbContext.Model.FindEntityType(typeof(TodoEFCoreModel));
var dbCommand = _dbContext.Database.GetDbConnection().CreateCommand();
object[] parameters =
{
    entityType.FindProperty("Item").FindRelationalMapping()
        .CreateParameter(dbCommand, "p0", databaseTodoEntity.Item),
    entityType.FindProperty("Done").FindRelationalMapping()
        .CreateParameter(dbCommand, "p1", databaseTodoEntity.Done),
    entityType.FindProperty("DateStamp").FindRelationalMapping()
        .CreateParameter(dbCommand, "p2", databaseTodoEntity.DateStamp),
};
await _dbContext.Database.ExecuteSqlCommandAsync(sql, parameters);

请注意,DbCommand 实例仅用作 DbParameter 工厂 (DbCommand.CreateParameter)。创建的参数不会添加到该命令,因此之后可以安全地丢弃它。