Skip to content

.NET Core Entity Framework Core

🏷️ .NET Core Entity Framework

1. 安装 Entity Framework Core

bash
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
bash
Install-Package Microsoft.EntityFrameworkCore.Tools

2. 根据数据库生成模型及 DbContext

bash
Scaffold-DbContext "Server=192.168.0.1;Database=SampleDB;uid=sa;pwd=123456;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models

默认上面的数据库连接字符串会被保存在 SampleDBContextOnConfiguring 方法中。
生成的模型会保存在 OutputDir 属性中设置的目录中,命名空间同目录结构一致。
生成的类名会去除特殊符号,所以数据表命名时不要出现去除特殊符号后名字一样的情况,否则后生成的模型会覆盖前面的模型,另外如果只是大小写不一样,还可能会导致文件名和类名不一致。所以应该在创建表时避免出现这种情况。

3. 配置数据库链接

  1. appsettings.json 中配置 ConnectionStrings

    json
    {
        "ConnectionStrings": {
            "Sample": "Server=192.168.0.1;Database=SampleDB;uid=sa;pwd=123456;"
        }
    }
  2. 新增 SampleDBContext 的构造函数

    cs
    public partial class SampleDBContext : DbContext
    {
        public OctopusB2bContext(DbContextOptions<SampleDBContext> options) : base(options)
        {
        }
    }
  3. Startup.cs 中注册该数据库连接

    cs
    public void ConfigureServices(IServiceCollection services)
    {
        // 注册数据库
        services.AddDbContext<SampleDBContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("Sample")));
    
        services.AddMvc();
    
        // ...
    }

4. 在 Controller 中使用该 DBContext

cs
[Produces("application/json")]
[Route("api/User")]
public class UserController : Controller
{
    private readonly SampleDBContext _context;

    public UserController(SampleDBContext context)
    {
        _context = context;
    }

    // GET: api/User/5
    [HttpGet("{id}")]
    public async Task<IActionResult> GetTSysUser([FromRoute] Guid id)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var tSysUser = await _context.TSysUser.SingleOrDefaultAsync(m => m.Guid == id);

        if (tSysUser == null)
        {
            return NotFound();
        }

        return Ok(tSysUser);
    }
}

6. 在 Service 中使用该 DBContext

  1. 创建 Service

    • interface

      cs
      public interface IAccountService
      {
          List<UserModel> GetUsers();
      }
    • 实现

      cs
      public class AccountService : IAccountService
      {
          private readonly SampleDBContext _context;
      
          public AccountService(SampleDBContext context)
          {
              _context = context;
          }
      
          public List<UserModel> GetUsers()
          {
              // 使用 Select 转型
              List<UserModel> uls1 = _context.TSysUser
                  .FromSql("SELECT * FROM T_SYS_User")
                  .Take(10)
                  .Select(u => new UserModel()
                  {
                      Guid = u.Guid,
                      Account = u.Account,
                      Name = u.Name,
                  }).ToList();
      
              //// 下面代码运行会出错
              //List<UserModel> uls2 = _context.UserLogin.FromSql("SELECT A.Guid, A.Account, A.Name, B.Guid BuyerStoreGuid, B.Name BuyerStoreName "
              //    + "FROM T_SYS_User A INNER JOIN T_CORP_BuyerStore B ON A.DepartmentGuid = B.Guid")
              //    .Take(10)
              //    .ToList();
      
              // 使用自定义的扩展方法实现查询结果直接返回需要的模型
              List<UserModel> uls3 = _context.ExecSQL<UserModel>("SELECT TOP 10 A.Guid, A.Account, A.Name, A.DepartmentGuid FROM T_SYS_User A WHERE AccountType = 4 AND DepartmentGuid IS NOT NULL");
      
              List<TCorpBuyerStore> buyerStores = _context.TCorpBuyerStore.Where(b => uls3.Select(u => u.DepartmentGuid).Contains(b.Guid.ToString())).ToList();
      
              foreach (var user in uls3)
              {
                  TCorpBuyerStore buyerStore = buyerStores.FirstOrDefault(b => b.Guid.ToString() == user.DepartmentGuid);
                  if (buyerStore != null)
                  {
                      user.BuyerStoreGuid = buyerStore.Guid;
                      user.BuyerStoreName = buyerStore.Name;
                  }
              }
      
              //List<UserModel> uls4 = _context.ExecSQL<UserModel>("SELECT TOP 10 A.Guid, A.Account, A.Name, B.Guid BuyerStoreGuid, B.Name BuyerStoreName "
              //    + "FROM T_SYS_User A INNER JOIN T_CORP_BuyerStore B ON  A.DepartmentGuid = CAST(B.Guid AS nvarchar(500))");
      
              return uls3;
          }
      }
  2. Startup.cs 中注册服务

    cs
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // 注册数据库
        services.AddDbContext<SampleDBContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("OCTOPUS3_0")));
    
        services.AddMvc();
    
        // 注册 Service
        services.AddScoped<IAccountService, AccountService>();
    }
  3. Controller 中调用服务

    cs
    [Produces("application/json")]
    [Route("api/Account")]
    public class AccountController : Controller
    {
        private readonly IAccountService _service;
    
        public AccountController(IAccountService service)
        {
            _service = service;
        }
    
        [HttpGet("")]
        public List<UserModel> GetUsers()
        {
            return _service.GetUsers();
        }
    }

  1. Entity Framework 中可以通过 _context.Database.SqlQuery<T>() 方法实现自定义的查询及返回自定义的模型,但在 Entity Framework Core 中,无法实现该功能(仅有 DbSet<T>.FromSql 方法)。

    cs
    var blogs = db.Database.SqlQuery<HomeCardModel>(sql, sqlParams.ToArray())
        .OrderByDescending(b => b.CreateTime)
        .ToList().ToPagedList(page, size);

    使用 DbSet<T>.FromSql 功能结合 IQueryable<T>.Select 方法可以实现部分效果,但局限比较大,只能在 SelectTResult 的字段都包含在 T 中时,才可以使用。

    cs
    // 使用 Select 转型
    List<UserModel> uls1 = _context.TSysUser
        .FromSql("SELECT * FROM T_SYS_User")
        .Take(10)
        .Select(u => new UserModel()
        {
            Guid = u.Guid,
            Account = u.Account,
            Name = u.Name,
        }).ToList();

    Raw store access APIs: Support for ad hoc mapping of arbitrary types 中给了一个暂时的替代方法。下面代码是在此基础上稍微修改后的版本,虽然可以实现该功能,但并不推荐使用。

    cs
    public static List<T> ExecSQL<T>(this SampleDBContext context, string query)
    {
        using (var command = context.Database.GetDbConnection().CreateCommand())
        {
            command.CommandText = query;
            command.CommandType = CommandType.Text;
    
            // 判断连接是否已打开
            if (context.Database.GetDbConnection().State == ConnectionState.Closed)
            {
                context.Database.OpenConnection();
            }
    
            using (var result = command.ExecuteReader())
            {
                List<T> list = new List<T>();
                T obj = default(T);
                while (result.Read())
                {
                    obj = Activator.CreateInstance<T>();
                    foreach (PropertyInfo prop in obj.GetType().GetProperties())
                    {
                        if (ReaderExists(result, prop.Name) && !object.Equals(result[prop.Name], DBNull.Value))
                        {
                            prop.SetValue(obj, result[prop.Name], null);
                        }
                    }
                    list.Add(obj);
                }
                return list;
    
            }
        }
    }
    
    private static bool ReaderExists(DbDataReader dr, string columnName)
    {
        dr.GetSchemaTable().DefaultView.RowFilter = "ColumnName= '" + columnName + "'";
    
        return (dr.GetSchemaTable().DefaultView.Count > 0);
    }
    cs
    // 使用自定义的扩展方法实现查询结果直接返回需要的模型
    List<UserModel> uls3 = _context.ExecSQL<UserModel>("SELECT TOP 10 A.Guid, A.Account, A.Name, A.DepartmentGuid FROM T_SYS_User A WHERE AccountType = 4 AND DepartmentGuid IS NOT NULL");
  2. 生成模型时报 PowerShell 版本过低错误

    The Entity Framework Core Package Manager Console Tools don't support PowerShell version 2.0. Upgrade to PowerShell version 3.0 or higher, restart Visual Studio, and try again.

    需要安装新版的 PowerShell,参照 Powershell 介绍和安装 安装即可。最新版 PowerShell 4.0 的 介绍下载地址

  3. 在一个有 Docker 支持的项目中使用 Scaffold-DbContext 命令生成模型时,发生了如下错误:

    powershell
    PM> Scaffold-DbContext "Server=192.168.0.1;Database=testdb;uid=sa;pwd=sa;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
    Scaffold-DbContext : 使用“1”个参数调用“Build”时发生异常:“The DOCKER_REGISTRY variable is not set. Defaulting to a blank string.
    The Compose file is invalid because:
    Service octopus.cloud has neither an image nor a build context specified. At least one must be provided.。
    如需更多故障排除信息,请转到 http://aka.ms/DockerToolsTroubleshooting”所在位置 行:1 字符:1
    + Scaffold-DbContext "Server=192.168.0.1;Database=testdb;uid=sa ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Scaffold-DbContext], MethodInvocationException
        + FullyQualifiedErrorId : ComMethodTargetInvocation,Scaffold-DbContext

    说是 DOCKER_REGISTRY 变量没有设置,但是这个项目是可以正常通过 Docker 启动的。

    对策

    由于不清楚是在哪里设置这个 DOCKER_REGISTRY 变量,所以使用了暂时卸载项目的方式。

    右键 docker-compose 项目,选择 卸载项目 ,然后再执行就 OK 了。