C# 内存缓存,无法找出发生了什么



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

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

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

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

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

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);
        }

所以从本质上讲,当字段参数本身有一个范围(不是 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;
        }
TicketService.MapValues(ticketService.GetCustomFields()...

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

为什么当字段参数本身具有范围(不是 ref 或 out(时,我的缓存会在 MapValues 方法中被修改。

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


为了回应下面的评论,如果你做这样的事情:

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();

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

相关内容

最新更新