You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF6 Code First下实现数据导入:直接SQL与存储过程的最佳实践咨询

ASP.NET MVC5 + EF6 Code First 数据导入方案选择与存储过程最佳实践

问题背景

我正在使用ASP.NET MVC 5和EF6 Code First(刚接触EF与MVC 5),需要从远程SQL Server数据库导入数据。

首先,我将远程视图的数据复制到本地数据库的表中,在Action方法中使用以下代码(变量名为意大利语):

using (var source = new TimeWebDBContext())
{
 using (var target = new GESTPrefContext())
  {
   // 1 - Truncate table AnagraficaTimeWeb is exists
   target.Database.ExecuteSqlCommand("IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='AnagraficaTimeWeb') TRUNCATE TABLE AnagraficaTimeWeb");
    
   // 2 - Copy from remote view  to  AnagraficaTimeWeb table
   var dati_importati = from i in source.VW_PREFNO_ANAGRAFICORUOLO
        select new AnagraficaTimeWeb()
        {
         Matricola = i.MATRICOLA,
         Cognome = i.COGNOME,
         Nome = i.NOME,
         Sesso = i.SESSO,
         Email = i.EMAIL,
         IdRuolo = i.IDRUOLO,
         Ruolo = i.RUOLO,
         DataInizio = i.DATAINIZIO,
         DataFine = i.DATAFINE,
         DataFineRapporto = i.DATALICENZ,
         DataUltimaImportazione = DateTime.Now
        };
        target.DatiAnagraficaTimeWeb.AddRange(dati_importati.ToList());
        target.SaveChanges();
   }
}

该远程视图返回员工及其角色列表,角色需导入到本地独立表PROFILO中,员工数据保存到IMPIEGATO表。

导入流程剩余步骤为:

  • a)向PROFILO表插入新数据(忽略已保存的数据)
  • b)更新本地IMPIEGATO表中已存在的员工数据(覆盖姓名、邮箱等信息)
  • c)插入IMPIEGATO表中尚未存在的新员工数据

由于刚接触EF6,考虑使用SQL代码实现,目前有两种方案:

方案1:Action方法中直接执行SQL

已编写对应步骤的SQL代码:

步骤a代码

StringBuilder sql = new StringBuilder();
sql.AppendLine("INSERT INTO PROFILO (IdTimeWeb, Descrizione, Ordinamento, Stato, Datainserimento)");
sql.AppendLine(" SELECT DISTINCT IdRuolo, Ruolo, 1," + ((int)EnumStato.Abilitato) + ",'" + DateTime.Now.ToShortDateString()+"'");
sql.AppendLine(" FROM AnagraficaTimeWeb i");
sql.AppendLine(" WHERE NOT EXISTS");
sql.AppendLine("(SELECT 1 FROM PROFILO p WHERE p.Descrizione = i.Ruolo)");
target.Database.ExecuteSqlCommand(sql.ToString());

步骤b代码

sql.Clear();
sql.Append("UPDATE i " + Environment.NewLine);
sql.Append(" SET i.Cognome = a.Cognome" + Environment.NewLine);
sql.Append(" , i.Nome = a.Nome" + Environment.NewLine);
sql.Append(" , i.Sesso = a.Sesso" + Environment.NewLine);
sql.Append(" ,i.Email = a.Email" + Environment.NewLine);
sql.Append(" ,i.DataModifica = '" + DateTime.Now.ToShortDateString() +"'"+ Environment.NewLine);
sql.Append(" FROM Impiegato i " + Environment.NewLine);
sql.Append(" JOIN AnagraficaTimeWeb a on i.Matricola=a.Matricola " + Environment.NewLine);
sql.Append(" WHERE i.Stato =" + ((int)EnumStato.Abilitato)  + Environment.NewLine);
target.Database.ExecuteSqlCommand(sql.ToString());

步骤c代码

sql.Clear();
sql.Append("INSERT INTO IMPIEGATO(Cognome, Nome, Matricola, Sesso, Email, Stato, DataInserimento) " + Environment.NewLine);
sql.Append("SELECT a.Cognome" + Environment.NewLine);
sql.Append(", a.Nome" + Environment.NewLine);
sql.Append(", a.Matricola" + Environment.NewLine);
sql.Append(", a.Sesso" + Environment.NewLine);
sql.Append(", a.Email" + Environment.NewLine);
sql.Append("," + ((int)EnumStato.Abilitato )+ Environment.NewLine);
sql.Append(",'"+ DateTime.Now.ToShortDateString() +"'" + Environment.NewLine);
sql.Append(" FROM AnagraficaTimeWeb a " + Environment.NewLine);
sql.Append(" LEFT OUTER JOIN IMPIEGATO on a.Matricola = Impiegato.Matricola " + Environment.NewLine);
sql.Append("  WHERE Impiegato.Matricola is null" + Environment.NewLine);

target.Database.ExecuteSqlCommand(sql.ToString());

方案2:创建存储过程并在Action中调用

不清楚存储过程的创建方式:

  • 2.a)在迁移的Up方法中创建?
  • 2.b)还是直接在数据库中执行存储过程创建脚本,之后在Action方法中调用?

想咨询哪种方案更符合EF6 Code First的最佳实践,以及存储过程的正确创建方式。


方案对比与最佳实践建议

1. 方案选择:存储过程更贴合Code First最佳实践

直接在Action中拼接SQL字符串虽然能快速实现功能,但存在明显弊端:

  • 维护性差:SQL逻辑分散在C#代码中,后续修改或调试需要跨语言查找;
  • 可读性低:复杂SQL拼接后结构混乱,容易出现语法错误;
  • 风险隐患:直接拼接日期、枚举值等存在SQL注入风险(即使当前场景参数可控,也不符合安全规范);
  • 版本控制缺失:SQL逻辑无法和代码一起纳入版本管理,数据库变更难以追踪。

使用存储过程则能解决这些问题:

  • 数据操作逻辑封装在数据库层,C#代码仅负责调用,职责划分更清晰;
  • 存储过程可单独调试、优化,性能更可控;
  • 结合EF迁移管理存储过程的创建/更新,能保证数据库结构与代码版本一致。

2. 存储过程的正确创建方式(Code First模式)

必须通过EF迁移的Up方法创建存储过程,而非直接在数据库中手动执行脚本,原因如下:

  • Code First的核心是通过代码控制数据库结构,手动修改数据库会导致代码与数据库状态不一致;
  • 迁移脚本可纳入版本控制,团队协作时能保证所有人的数据库结构同步;
  • 后续修改存储过程时,只需新增迁移并更新Up/Down方法,即可实现平滑升级和回滚。

具体实现步骤:

  1. 生成空迁移:在Package Manager Console中执行命令:
Add-Migration CreateImportDataProcedures
  1. 在迁移的Up方法中编写存储过程创建脚本:
public override void Up()
{
    // 创建导入PROFILO的存储过程
    Sql(@"
        CREATE PROCEDURE dbo.ImportProfilo
        AS
        BEGIN
            INSERT INTO PROFILO (IdTimeWeb, Descrizione, Ordinamento, Stato, Datainserimento)
            SELECT DISTINCT IdRuolo, Ruolo, 1, @StatoAbilitato, GETDATE()
            FROM AnagraficaTimeWeb i
            WHERE NOT EXISTS (SELECT 1 FROM PROFILO p WHERE p.Descrizione = i.Ruolo)
        END
    ", new SqlParameter("@StatoAbilitato", (int)EnumStato.Abilitato));

    // 创建更新Impiegato的存储过程
    Sql(@"
        CREATE PROCEDURE dbo.UpdateImpiegato
        AS
        BEGIN
            UPDATE i 
            SET i.Cognome = a.Cognome
                ,i.Nome = a.Nome
                ,i.Sesso = a.Sesso
                ,i.Email = a.Email
                ,i.DataModifica = GETDATE()
            FROM Impiegato i 
            JOIN AnagraficaTimeWeb a on i.Matricola=a.Matricola 
            WHERE i.Stato = @StatoAbilitato
        END
    ", new SqlParameter("@StatoAbilitato", (int)EnumStato.Abilitato));

    // 创建插入Impiegato的存储过程
    Sql(@"
        CREATE PROCEDURE dbo.InsertNewImpiegato
        AS
        BEGIN
            INSERT INTO IMPIEGATO(Cognome, Nome, Matricola, Sesso, Email, Stato, DataInserimento) 
            SELECT a.Cognome
                ,a.Nome
                ,a.Matricola
                ,a.Sesso
                ,a.Email
                ,@StatoAbilitato
                ,GETDATE()
            FROM AnagraficaTimeWeb a 
            LEFT OUTER JOIN IMPIEGATO on a.Matricola = Impiegato.Matricola 
            WHERE Impiegato.Matricola is null
        END
    ", new SqlParameter("@StatoAbilitato", (int)EnumStato.Abilitato));
}
  1. 在Down方法中添加删除存储过程的逻辑(用于回滚):
public override void Down()
{
    Sql("DROP PROCEDURE dbo.ImportProfilo");
    Sql("DROP PROCEDURE dbo.UpdateImpiegato");
    Sql("DROP PROCEDURE dbo.InsertNewImpiegato");
}
  1. 执行迁移:在Package Manager Console中执行:
Update-Database
  1. 在Action方法中调用存储过程:
using (var target = new GESTPrefContext())
{
    // 调用存储过程
    target.Database.ExecuteSqlCommand("EXEC dbo.ImportProfilo");
    target.Database.ExecuteSqlCommand("EXEC dbo.UpdateImpiegato");
    target.Database.ExecuteSqlCommand("EXEC dbo.InsertNewImpiegato");
}

额外优化建议:

  • 把三个存储过程合并为一个ImportAllData存储过程,减少数据库调用次数;
  • 使用GETDATE()代替C#中的DateTime.Now,避免客户端与数据库时间不一致的问题;
  • 存储过程中始终使用参数化查询,保持代码规范。

内容的提问来源于stack exchange,提问作者Liuc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 00:57:02