我创建了一个支持我的asp.net mvc控制器的服务。该服务返回一个List。它是我在创建和编辑视图中呈现的自定义字段列表。由于这些字段是在我想要在可用时返回缓存时定义的,所以当创建缓存时。由于我正在测试,我没有定义缓存过期。
当我执行编辑操作时,此服务有助于将查询的值映射到自定义字段的缓存列表。会发生什么是我的缓存中的对象被修改。
我很遗憾MemoryCache包含一个引用,并且它不包含该对象的副本。我不明白为什么MemoryCache被修改,当我实际使用的对象 - 在我看来 - 不是对缓存的引用并且已经传递给方法并且没有定义ref或out参数。对我来说,参考的范围完全不同?
我尝试了各种各样的事情,但我错过了造成这种行为的基本问题,我真的想弄清楚这里发生了什么。是否有更广泛的参考范围。局部变量是否仍然在方法之间共享?
这是服务中返回缓存信息或查询数据库并将结果存储在缓存中的方法。它由“创建”和“编辑”操作使用。请注意,value属性被定义为null,因此Create actions以空字段开始。
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方法中被修改。真想了解这里发生了什么。
更新
通过提供新的List引用进行修改后,我没有注意到任何更改。
看起来引用仍然从局部变量传递到新创建的参数。有一件事是用新创建的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;
}
答案 0 :(得分:1)
TicketService.MapValues(ticketService.GetCustomFields()...
在Edit
方法中,您调用MapValues
传递GetCustomFields
的结果,结果是缓存列表。因此,在MapValues
中,a
,b
和fields
的所有内容都是对相同列表(缓存对象)的引用。这就是为什么您看到对fields
所做的更改也会显示在b
中。
当fields参数有自己的范围(不是引用或输出)时,为什么我的缓存在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();
但是,您可以看到,如果您有更多复杂类型的属性或属性(需要复制属性的属性等),这可能会很快出现问题。有一些解决方案,如序列化/反序列化要复制的对象,但我首先考虑不同的设计。就像我说的那样,在你的情况下,我认为手动复制一些属性并不是太糟糕。