К сожалению, Dapper.Contrib не может делать то, что вы хотите.Дело в том, что согласно источникам метод расширения Insert<T>
и Update<T>
имеет абсолютно одинаковый механизм для сбора полей, которые будут затронуты.В основном это выглядит так:
// in Insert<T>
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
...
// in Update<T>
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
Таким образом, эти методы всегда влияют на один и тот же набор полей.Чтобы достичь целевого поведения, вам нужно написать собственное расширение и атрибут.Ниже приведено много кода, и я не думаю, что это элегантное решение.Однако вы можете свободно использовать и переделывать его по мере необходимости.Это грубая синхронная реализация, поэтому рассмотрим ее в качестве примера, которая поможет вам разработать собственное решение.Вот тестовый пример для пользовательского MyUpdate<T>
метода расширения:
[Table("Media")]
public class Media
{
[Key]
public long Id { get; set; }
[NotUpdateable]
public int OwnerId { get; set; }
public string Name { get; set; }
}
[Test]
public void DapperContribNotWriteableField()
{
// Arrange
var conn = new SqlConnection(
"Data Source=vrpisilstage.c0hnd1p1buwt.us-east-1.rds.amazonaws.com;Initial Catalog=VRPISIL;User ID=VRPISILSTAGE;Password=ottubansIvCajlokojOt;Connect Timeout=100");
conn.Open();
var media = new Media
{
OwnerId = 100500,
Name = "Media"
};
// Act
media.Id = conn.Insert(media);
media.OwnerId = 500100;
conn.MyUpdate(media);
var result = conn.Get<Media>(media.Id);
// Assert
Assert.AreEqual(result.OwnerId, 100500);
}
Таблица в БД:
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
{
}
А вот немного переработанный метод расширения, который учитывает атрибут NotUpdatable
:
/// <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];
}
}
Надеюсь, это поможет.