存储过程与EF6 Code First – 最佳实践?

huangapple go评论62阅读模式
英文:

Stored procedure with EF6 Code first - best practice?

问题

在我的程序中,我必须从远程 SQL Server 数据库导入数据。
我正在使用 ASP.NET MVC 5 和 EF6 Code First(我对 EF 和 MVC 5 还很新)。

首先,我将数据从远程视图复制到本地数据库中的表中。
对于这部分,我在操作方法中使用以下代码(名称为意大利语):

using (var source = new TimeWebDBContext())
{
    using (var target = new GESTPrefContext())
    {
        // 1 - 如果存在则截断 AnagraficaTimeWeb 表
        target.Database.ExecuteSqlCommand("IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='AnagraficaTimeWeb') TRUNCATE TABLE AnagraficaTimeWeb");

        // 2 - 从远程视图复制到 AnagraficaTimeWeb 表
        var importedData = 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(importedData.ToList());
        target.SaveChanges();
    }
}

视图返回一个带有角色的员工列表。
角色必须导入到名为 PROFILO 的本地表中,而员工数据保存在 IMPIEGATO 表中。

导入过程的剩余部分包括:
a) 在 PROFILO 表中插入新数据(已保存的数据将被忽略)
b) 更新已经存在于本地 IMPIEGATO 表中的员工数据(姓名、电子邮件等将被覆盖)
c) 插入尚未在 IMPIEGATO 表中的新员工。

由于我对 EF6 还很陌生,我想使用 SQL 代码。
我认为可能的解决方案是:

  1. 在操作方法中直接执行 SQL 代码,使用 db.Database.ExecuteSqlCommand。以下是我编写的代码:

用于 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());
  1. 创建一个存储过程,在操作方法中调用它。在这种情况下,如何创建存储过程?

2.a) 在迁移的 Up 方法中创建?

2.b) 或者在数据库中直接运行存储过程创建脚本(在首次创建后),然后从操作方法中运行存储过程?

英文:

In my program I have to import data from a remote sqlserver database.
I am using ASP.NET MVC 5 and EF6 Code First (I'm new with EF and MVC 5).

First, I copy data from a remote view to a table in the local database.
For this part I use this code in the action method (names are in italian):

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();
   }
}

The view returns a list of employees with their role.
Roles have to be imported in a distinct local table called PROFILO, while employees data are saved in the IMPIEGATO table.

The remaining part of the import process consists of :
a) insert new data in the PROFILO table (data already saved are ignored)
b) update of employee data already present in the local IMPIEGATO table (name, email,etc. are overwritten)
c) insert new empoyee not yet in the IMPIEGATO table.

since I’m new to EF6 I thought I’d use SQL code .
In my opinion the possible solutions are :

  1. execute SQL code directly in the actionmethod with db.Database.ExecuteSqlCommand
    This is the code i write:

code for point 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());

code for point 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());

code for point 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());		
  1. create a storedprocedure to call in the actionmetghod. in this case how to create the storedprocedure?

2.a) in the Up method of a migration ?

2.b) or running the storedprocedure creation script directly in the database (after it was first created ) and then run the stored from the action method?

答案1

得分: 1

你可以直接在数据库中创建存储过程,并在一个存储过程中编写剩余的三个部分(点a、b、c)。存储过程(SP)以对象文件的形式存储在SQL中,因此执行速度很快,SQL服务器不需要花时间制定执行计划和其他额外的事情。

要创建存储过程,您需要更熟悉SQL语句及其控制结构等。这会感觉像学习一门新语言。您可以从以下链接开始:

https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/stored-procedures-database-engine?view=sql-server-ver16

要将该存储过程保存为迁移脚本,您可以创建一个空白迁移,使用以下命令:

add-migration 'SPName' -IgnoreChanges

然后添加您的存储过程:

public partial class SPName: DbMigration
{
    public override void Up()
    {
        Sql(@"Create Stored Procedure script");
    }

    public override void Down()
    {
        Sql(@"Drop Stored Procedure script");
    }
}

不要忘记每次修改/更改存储过程时更改迁移脚本。您还可以创建修改后存储过程的迁移脚本。

要执行您的存储过程,您可以参考以下代码片段。SqlParameter可帮助您编写干净的查询,以避免SQL注入。

List<SqlParameter> sqlParms = new List<SqlParameter>
{
    new SqlParameter { ParameterName = "@Id", Value = employee.EmployeeID },
    new SqlParameter { ParameterName = "@FirstName ", Value = employee.FirstName },
    new SqlParameter { ParameterName = "@LastName", Value = employee.LastName }
};
db.Database.ExecuteSqlRaw("EXEC dbo.spName @Id, @FirstName, @LastName", sqlParms.ToArray());
英文:

You can create stored procedure directly in db and write all three remaining part of your process (point a,b,c) in one SP (stored procedure). SP is stored as object file in sql so it's fast and sql server don't spend time in making execution plan and other extra things.

In order to create sp, you need to more familiar with sql statements and it's control structures etc. It will feel like learning new language. You can start with below link

https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/stored-procedures-database-engine?view=sql-server-ver16

To save that stored procedure as migration script you can create a blank migration using

add-migration &#39;SPName&#39; -IgnoreChanges

Then add your sp

public partial class SPName: DbMigration
{
    public override void Up()
    {
        Sql(@&quot;Create Stored Procedure script&quot;);

    }

    public override void Down()
    {
        Sql(@&quot;Drop Stored Procedure script&quot;)
    }
} 

don't forget to change migration script every time you modify/alter sp. You can also make migration script of modified sp.

To execute your sp you can refer the below code snippet. SqlPatameter helps you to make query clean,you can avoid sql injection.

List&lt;SqlParameter&gt; sqlParms = new List&lt;SqlParameter&gt;
  {
    new SqlParameter { ParameterName = &quot;@Id&quot;, Value = employee.EmployeeID },
    new SqlParameter { ParameterName = &quot;@FirstName &quot;, Value = employee.FirstName },
    new SqlParameter { ParameterName = &quot;@LastName&quot;, Value = employee.LastName}
};
db.Database.ExecuteSqlRaw(&quot;EXEC dbo.spName @Id, @FirstName, @LastName&quot; sqlParms.ToArray());

答案2

得分: 0

在使用 EF 和 Code First Migrations 时,如果这段代码仅在升级或初始数据库迁移过程的一部分中需要,并且不再需要,则应将此逻辑限制在关联迁移的 Up 方法中。

对于一次性或不经常执行的操作,定义和执行存储过程应该需要非常类似的工作量,但现在你在数据库中引入了一个存储过程的构件,如果它从未执行过,将会使未来的数据库管理员感到困惑。以这种方式封装它几乎没有价值。

  • 如果你确实使用了存储过程,那么存储过程的创建脚本也应该通过 Up 方法执行,因此,如果 SQL 在任何情况下都会以一种形式定义在其中,那么在大多数情况下,我们不会从将此逻辑封装在存储过程中获得任何额外的好处。

如果你打算在迁移中编写升级逻辑(我强烈鼓励你这样做),那么你还应该考虑在 Down 方法中实现反向逻辑,或者简单地在 Down 方法中加入 Truncate 语句。

public override void Up()
{
    // ...生成的迁移操作...

    // 手动迁移逻辑:
    Sql($@"
    INSERT INTO PROFILO (IdTimeWeb, Descrizione, Ordinamento, Stato, Datainserimento)
    SELECT DISTINCT IdRuolo, Ruolo, 1,{(int)EnumStato.Abilitato},'{DateTime.Now.ToShortDateString()}'
    FROM AnagraficaTimeWeb i
    WHERE NOT EXISTS 
    (SELECT 1 FROM PROFILO p WHERE p.Descrizione = i.Ruolo);
    ");

    Sql($@"
    UPDATE i 
     SET i.Cognome = a.Cognome
     , i.Nome = a.Nome
     , i.Sesso = a.Sesso
     ,i.Email = a.Email
     ,i.DataModifica = '{DateTime.Now.ToShortDateString()}'
     FROM Impiegato i 
     JOIN AnagraficaTimeWeb a on i.Matricola=a.Matricola 
     WHERE i.Stato ={(int)EnumStato.Abilitato})
     ");

    Sql($@"
    INSERT INTO IMPIEGATO(Cognome, Nome, Matricola, Sesso, Email, Stato, DataInserimento)
    SELECT a.Cognome
    , a.Nome
    , a.Matricola
    , a.Sesso
    , a.Email
    , {(int)EnumStato.Abilitato}
    , '{DateTime.Now.ToShortDateString()}'
     FROM AnagraficaTimeWeb a 
     LEFT OUTER JOIN IMPIEGATO on a.Matricola = Impiegato.Matricola
      WHERE Impiegato.Matricola is null
     ");
}

public override void Down()
{
    // TODO: 降级逻辑
}

如果脚本需要在应用程序的生命周期中多次执行,因此与特定数据模式版本无关,那么存储过程开始变得更有意义,但相同的简单脚本也可以封装为 C# 编写的实用工具。如果你选择通过代码来管理模式,那么这也是有道理的。

英文:

In EF with Code First Migrations, if this is one-time code that is only needed as part of the upgrade or initial database migration process and will not be needed again then you should constrain this logic to the Up method of the associated migration.

For once off or infrequent execution, defining and executing a stored procedure should result in a very similar amount of effort, but now you have introduced an artifact that in the database, the stored procedure, that will confound future DBAs if it is never executed. There is little value in packaging it in this way.

  • If you did use a stored procedure, then the create script for the SP should also be executed via the Up method, so if the SQL is going to be defined in there anyway in one form or another, In most circumstances we do not get any additional benefit from encapsulating this logic in an SP.

If you are going to script upgrade logic like this in the migration (and I heartily encourage you to do so) then you should also considering implementation of the reverse logic in the Down method. Or simply put a Truncate statement in the Down.

public override void Up()
{
    // ...Generated Migration Operations...

    // Manual Migration Logic:
    Sql($@&quot;
    INSERT INTO PROFILO (IdTimeWeb, Descrizione, Ordinamento, Stato, Datainserimento)
    SELECT DISTINCT IdRuolo, Ruolo, 1,{(int)EnumStato.Abilitato},&#39;{DateTime.Now.ToShortDateString()}&#39;
    FROM AnagraficaTimeWeb i
    WHERE NOT EXISTS 
    (SELECT 1 FROM PROFILO p WHERE p.Descrizione = i.Ruolo);
    &quot;);

    Sql($@&quot;
    UPDATE i 
     SET i.Cognome = a.Cognome
     , i.Nome = a.Nome
     , i.Sesso = a.Sesso
     ,i.Email = a.Email
     ,i.DataModifica = &#39;{DateTime.Now.ToShortDateString()}&#39;
     FROM Impiegato i 
     JOIN AnagraficaTimeWeb a on i.Matricola=a.Matricola 
     WHERE i.Stato ={(int)EnumStato.Abilitato})
     &quot;);

    Sql($@&quot;
    INSERT INTO IMPIEGATO(Cognome, Nome, Matricola, Sesso, Email, Stato, DataInserimento)
    SELECT a.Cognome
    , a.Nome
    , a.Matricola
    , a.Sesso
    , a.Email
    , {(int)EnumStato.Abilitato}
    , &#39;{DateTime.Now.ToShortDateString()}&#39;
     FROM AnagraficaTimeWeb a 
     LEFT OUTER JOIN IMPIEGATO on a.Matricola = Impiegato.Matricola
      WHERE Impiegato.Matricola is null
     &quot;);
}

public override void Down()
{
    // TODO: Downgrade logic
}

If the script needs to be executed many times in the lifetime of the app, so it is not associated with a specific data schema version, then a stored procedure starts to make more sense but the same simple script could be encapsulated as a C# coded utility. If you are choosing to manage the schema through code then this makes sense too.

huangapple
  • 本文由 发表于 2023年6月1日 19:26:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76381369.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定