gpt4 book ai didi

如何使用C#中的Lambda表达式操作RedisHash结构,简化缓存中对象属性的读写操作

转载 作者:我是一只小鸟 更新时间:2023-07-15 22:31:27 28 4
gpt4 key购买 nike

Redis是一个开源的、高性能的、基于内存的键值数据库,它支持多种数据结构,如字符串、列表、集合、散列、有序集合等。其中,Redis的散列(Hash)结构是一个常用的结构,今天跟大家分享一个我的日常操作,如何使用Redis的散列(Hash)结构来缓存和查询对象的属性值,以及如何用Lambda表达式树来简化这个过程.

1、什么是Redis Hash结构

Redis Hash结构是一种键值对的集合,它可以存储一个对象的多个字段和值。例如,我们可以用一个Hash结构来存储一个人的信息,如下所示:

                          HSET person:
                          
                            1
                          
                          
                            id
                          
                          
                            1
                          
                          
                            
HSET person:
                          
                          
                            1
                          
                          
                             name Alice
HSET person:
                          
                          
                            1
                          
                           age 
                          
                            20
                          
                        

上面的命令将一个人的信息存储到了一个名为person:1的Hash结构中,其中每个字段都有一个名称和一个值。我们可以使用HGET命令来获取某个字段的值,例如:

                          HGET person:
                          
                            1
                          
                           name#Alice
                        

我们也可以使用HGETALL命令来获取所有字段的值,例如:

                          HGETALL person:1
                          
                            id
                          
                           1name Aliceage 
                          
                            20
                          
                        

2、如何使用C#来操作Redis Hash结构

为了在C#中操作Redis Hash结构,我们需要使用一个第三方库:StackExchange.Redis。这个库提供了一个ConnectionMultiplexer类,用于创建和管理与Redis服务器的连接,以及一个IDatabase接口,用于执行各种命令。例如,我们可以使用以下代码来创建一个连接对象和一个数据库对象:

                          
                            //
                          
                          
                             连接Redis服务器
                          
                          
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(
                          
                            "
                          
                          
                            localhost
                          
                          
                            "
                          
                          
                            );

                          
                          
                            //
                          
                          
                             获取数据库对象IDatabase db = redis.GetDatabase();
                          
                        

然后,我们可以使用db对象的HashSet方法和HashGet方法来存储和获取Hash结构中的字段值.

                          
                            //
                          
                          
                             创建一个HashEntry数组,存放要缓存的对象属性
                          
                          
HashEntry[] hashfield = 
                          
                            new
                          
                           HashEntry[
                          
                            3
                          
                          
                            ];
hashfield[
                          
                          
                            0
                          
                          ] = 
                          
                            new
                          
                           HashEntry(
                          
                            "
                          
                          
                            id
                          
                          
                            "
                          
                          , 
                          
                            "
                          
                          
                            1
                          
                          
                            "
                          
                          
                            );
hashfield[
                          
                          
                            1
                          
                          ] = 
                          
                            new
                          
                           HashEntry(
                          
                            "
                          
                          
                            name
                          
                          
                            "
                          
                          , 
                          
                            "
                          
                          
                            Alice
                          
                          
                            "
                          
                          
                            );
hashfield[
                          
                          
                            2
                          
                          ] = 
                          
                            new
                          
                           HashEntry(
                          
                            "
                          
                          
                            age
                          
                          
                            "
                          
                          , 
                          
                            "
                          
                          
                            20
                          
                          
                            "
                          
                          
                            );


                          
                          
                            //
                          
                          
                             使用HashSet方法将对象属性缓存到Redis的散列(Hash)结构中
                          
                          
db.HashSet(
                          
                            "
                          
                          
                            person:1
                          
                          
                            "
                          
                          
                            , hashfield);

                          
                          
                            //
                          
                          
                             使用HashGetAll方法从Redis的散列(Hash)结构中查询对象属性
                          
                          
HashEntry[] result = db.HashGetAll(
                          
                            "
                          
                          
                            person:1
                          
                          
                            "
                          
                          
                            );

                          
                          
                            //
                          
                          
                             遍历结果数组,打印对象属性
                          
                          
                            foreach
                          
                           (
                          
                            var
                          
                           item 
                          
                            in
                          
                          
                             result)
{
    Console.WriteLine(item.Name 
                          
                          + 
                          
                            "
                          
                          
                            : 
                          
                          
                            "
                          
                           +
                          
                             item.Value);
}
                          
                        

但是,这种方式有一些缺点:

  • 首先,我们需要手动将对象的属性名和值转换为HashEntry数组,并且保持一致性。
  • 其次,我们需要使用字符串来指定要存储或获取的字段名,并且还要避免拼写错误或重复。
  • 最后,我们需要手动将返回的RedisValue类型转换为我们需要的类型。

有没有更优雅的方法来解决这个问题呢?答案是肯定的.

3、如何用Lambda表达式轻松操作Redis Hash结构

Lambda表达式是一种匿名函数,可以用来表示委托或表达式树。在.NET中,我们可以使用Lambda表达式来操作实体类的属性,比如获取属性的值或者更新属性的值.

我们可以利用 Lambda表达式来指定要存储或获取的对象的属性,而不是使用字符串。使用表达式树来遍历Lambda表达式,提取出属性名和属性值,并转换为HashEntry数组或RedisValue数组,使其更易于使用。例如:

                          Get<Person>(p => 
                          
                            new
                          
                           { p.Name, p.Age });
                        

如果我们只想选择一个属性,就可以直接写:

                          Get<Person>(p => p.Name)
                        

如果要更新对象指定的属性,可以这样写了:

                          Update<Person>(p =>
                          
                             p
    .SetProperty(x 
                          
                          => x.Name, 
                          
                            "
                          
                          
                            Alice
                          
                          
                            "
                          
                          
                            ) 
    .SetProperty(x 
                          
                          => x.Age, 
                          
                            25
                          
                          ));
                        

怎么样,这样是不是优雅多了,这样做有以下好处:

  • 代码更加可读和可维护,因为我们可以直接使用对象的属性,而不是使用字符串。
  • 代码更加稳定和精确,因为我们可以避免拼写错误或重复,并且可以利用编译器的类型检查和提示。

那么,我们如何实现上面的方法呢?

1、Get方法

这个方法的目的是从缓存中获取对象的一个或多个属性值,使用一个泛型方法和一个Lambda表达式来实现.

                          
                            private
                          
                          
                            static
                          
                           TResult Get<T, TResult>(IDatabase db, 
                          
                            int
                          
                           id, Expression<Func<T, TResult>>
                          
                             selector)
{
    
                          
                          
                            if
                          
                           (selector == 
                          
                            null
                          
                          
                            )
        
                          
                          
                            throw
                          
                          
                            new
                          
                          
                             ArgumentNullException(nameof(selector));

    
                          
                          
                            //
                          
                          
                             使用扩展方法获取要查询的属性名数组
                          
                          
                            var
                          
                           hashFields = selector.GetMemberNames().Select(m => 
                          
                            new
                          
                          
                             RedisValue(m)).ToArray();
    
                          
                          
                            //
                          
                          
                             从缓存中获取对应的属性值数组
                          
                          
                            var
                          
                           values = db.HashGet($
                          
                            "
                          
                          
                            person:{id}
                          
                          
                            "
                          
                          
                            , hashFields);
    
                          
                          
                            //
                          
                          
                             使用扩展方法将HashEntry数组转换为对象
                          
                          
                            var
                          
                           obj = values.ToObject<T>
                          
                            (hashFields);
    
                          
                          
                            //
                          
                          
                             返回查询结果
                          
                          
                            return
                          
                          
                             selector.Compile()(obj);
}


                          
                          
                            private
                          
                          
                            static
                          
                           TResult Get<TResult>(IDatabase db, 
                          
                            int
                          
                           id, Expression<Func<Person, TResult>>
                          
                             selector)
    
                          
                          => Get<Person, TResult>(db, id, selector);
                        
  • 首先,定义一个泛型方法Get<T, TResult>,它接受一个数据库对象db,一个对象id,和一个Lambda表达式selector作为参数。这个Lambda表达式的类型是Expression<Func<T, TResult>>,表示它接受一个T类型的对象,并返回一个TResult类型的结果。这个Lambda表达式的作用是指定要查询的属性。
  • 然后,在Get<T, TResult>方法中,首先判断selector是否为空,如果为空,则抛出异常。然后,使用扩展方法GetMemberNames来获取selector中的属性名数组,并转换为RedisValue数组hashFields。这个扩展方法使用了ExpressionVisitor类来遍历表达式树,并重写了VisitMember方法来获取属性名。接下来,使用db.HashGet方法从缓存中获取对应的属性值数组values,使用id作为键。然后,使用扩展方法ToObject来将values数组转换为T类型的对象obj。这个扩展方法使用了反射来获取T类型的属性,并设置对应的属性值和类型转换。最后,返回selector编译后并传入obj作为参数的结果。
  • 接下来,定义一个私有方法Get<TResult>,它接受一个数据库对象db,一个对象id,和一个Lambda表达式selector作为参数。这个Lambda表达式的类型是Expression<Func<Person, TResult>>,表示它接受一个Person类型的对象,并返回一个TResult类型的结果。这个Lambda表达式的作用是指定要查询的Person对象的属性。
  • 然后,在Get<TResult>方法中,直接调用Get<T, TResult>方法,并传入db,id,selector作为参数,并指定T类型为Person。这样,就可以得到一个TResult类型的结果。

2、MemberExpressionVisitor扩展类

这个类的作用是遍历一个表达式树,收集其中的成员表达式的名称,并存储到一个列表中.

                          
                            public
                          
                          
                            class
                          
                          
                             MemberExpressionVisitor : ExpressionVisitor
{
    
                          
                          
                            private
                          
                          
                            readonly
                          
                           IList<
                          
                            string
                          
                          >
                          
                             _names;

    
                          
                          
                            public
                          
                           MemberExpressionVisitor(IList<
                          
                            string
                          
                          >
                          
                             list)
    {
        _names 
                          
                          =
                          
                             list;
    }

    
                          
                          
                            protected
                          
                          
                            override
                          
                          
                             Expression VisitMember(MemberExpression node)
    {
        
                          
                          
                            var
                          
                           name =
                          
                             node.Member.Name; 
        
                          
                          
                            if
                          
                           (node.Expression 
                          
                            is
                          
                          
                             MemberExpression member)
        {
            Visit(member); 
            name 
                          
                          = member.Member.Name + 
                          
                            "
                          
                          
                            .
                          
                          
                            "
                          
                           +
                          
                             name; 
        }
        _names.Add(name); 

        
                          
                          
                            return
                          
                          
                            base
                          
                          
                            .VisitMember(node);
    }
}
                          
                        
  • 首先,定义一个类MemberExpressionVisitor,它继承自ExpressionVisitor类。这个类有一个私有字段_names,用于存储属性名。它还有一个构造函数,接受一个IList<string>类型的参数list,并将其赋值给_names字段。
  • 然后,在MemberExpressionVisitor类中,重写了VisitMember方法,这个方法接受一个MemberExpression类型的参数node。这个方法的作用是访问表达式树中的成员表达式节点,并获取其属性名。
  • 接下来,在VisitMember方法中,首先获取node节点的属性名,并赋值给name变量。然后判断node节点的表达式是否是另一个成员表达式,如果是,则递归地访问该表达式,并将其属性名和name变量用"."连接起来,形成一个属性路径。然后将name变量添加到_names集合中。最后返回基类的VisitMember方法的结果。

3、Update方法

这个方法目的是将一个对象指定的属性名和值更新到缓存中,使用一个泛型方法和一个委托函数来实现.

                          
                            public
                          
                          
                            static
                          
                           Dictionary<
                          
                            string
                          
                          , 
                          
                            object
                          
                          > Update<TSource>(Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>
                          
                             setPropertyCalls)
{
    
                          
                          
                            if
                          
                           (setPropertyCalls == 
                          
                            null
                          
                          
                            )
        
                          
                          
                            throw
                          
                          
                            new
                          
                          
                             ArgumentNullException(nameof(setPropertyCalls));

    
                          
                          
                            var
                          
                           nameValues = 
                          
                            new
                          
                           Dictionary<
                          
                            string
                          
                          , 
                          
                            object
                          
                          >(
                          
                            100
                          
                          ); 
                          
                            //
                          
                          
                             创建一个字典用于存储属性名和值
                          
                          
                            var
                          
                           calls = 
                          
                            new
                          
                           SetPropertyCalls<TSource>(nameValues); 
                          
                            //
                          
                          
                             创建一个SetPropertyCalls对象
                          
                          
                            
    setPropertyCalls(calls); 
                          
                          
                            //
                          
                          
                             调用传入的函数,将属性名和值添加到字典中
                          
                          
                            return
                          
                           nameValues; 
                          
                            //
                          
                          
                             返回字典
                          
                          
                            }


                          
                          
                            private
                          
                          
                            static
                          
                          
                            void
                          
                           Update(IDatabase db, 
                          
                            int
                          
                           id, Func<SetPropertyCalls<Person>, SetPropertyCalls<Person>>
                          
                             setPropertyCalls)
{
    
                          
                          
                            var
                          
                           hashEntries =
                          
                             Update(setPropertyCalls)
        .Select(kv 
                          
                          => 
                          
                            new
                          
                           HashEntry(kv.Key, kv.Value != 
                          
                            null
                          
                           ?
                          
                             kv.Value.ToString() : RedisValue.EmptyString))
        .ToArray();

    
                          
                          
                            //
                          
                          
                             将HashEntry数组存储到缓存中,使用对象的Id作为键
                          
                          
                                db.HashSet(id.ToString(), hashEntries);
}}
                          
                        
  • 首先,定义一个泛型方法Update<TSource>,它接受一个函数作为参数,这个函数的类型是Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>,表示它接受一个SetPropertyCalls<TSource>对象,并返回一个SetPropertyCalls<TSource>对象。这个函数的作用是设置要更新的属性名和值。
  • 然后,在Update<TSource>方法中,创建一个字典nameValues,用于存储属性名和值。创建一个SetPropertyCalls<TSource>对象calls,传入nameValues作为构造参数。调用传入的函数setPropertyCalls,并传入calls作为参数。这样,setPropertyCalls函数就可以通过调用calls的SetProperty方法来添加属性名和值到nameValues字典中。最后,返回nameValues字典。
  • 接下来,定义一个私有方法Update,它接受一个数据库对象db,一个对象id,和一个函数setPropertyCalls作为参数。这个函数的类型是Func<SetPropertyCalls<Person>, SetPropertyCalls<Person>>,表示它接受一个SetPropertyCalls<Person>对象,并返回一个SetPropertyCalls<Person>对象。这个函数的作用是设置要更新的Person对象的属性名和值。
  • 然后,在Update方法中,调用Update(setPropertyCalls)方法,并传入setPropertyCalls作为参数。这样,就可以得到一个字典nameValues,包含了要更新的Person对象的属性名和值。将nameValues字典转换为HashEntry数组hashEntries,使用属性值的字符串表示作为HashEntry的值。如果属性值为空,则使用RedisValue.EmptyString作为HashEntry的值。最后,使用db.HashSet方法将hashEntries数组存储到缓存中,使用id作为键。

4、SetPropertyCalls泛型类

这个类的作用是收集一个源对象的属性名称和值的对应关系,并提供一个链式调用的方法,用于设置属性的值.

                          
                            public
                          
                          
                            class
                          
                           SetPropertyCalls<TSource>
                          
                            
{
    
                          
                          
                            private
                          
                          
                            readonly
                          
                           Dictionary<
                          
                            string
                          
                          , 
                          
                            object
                          
                          >
                          
                             _nameValues;

    
                          
                          
                            public
                          
                           SetPropertyCalls(Dictionary<
                          
                            string
                          
                          , 
                          
                            object
                          
                          >
                          
                             nameValues)
    {
        _nameValues 
                          
                          =
                          
                             nameValues;
    }

    
                          
                          
                            public
                          
                           SetPropertyCalls<TSource> SetProperty<TProperty>(Expression<Func<TSource, TProperty>>
                          
                             propertyExpression, TProperty valueExpression)
    {
        
                          
                          
                            if
                          
                           (propertyExpression == 
                          
                            null
                          
                          
                            )
            
                          
                          
                            throw
                          
                          
                            new
                          
                          
                             ArgumentNullException(nameof(propertyExpression));

        
                          
                          
                            if
                          
                           (propertyExpression.Body 
                          
                            is
                          
                           MemberExpression member && member.Member 
                          
                            is
                          
                          
                             PropertyInfo property)
        {
            
                          
                          
                            if
                          
                           (!
                          
                            _nameValues.TryAdd(property.Name, valueExpression))
            {
                
                          
                          
                            throw
                          
                          
                            new
                          
                           ArgumentException($
                          
                            "
                          
                          
                            The property '{property.Name}' has already been set.
                          
                          
                            "
                          
                          
                            );
            }
        }
        
                          
                          
                            return
                          
                          
                            this
                          
                          
                            ;
    }
}
                          
                        
  • 首先,这个类有一个构造函数,接受一个Dictionary<string, object>类型的参数,作为存储属性名称和值的对应关系的字典,并赋值给一个私有字段_nameValues。
  • 然后,这个类有一个泛型方法,叫做SetProperty。这个方法接受两个参数,一个是表示源对象属性的表达式,另一个是表示属性值的表达式。
  • 在这个方法中,首先判断第一个参数是否为空,如果为空,则抛出ArgumentNullException异常。
  • 然后判断第一个参数的表达式体是否是一个成员表达式,并且该成员表达式的成员是否是一个属性,如果是,则获取该属性的名称,并赋值给一个局部变量property。
  • 然后尝试将该属性名称和第二个参数的值添加到_nameValues字典中,如果添加失败,则说明该属性已经被设置过了,抛出ArgumentException异常。
  • 最后,返回当前对象的引用,实现链式调用的效果。

这样,我们就可以得到一个包含所有要更新的属性名和值的字典,然后我们就可以根据这些属性名和值来更新实体类的属性了.

Demo示例

让我们来看一下代码示例,为了方便演示和阅读,这是临时码的,实际中大家可以根据自己习惯来进行封装,简化调用,同时也可以使用静态字典来缓存编译好的委托及对象属性,提高性能.

👇感谢阅读,点赞+分享+收藏+关注👇 。

公众号:猿惑豁 。

写作不易,转载请注明博文地址,否则禁转!!! 。

最后此篇关于如何使用C#中的Lambda表达式操作RedisHash结构,简化缓存中对象属性的读写操作的文章就讲到这里了,如果你想了解更多关于如何使用C#中的Lambda表达式操作RedisHash结构,简化缓存中对象属性的读写操作的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com