【问题标题】:C# MemoryCache, Cannot find out what is happeningC# MemoryCache,无法找出发生了什么
【发布时间】:2016-04-02 16:54:27
【问题描述】:

我创建了一个支持我的 asp.net mvc 控制器的服务。该服务返回一个列表。它是我在创建和编辑视图中呈现的自定义字段列表。由于这些字段是在我想在可用时返回缓存时定义的,当不可用时,创建缓存。由于我正在测试,我还没有定义缓存过期。

当我执行编辑操作时,此服务有助于将查询值映射到自定义字段的缓存列表。发生的事情是我在缓存中的对象被修改了。

我很熟悉 MemoryCache 包含一个引用并且不包含该对象的副本。我不明白为什么 MemoryCache 被修改,当我实际使用一个对象时 - 在我看来 - 不是对缓存的引用并且已传递给方法并且没有定义 ref 或 out 参数。对我来说,参考的范围完全不同?

我尝试了各种方法,但我错过了导致这种行为的基本问题,我真的很想弄清楚这里发生了什么。是否有更广泛的参考范围。局部变量是否仍在方法之间共享?

这是服务中的方法,它要么返回缓存的信息,要么查询数据库并将结果存储在缓存中。它由创建和编辑操作使用。请注意,value 属性被定义为 null,因此 Create 操作以空字段开始。

public IList<CustomField> GetCustomFields()
        {
            var result = MemoryCache.Default["cache_customfield"] as List<CustomField>;
            if (result == null)
            {
                result = session.Query<CustomField>()
                        .AsEnumerable()
                        .Select(c => new CustomField
                        {
                            Id = c.Id,
                            Name = c.Name,
                            Value = null
                        })
                        .ToList();

                MemoryCache.Default["cache_customfield"] = result;
            }
            return result;
        }

public static IList<CustomField> MapValues(IList<CustomField> fields, IDictionary<string,string> values = null)
        {
            // the cached information still has value properties that are null
            var a = MemoryCache.Default["cache_customfield"] as List<CustomField>;

            foreach (var field in fields.OrderBy(x => x.Name))
            {
                var persistedValue = string.Empty;
                values?.TryGetValue(field.Id, out persistedValue);
                field.Value = persistedValue;
            }

            // the cached information suddenly has value properties that are defined, however the 'fields' parameter has no reference to the original information?!
            var b = MemoryCache.Default["cache_customfield"] as List<CustomField>;

            return fields;
        }

我怀疑这些对情况有多大影响,但这些是控制器上用于创建和编辑的操作。

public ActionResult Create()
        {
            var ticketService = BusinessServiceFacade.GetTicketService(RavenSession);

            var vm = new TicketViewModel();
            vm.Controls = ControlViewModel.CreateControls(ticketService.GetCustomFields());
            return View(vm);
        }

public ActionResult Edit(string id)
        {
            var ticketService = BusinessServiceFacade.GetTicketService(RavenSession);
            var ticket = RavenSession.Load<Ticket>(id);
            var customfieldValues = ticket.Attributes.ToDictionary(x => x.Name, x => x.Value);

            var vm = new TicketViewModel(ticket);

            var listOfCustomFields = TicketService.MapValues(ticketService.GetCustomFields(), customfieldValues);
            vm.Controls = ControlViewModel.CreateControls(listOfCustomFields);

            return View(vm);
        }

所以本质上,当 fields 参数具有自己的范围(不是 ref 或 out)时,为什么我的缓存在 MapValues 方法中被修改。真的很想了解这里发生了什么。

更新:

通过提供新的列表参考进行修改后,我没有注意到任何变化。

看起来引用仍然从局部变量传递到新创建的参数。一件事是用新创建的 CustomField 对象完全建立一个新列表,但如果可能的话,我想避免这种情况。

我可能犯了一个简单的错误。

    public ActionResult Create()
    {
        var ticketService = BusinessServiceFacade.GetTicketService(RavenSession);

        var vm = new TicketViewModel();

        var fields = ticketService.GetCustomFields();
        vm.Controls = ControlViewModel.CreateControls(new List<CustomField>(fields));

        return View(vm);
    }

   public ActionResult Edit(string id)
    {
        var ticketService = BusinessServiceFacade.GetTicketService(RavenSession);
        var ticket = RavenSession.Load<Ticket>(id);
        var customfieldValues = ticket.Attributes.ToDictionary(x => x.Name, x => x.Value);

        var vm = new TicketViewModel(ticket);

        var fields = ticketService.GetCustomFields();
        var listOfCustomFields = TicketService.MapValues(new List<CustomField>(fields), customfieldValues);
        vm.Controls = ControlViewModel.CreateControls(listOfCustomFields);

        return View(vm);
    }

解决方案

做一个深拷贝。

public static IList<CustomField> MapValues(IList<CustomField> fields, IDictionary<string,string> values = null)
        {
            // break reference, deep copy to new list
            var oldList = (List<CustomField>) fields;
            var newList = oldList.ConvertAll(c => new CustomField(c.Id, c.Name, c.Visible, c.Type, c.TypeFormat, c.Value));

            foreach (var field in newList.OrderBy(x => x.Name))
            {
                var persistedValue = string.Empty;
                values?.TryGetValue(field.Id, out persistedValue);
                field.Value = persistedValue;
            }

            return newList;
        }

【问题讨论】:

    标签: c# asp.net-mvc caching


    【解决方案1】:
    TicketService.MapValues(ticketService.GetCustomFields()...
    

    在您的Edit 方法中,您调用MapValues 并传入GetCustomFields 的结果,该结果就是缓存列表。因此,在MapValues 中,所有abfields 都是对same 列表(缓存对象)的引用。这就是为什么您会看到您对fields 所做的更改也出现在b 中。

    当 fields 参数有自己的范围(不是 ref 或 out)时,为什么我的缓存在 MapValues 方法中被修改。

    是的,fields 的作用域是方法。但我认为您混淆了 1) 更改 fields 的值之间的区别——这是对列表的引用。并且 2) 更改 fields 引用的实际 list。是的,您对fields 所做的更改仅限于此方法(例如,它不会影响传入的值)。但是,只要它指向特定列表,您对该列表所做的任何更改都可以通过对同一列表的其他引用来观察。因此,fields 的范围并不意味着您对 list 所做的更改将在此方法的范围内。


    针对下面的评论,如果您这样做:

    IList<CustomField> originalList = ticketService.GetCustomFields();
    IList<CustomField> newList = new List<CustomField>(originalList);
    

    并将新列表传递给MapValues (TicketService.MapValues(newList...),然后MapValues 中的更改不会影响originalList 引用的列表。因为现在你有两个不同的列表。


    更新:如下所述,我没有注意到您正在修改列表中的单个项目。所以在这种情况下你需要深拷贝。在这种特定情况下,深度复制并不算太糟糕,因为您只需复制几个属性:

    IList<CustomField> originalList = ticketService.GetCustomFields();
    IList<CustomField> newList = originalList
        .Select(x => new CustomField
        {
            Id = x.Id,
            Name = x.Name,
            Value = x.Value
        })
        .ToList();
    

    但是,您可以看到当您拥有更多属性或复杂类型的属性(需要复制属性的属性等)时,这会如何迅速出现问题。有一些解决方案,例如序列化/反序列化要复制的对象,但我会首先考虑不同的设计。就像我说的,在你的情况下,我认为手动复制几个属性还不错。

    【讨论】:

    • 所以,如果我理解正确的话。对提供的 fields 参数的修改仍然对在 GetCustomFields() 方法中创建并作为 fields 参数传递的原始对象有影响?有什么办法可以打破这种行为?我假设复制从 GetCustomFields() 返回的对象也将引用原始对象,从而修改缓存信息?
    • 如果“复制”是指将其分配给另一个变量,那么是的,它不会产生任何影响——您将观察到相同的行为。查看更新。
    • 感谢您的帮助。这确实有助于澄清情况。
    • 不幸的是,当我创建一个新列表并传递值时,引用仍在转发。我假设通过创建一个局部变量并引用列表,该引用仍保留在新创建的列表对象中。我会将更改后的代码添加到我原来的问题中。
    • @MichaSchopman 我没有注意到您没有修改列表但您正在修改列表中的各个项目的事实。在这种情况下,您不仅需要创建一个新列表,还必须复制各个项目(这称为“深度复制”)。
    猜你喜欢
    • 2012-01-20
    • 2013-08-26
    • 2018-11-02
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 2014-03-07
    • 1970-01-01
    • 2020-07-26
    相关资源
    最近更新 更多