C# 如何避免使用Dapper.Contrib在后续更新调用中更新列?
我有一个类C# 如何避免使用Dapper.Contrib在后续更新调用中更新列?,c#,dapper,dapper-contrib,C#,Dapper,Dapper Contrib,我有一个类Media,它与我的DB表Media基本上是一对一的。我正在使用Dapper.Contrib的UpdateSqlConnection扩展方法更新表,方法是传入Media对象 表中的一列(以及相应的类属性)是OwnerID,只是它是第一个创建此媒体的用户的ID。它应该写在第一个insert上(这也是使用Dapper.Contrib的insertextension方法完成的),然后更新不应该改变它。有没有可能只用简洁的设计就可以做到这一点?如果可能的话,我不想在执行更新之前阅读OwnerI
Media
,它与我的DB表Media
基本上是一对一的。我正在使用Dapper.Contrib的Update
SqlConnection
扩展方法更新表,方法是传入Media
对象
表中的一列(以及相应的类属性)是OwnerID
,只是它是第一个创建此媒体的用户的ID。它应该写在第一个insert上(这也是使用Dapper.Contrib的insert
extension方法完成的),然后更新不应该改变它。有没有可能只用简洁的设计就可以做到这一点?如果可能的话,我不想在执行更新之前阅读OwnerID
列,只是为了确保OwnerID
对象属性相同
[Computed]
属性似乎在Update
和Insert
中都忽略了此列。描述似乎表明此属性只应在更新时忽略对列的写入,这给了我希望,我只是不正确地使用了库。不幸的是,衣冠楚楚,Contrib不能做你想做的事。要点是,根据扩展方法Insert
和Update
收集字段的机制完全相同,这将受到影响。基本上它看起来像:
// in Insert<T>
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
...
// in Update<T>
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
以下是标记属性的属性,这些属性不能用于更新查询:
/// <summary>
/// Specifies that this is a not updateable column.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class NotUpdateableAttribute : Attribute
{
}
希望能有所帮助。谢谢,我不会选择使用扩展方法,而是将此标记为答案,因为它解释了Dapper.Contrib无法实现的原因,并说明了如何通过编写自己的扩展方法来实现。
CREATE TABLE [Media]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[OwnerId] BIGINT NOT NULL,
[Name] VARCHAR(50) NOT NULL
)
/// <summary>
/// Specifies that this is a not updateable column.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class NotUpdateableAttribute : Attribute
{
}
/// <summary>
/// My extensions for Dapper
/// </summary>
public static class TestSqlMapperExtensions
{
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ExplicitKeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ComputedProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> UpdatableProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeTableName = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter();
private static readonly Dictionary<string, ISqlAdapter> AdapterDictionary
= new Dictionary<string, ISqlAdapter>
{
["sqlconnection"] = new SqlServerAdapter(),
["sqlceconnection"] = new SqlCeServerAdapter(),
["npgsqlconnection"] = new PostgresAdapter(),
["sqliteconnection"] = new SQLiteAdapter(),
["mysqlconnection"] = new MySqlAdapter(),
["fbconnection"] = new FbAdapter()
};
private static List<PropertyInfo> ComputedPropertiesCache(Type type)
{
if (ComputedProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pi))
{
return pi.ToList();
}
var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
ComputedProperties[type.TypeHandle] = computedProperties;
return computedProperties;
}
private static List<PropertyInfo> NotUpdateablePropertiesCache(Type type)
{
if (UpdatableProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pi))
{
return pi.ToList();
}
var notUpdateableProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is NotUpdateableAttribute)).ToList();
UpdatableProperties[type.TypeHandle] = notUpdateableProperties;
return notUpdateableProperties;
}
private static List<PropertyInfo> ExplicitKeyPropertiesCache(Type type)
{
if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pi))
{
return pi.ToList();
}
var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList();
ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties;
return explicitKeyProperties;
}
private static List<PropertyInfo> KeyPropertiesCache(Type type)
{
if (KeyProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pi))
{
return pi.ToList();
}
var allProperties = TypePropertiesCache(type);
var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList();
if (keyProperties.Count == 0)
{
var idProp = allProperties.Find(p => string.Equals(p.Name, "id", StringComparison.CurrentCultureIgnoreCase));
if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute))
{
keyProperties.Add(idProp);
}
}
KeyProperties[type.TypeHandle] = keyProperties;
return keyProperties;
}
private static List<PropertyInfo> TypePropertiesCache(Type type)
{
if (TypeProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pis))
{
return pis.ToList();
}
var properties = type.GetProperties().Where(IsWriteable).ToArray();
TypeProperties[type.TypeHandle] = properties;
return properties.ToList();
}
private static bool IsWriteable(PropertyInfo pi)
{
var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList();
if (attributes.Count != 1) return true;
var writeAttribute = (WriteAttribute)attributes[0];
return writeAttribute.Write;
}
private static string GetTableName(Type type)
{
if (TypeTableName.TryGetValue(type.TypeHandle, out string name)) return name;
if (SqlMapperExtensions.TableNameMapper != null)
{
name = SqlMapperExtensions.TableNameMapper(type);
}
else
{
var info = type;
//NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework
var tableAttrName =
info.GetCustomAttribute<TableAttribute>(false)?.Name
?? (info.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name;
if (tableAttrName != null)
{
name = tableAttrName;
}
else
{
name = type.Name + "s";
if (type.IsInterface && name.StartsWith("I"))
name = name.Substring(1);
}
}
TypeTableName[type.TypeHandle] = name;
return name;
}
/// <summary>
/// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension.
/// </summary>
/// <typeparam name="T">Type to be updated</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToUpdate">Entity to be updated</param>
/// <param name="transaction">The transaction to run under, null (the default) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>true if updated, false if not found or not modified (tracked entities)</returns>
public static bool MyUpdate<T>(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
if (entityToUpdate is Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy proxy && !proxy.IsDirty)
{
return false;
}
var type = typeof(T);
if (type.IsArray)
{
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)
{
type = type.GetGenericArguments()[0];
}
}
var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy
var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("update {0} set ", name);
var allProperties = TypePropertiesCache(type);
keyProperties.AddRange(explicitKeyProperties);
var computedProperties = ComputedPropertiesCache(type);
// Exclude not updateable fields
var notUpdateableProperties = NotUpdateablePropertiesCache(type);
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties).Union(notUpdateableProperties)).ToList();
var adapter = GetFormatter(connection);
for (var i = 0; i < nonIdProps.Count; i++)
{
var property = nonIdProps[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
if (i < nonIdProps.Count - 1)
sb.Append(", ");
}
sb.Append(" where ");
for (var i = 0; i < keyProperties.Count; i++)
{
var property = keyProperties[i];
adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
if (i < keyProperties.Count - 1)
sb.Append(" and ");
}
var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
return updated > 0;
}
private static ISqlAdapter GetFormatter(IDbConnection connection)
{
var name = SqlMapperExtensions.GetDatabaseType?.Invoke(connection).ToLower()
?? connection.GetType().Name.ToLower();
return !AdapterDictionary.ContainsKey(name)
? DefaultAdapter
: AdapterDictionary[name];
}
}