Suggested Improvement to handling Insert with Db-generated primary keys
还没有人认领这个 Issue。
评估
调研方向
首先查看现有的 SQL Server sync Insert 实现,以及此处描述的单实体和多实体 Insert 入口点。该 issue 未指定任何文件或测试;要完成此工作,需要一种经过协商的参数化方案,在受支持的情况下返回所列键类型和键名称对应的生成键。
由索引模型根据 Issue 内容生成。
描述
This addresses 3 issues for Insert:
- Use of GUID as a Db generated PK
- Handling a Pk field that is not named Id
- Returning the Pk values with the original insert object for Multi and Single Insert (GUID or Int and any name)
The suggested handling below is working examples but only for SQL Server Sync methods, though this can be extended to Async SQL Server for sure. Other's expertise would be needed for other supported databases.
The following is for single entity Insert statements that will include the updated PK in the return record, whether it is of type Int or Guid or if it is named other than Id (i.e. UserId rather than simply Id).
Ive left the return value = 1 if there is a GUID as the Pk but ideally it would change to dynamic and return the GUID if one is present. Returning OUTPUT null is there just to minimise other code changes.
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
{
string outClause = "";
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Length > 0)
{
outClause = $"OUTPUT INSERTED.{propertyInfos[0].Name} as autoId";
}
else
{
outClause = $"OUTPUT null as autoId";
}
var cmd = $"insert into {tableName} ({columnList}) {outClause} values ({parameterList})";
var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout);
var first = multi.Read().FirstOrDefault();
if (first == null || first.autoId == null) return 0;
if (propertyInfos.Length > 0)
{
propertyInfos[0].SetValue(entityToInsert, Convert.ChangeType(first.autoId, propertyInfos[0].PropertyType), null);
if (propertyInfos[0].PropertyType == typeof(int))
return (int)first.autoId;
}
return 1;
}
The suggestion below for multiple objects needs improvement, but I ran out of knowledge/understanding of the excellent Dapper codebase!
Also I am not entirely happy with the approach, but I think the principle is sound, at least for SQL Server:
- Instead of parameterised query, create an insert query with literal values and execute as Query
- Use the OUTPUT clause to return the Pk values Inserted
- Push the values back into the original Pk fields of the returned object
The things that make me unhappy:
- Not using Parameters as you do throughout the rest of the codebase.
- Converting parameter values to literal values is done crudely here for proof of concept
- Using Select (work round) rather than ExecuteScalar (logically the correct call)
- This also un-caches the result as return values must be enumerated within the Insert command.
public static long Insert<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var isList = false;
var type = typeof(T);
if (type.IsArray)
{
isList = true;
type = type.GetElementType();
}
else if (type.IsGenericType)
{
var typeInfo = type.GetTypeInfo();
bool implementsGenericIEnumerableOrIsGenericIEnumerable =
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
if (implementsGenericIEnumerableOrIsGenericIEnumerable)
{
isList = true;
type = type.GetGenericArguments()[0];
}
}
var name = GetTableName(type);
var sbColumnList = new StringBuilder(null);
var sbKeyList = new StringBuilder(null);
var allProperties = TypePropertiesCache(type);
var keyProperties = KeyPropertiesCache(type);
var computedProperties = ComputedPropertiesCache(type);
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
var adapter = GetFormatter(connection);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336
if (i < allPropertiesExceptKeyAndComputed.Count - 1)
sbColumnList.Append(", ");
}
var sbParameterList = new StringBuilder(null);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
sbParameterList.AppendFormat("@{0}", property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count - 1)
sbParameterList.Append(", ");
}
if (keyProperties.Count > 0) //why would you have 2??
{
sbKeyList.Append("OUTPUT INSERTED.");
adapter.AppendColumnName(sbKeyList, keyProperties[0].Name);
sbKeyList.Append(" as autoId");
}
int returnVal;
var wasClosed = connection.State == ConnectionState.Closed;
if (wasClosed) connection.Open();
if (!isList) //single entity
{
returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
sbParameterList.ToString(), keyProperties, entityToInsert);
}
else
{
//Generate a list of literal Values rather than Parameters
string values = "VALUES ";
var toInsertArr = (entityToInsert as IEnumerable<object>).ToArray();
foreach (var item in toInsertArr)
{
string toAdd = "";
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
var property = allPropertiesExceptKeyAndComputed[i];
var val = property.GetValue(item);
//For sure there will be a better way to do this somewhere in Dapper!
toAdd += $"{quotedVal(val)}, ";
}
toAdd = $"\n({toAdd.TrimEnd(", ".ToCharArray())}),";
values += toAdd;
}
values = values.TrimEnd(',');
var cmd = $"insert into {name} ({sbColumnList}) {sbKeyList} {values}";
//insert list of entities and return Enumerable of key values
var output = connection.Query(cmd, null, transaction, false, commandTimeout, CommandType.Text);
var outputArr = output.ToArray();
returnVal = outputArr.Count();
if (returnVal == toInsertArr.Length)
{
for (int i = 0; i < outputArr.Count(); i++)
{
foreach (var key in keyProperties)
{
var keyVal = outputArr[i].autoId;
key.SetValue(toInsertArr[i], keyVal);
}
}
}
}
if (wasClosed) connection.Close();
return returnVal;
}
- 主要语言
- C#
- 星标
- 293
- 派生
- 108
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
DapperLib/Dapper.Contrib 的其他 Issue
-
难度 1/5 1 小时以内 新手友好度 75/100
DapperLib/Dapper.Contrib#22 · 2 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 42/100
DapperLib/Dapper.Contrib#174 ·
-
难度 2/5 1-3 小时 新手友好度 45/100
DapperLib/Dapper.Contrib#173 ·
-
难度 1/5 1 小时以内 新手友好度 10/100
DapperLib/Dapper.Contrib#172 · 1 条评论 · 4 个 reaction ·
-
难度 3/5 1-2 天 新手友好度 48/100
DapperLib/Dapper.Contrib#169 ·
查看 DapperLib/Dapper.Contrib 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 62/100
builtbybel/CrapFixer#112 ·
-
area-ai untriaged
难度 2/5 1-3 小时 新手友好度 85/100
dotnet/extensions#7790 ·
维护者通常 1 天内回复
-
Beginner Friendly T: Bugfix
难度 2/5 1-3 小时 新手友好度 82/100
space-wizards/space-station-14#46220 ·
维护者通常 1 天内回复
-
P2 testing
难度 1/5 1 小时以内 新手友好度 90/100
维护者通常 1 天内回复
-
area-Infrastructure-coreclr os-ios os-maccatalyst os-tvos untriaged
难度 2/5 1-3 小时 新手友好度 78/100
dotnet/runtime#134766 · 3 条评论 ·
维护者通常 1 天内回复