C# 在sql查询期间获取Id并用于另一个Insert语句

C# 在sql查询期间获取Id并用于另一个Insert语句,c#,.net,sql-server,transactions,dapper,C#,.net,Sql Server,Transactions,Dapper,所以我一直在创建一个库,它使用dapper并允许用户操作数据库 我需要一些帮助来找到实现以下目标的最佳方法 假设我有一个“订单”表,一个“交易”表和一个“订单行”表。 我希望在插入时获取表“order”的增量Id,并使用它将其存储在“transaction”和“order_line”表中的一列中,我希望所有这些都在SQL事务中完成,以便在出现任何问题时可以回滚 现在,由于我的库对任何类型和操作都是动态的,所以我不确定如何处理类似的事情 以下是有关如何插入的代码: 我有两个全局变量 priva

所以我一直在创建一个库,它使用dapper并允许用户操作数据库

我需要一些帮助来找到实现以下目标的最佳方法

假设我有一个“订单”表,一个“交易”表和一个“订单行”表。 我希望在插入时获取表“order”的增量Id,并使用它将其存储在“transaction”和“order_line”表中的一列中,我希望所有这些都在SQL事务中完成,以便在出现任何问题时可以回滚

现在,由于我的库对任何类型和操作都是动态的,所以我不确定如何处理类似的事情

以下是有关如何插入的代码: 我有两个全局变量

  private string connectionString { get; set; }

        public void newConnection(string connection)
        {
            if (string.IsNullOrWhiteSpace(connectionString))
            {
                connectionString = connection;
            }
        }

        private List<KeyValuePair<string, object>> transactions = new List<KeyValuePair<string, object>>();
私有字符串连接字符串{get;set;}
公共连接(字符串连接)
{
if(string.IsNullOrWhiteSpace(connectionString))
{
connectionString=连接;
}
}
私有列表事务=新列表();
下面是如何调用以将类保存到数据库中:

public void Add(object item)
{
    string propertyNames = "";
    string propertyParamaters = "";
    Type itemType = item.GetType();

    System.Reflection.PropertyInfo[] properties = itemType.GetProperties();

    for (int I = 0; I < properties.Count(); I++)
    {
        if (properties[I].Name.Equals("Id", StringComparison.CurrentCultureIgnoreCase) || properties[I].Name.Equals("AutoId", StringComparison.CurrentCultureIgnoreCase))
        {
            continue;
        }

        if (I == properties.Count() - 1)
        {
            propertyNames += "[" + properties[I].Name + "]";
            propertyParamaters += "@" + properties[I].Name;
        }
        else
        {
            propertyNames += "[" + properties[I].Name + "],";
            propertyParamaters += "@" + properties[I].Name + ",";
        }
    }

    string itemName = itemType.Name;
    KeyValuePair<string, object> command = new KeyValuePair<string, object>($"Insert Into[{ itemName}] ({ propertyNames}) Values({ propertyParamaters})", item);
    transactions.Add(command);
}
公共作废添加(对象项)
{
字符串propertyNames=“”;
字符串PropertyParameters=“”;
Type itemType=item.GetType();
System.Reflection.PropertyInfo[]properties=itemType.GetProperties();
对于(int I=0;I
有更多的方法,如编辑、删除、编辑列表、删除列表等,但在这种情况下不相关

当您要将更改提交到您调用的数据库时:

public void SaveChanges()
        {
            using (SqlConnection sqlConnection = new SqlConnection(connectionString))
            {
                sqlConnection.Open();
                using (SqlTransaction sqlTransaction = sqlConnection.BeginTransaction())
                {
                    try
                    {
                        foreach (KeyValuePair<string, object> command in transactions)
                        {
                            sqlConnection.Execute(command.Key, command.Value, sqlTransaction);
                        }

                        sqlTransaction.Commit();
                    }
                    catch
                    {
                        sqlTransaction.Rollback();
                        throw;
                    }
                    finally
                    {
                        sqlConnection.Close();
                        transactions.Clear();
                    }
                }
                sqlConnection.Close();
            }

            transactions.Clear();
        }
public void SaveChanges()
{
使用(SqlConnection SqlConnection=newsqlconnection(connectionString))
{
sqlConnection.Open();
使用(SqlTransaction SqlTransaction=sqlConnection.BeginTransaction())
{
尝试
{
foreach(事务中的KeyValuePair命令)
{
sqlConnection.Execute(command.Key、command.Value、sqlTransaction);
}
提交();
}
抓住
{
sqlTransaction.Rollback();
投掷;
}
最后
{
sqlConnection.Close();
transactions.Clear();
}
}
sqlConnection.Close();
}
transactions.Clear();
}
您可以在github.com找到我的图书馆

它没有说明您正在使用哪个数据库。如果是MSSQL,您可以这样做

var id =  connection.Query<int?>("SELECT @@IDENTITY").SingleOrDefault();
var id=connection.Query(“选择@@IDENTITY”).SingleOrDefault();

在执行Insert之后。这将为您提供最后一次插入的id。

可以完成吗。。。对我们应该试着自己去做吗。。。我不会:)但我们无论如何都要试试

可以使此代码更简单的一些想法:

  • 定义助手接口并强制数据类实现它们,或使用属性声明指定id字段和外键引用
  • 研究注入或代码生成技术,以便可以在编译时而不是运行时执行某些“动态”编码和查找
我不使用Dapper,您的SqlConnection.Execute()是我不熟悉的扩展方法,但我假设它从传入的对象生成DbParameters,并在执行SqlCommand时将其应用于SqlCommand。希望dapper有一些函数来提取参数,以便在这个代码示例中使用它们,或者您可以使用其中的一些概念并使它们适应您的dapper代码。我只想承认这一点,我在这里省略了在执行命令时参数化对象的任何代码示例

以下片段将讲述这段旅程

  • 准备生成的SQL以捕获Id字段
  • 保存更改时输出Id值
  • 迭代数组中的所有剩余对象并设置外键值
  • 注意:这些代码更改并没有针对生产进行测试或异常处理,我也不会称之为“最佳实践”,它只是为了证明这个概念并帮助其他程序员:)

  • 您已经为Id字段跟踪设计了一个约定,让我们通过准备sql语句来设置输出参数的值来扩展这个想法:

    注意:在MS SQL中,请优先使用SCOPE_IDENTITY()而不是@@IDENTITY。

    注意:因为生成的语句正在使用参数,并且我们还没有读取参数值,所以在找到要插入到其他对象的Id值之后,我们将不需要重新生成保存的SQL语句。。。呸

    您需要发展一些相当高级的逻辑来处理所有可能的链接组合

    一些orm通过将值设置为负数,并减少每种类型的数字来实现这一点。一种新类型被添加到命令co中
    public void Add(object item)
    {
        List<string> propertyNames = new List<string>();
        Type itemType = item.GetType();
    
        System.Reflection.PropertyInfo[] properties = itemType.GetProperties();
    
        for (int I = 0; I < properties.Count(); I++)
        {
            if (properties[I].Name.Equals("Id", StringComparison.CurrentCultureIgnoreCase) || properties[I].Name.Equals("AutoId", StringComparison.CurrentCultureIgnoreCase))
            {
                continue;
            }
            propertyNames.Add(properties[I].Name);
        }
    
        string itemName = itemType.Name;
        KeyValuePair<string, object> command = new KeyValuePair<string, object>
            ($"Insert Into[{itemName}] ({String.Join(",", propertyNames.Select(p => $"[{p}]"))}) Values({String.Join(",", propertyNames.Select(p => $"@{p}"))}); SET @OutId = SCOPE_IDENTITY();", item);
        transactions.Add(command);
        // Simply append your statement with a set command on an @id parameter we will add in SaveChanges()
    }
    
    public void SaveChanges()
    {
        using (SqlConnection sqlConnection = new SqlConnection(connectionString))
        {
            sqlConnection.Open();
            using (SqlTransaction sqlTransaction = sqlConnection.BeginTransaction())
            {
                try
                {
                    for (int i = 0; i < transactions.Count; i++)
                    {
                        KeyValuePair<string, object> command = transactions[i];
                        // 1. Execute the command, but use an output parameter to capture the generated id
                        var cmd = sqlConnection.CreateCommand();
                        cmd.Transaction = sqlTransaction;
                        cmd.CommandText = command.Key;
                        SqlParameter p = new SqlParameter()
                        {
                            ParameterName = "@OutId",
                            Size = 4,
                            Direction = ParameterDirection.Output
                        };
                        cmd.Parameters.Add(p);
                        cmd.ExecuteNonQuery();
    
                        // Check if the value was set, non insert operations wil not set this parameter
                        // Could optimise by not preparing for the parameter at all if this is not an 
                        // insert operation.
                        if (p.Value != DBNull.Value)
                        {
                            int idOut = (int)p.Value;
    
                            // 2. Stuff the value of Id back into the Id field.
                            string foreignKeyName = null;
                            SetIdValue(command.Value, idOut, out foreignKeyName);
                            // 3. Update foreign keys, but only in commands that we haven't execcuted yet
                            UpdateForeignKeys(foreignKeyName, idOut, transactions.Skip(i + 1));
                        }
                    }
    
                    sqlTransaction.Commit();
                }
                catch
                {
                    sqlTransaction.Rollback();
                    throw;
                }
                finally
                {
                    sqlConnection.Close();
                    transactions.Clear();
                }
            }
            sqlConnection.Close();
        }
    
        transactions.Clear();
    }
    
    
    /// <summary>
    /// Update the Id field of the specified object with the provided value
    /// </summary>
    /// <param name="item">Object that we want to set the Id for</param>
    /// <param name="idValue">Value of the Id that we want to push into the item</param>
    /// <param name="foreignKeyName">Name of the expected foreign key fields</param>
    private void SetIdValue(object item, int idValue, out string foreignKeyName)
    {
        // NOTE: There are better ways of doing this, including using interfaces to define the key field expectations.
        // This logic is consistant with existing code so that you are familiar with the concepts
        Type itemType = item.GetType();
        foreignKeyName = null;
    
        System.Reflection.PropertyInfo[] properties = itemType.GetProperties();
    
        for (int I = 0; I < properties.Count(); I++)
        {
            if (properties[I].Name.Equals("Id", StringComparison.CurrentCultureIgnoreCase) || properties[I].Name.Equals("AutoId", StringComparison.CurrentCultureIgnoreCase))
            {
                properties[I].SetValue(item, idValue);
                foreignKeyName = $"{item.GetType().Name}_{properties[I].Name}";
                break;
            }
        }
    }
    
        private void UpdateForeignKeys(string foreignKeyName, int idValue, IEnumerable<KeyValuePair<string, object>> commands)
        {
            foreach(var command in commands)
            {
                Type itemType = command.Value.GetType();
                var keyProp = itemType.GetProperty(foreignKeyName);
                if(keyProp != null)
                {
                    keyProp.SetValue(command.Value, idValue);
                }
            }
        }
    
    public class Manifest
    {
        ...
        Driver_UserId { get; set; }
        Sender_UserId { get; set; }
        Receiver_UserId { get; set; }
        ...
    }