首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何避免使用Dapper.Contrib更新后续更新调用的列?

如何避免使用Dapper.Contrib更新后续更新调用的列?
EN

Stack Overflow用户
提问于 2018-10-04 00:03:08
回答 1查看 1.3K关注 0票数 1

我有一个类Media,它的DB表Media主要是1比1。我使用Dapper.Contrib的Update SqlConnection扩展方法通过传入Media对象更新表。

表中的列之一(以及相应的类属性)是OwnerID,它只是第一次创建这个媒体的用户的ID。它应该写在第一个insert上(这也是使用Dapper.Contrib的Insert扩展方法完成的),然后更新不应该改变它。仅仅用Dapper.Contrib就能实现这一点吗?如果可能的话,我不希望在进行更新之前读取OwnerID列,只是为了确保OwnerID对象属性是相同的。

[Computed]属性似乎忽略了UpdateInsert中的这一列,description 这里似乎表明该属性只应该省略在更新时写入该列,这使我希望我只是错误地使用了这个库。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-10-04 04:07:22

不幸的是,Dapper.Contrib不能做你想做的事。问题是,根据资料来源扩展方法,Insert<T>Update<T>有完全相同的机制来收集字段,这会影响到它们。基本上看上去:

代码语言:javascript
运行
复制
// in Insert<T>
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();

...

// in Update<T>
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();

因此,这些方法总是影响同一组字段。要实现目标行为,需要编写自己的扩展和属性。下面有很多代码,我不认为它是一个优雅的解决方案。然而,您可以自由地使用和返工它的需要。它是一个粗略的同步实现,因此将其作为示例来考虑,而不是帮助您开发自己的解决方案。以下是自定义MyUpdate<T>扩展方法的测试示例:

代码语言:javascript
运行
复制
[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);
}

DB表:

代码语言:javascript
运行
复制
CREATE TABLE [Media]
(
    [Id]           INT IDENTITY (1, 1) NOT NULL,
    [OwnerId]      BIGINT              NOT NULL,    
    [Name]         VARCHAR(50)         NOT NULL
)

下面是标记属性的属性,这些属性不能在Update查询中使用:

代码语言:javascript
运行
复制
/// <summary>
/// Specifies that this is a not updateable column.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class NotUpdateableAttribute : Attribute
{
}

下面是一个稍微修改过的扩展方法,它考虑了属性NotUpdatable

代码语言:javascript
运行
复制
/// <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];
    }


}

希望能帮上忙。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52637178

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档