0

Net core application. I have come up with below problem. I am trying updating values in db table.

 public async Task<MyResponseModel> Update(RequestModel request)
{
  IEnumerable<MyModel> mymodel = await _myRepository.GetAsync(x => Ids.Contains(x.Id));
  List<MyModel> copy = new List<MyModel>();
  copy.AddRange(mymodel)

for (int i = 0; i < mymodel.Count; i++)
{
//some property update
}
myRepository.UpdateRange(mymodel);
await _unitOfWork.CompleteAsync().ConfigureAwait(false);

here problem is in above code in copy variable i want to keep old values directly returned from first query. But problem is after executing update command even my copy variable values will be updated with new values those are updated. I want to keep old values in copy list which was there before updating. Can someone help me to find the root cause of it. Any help would be appreciated. Thanks

Mr Perfect
  • 585
  • 8
  • 30

3 Answers3

2

Quick workaround get two copies of the models:

public async Task<MyResponseModel> Update(RequestModel request)
{
    var models = await _myRepository.GetAsync(x => Ids.Contains(x.Id));
    var copyModels = await _myRepository.GetAsync(x => Ids.Contains(x.Id));

    foreach (var model in models)
    {
        model.SomeProperty = "new value";
    }

    myRepository.UpdateRange(models);
    await _unitOfWork.CompleteAsync().ConfigureAwait(false);

    // copyModels still have old previous value
}

Root cause of the original problem is that adding instances to the new list will not create a copy of the object - but will create a copy of the references to the same object.

Fabio
  • 31,528
  • 4
  • 33
  • 72
  • 1
    This repeats the query in the database twice, which depending on the query could be expensive. It's much better to query once and just clone the resulting objects. – Alejandro May 18 '21 at 13:10
  • Also, take into account that some ORMs have caches that, in response to repeated queries, don't query the DB but return cached responses, ie, the very same objects, replicating the original problem again. It depends on the particular implementation of the repository. – Alejandro May 18 '21 at 13:11
  • @Alejandro this is quick and simple approach based on the information provided by OP - which is not enough to suggest something more effective. Everything else is a guessing. – Fabio May 18 '21 at 20:17
  • @Alejandro - about ORM returning same objects for second query - are you saying that if I make a second call to get `copyModels` after foreach loop but before calling `CompleteAsync` copyModels will have updated property? – Fabio May 18 '21 at 20:21
  • It's possible, yes. Some ORMs (for example, NHibernate) will detect the same query ran twice and simply return the very same objects again, sometimes without even querying the DB. If that's the case, you'll end up with the same situation as the OP, as it would be two lists containing the same instances. Of course, that really happening depends on the concrete implementation of the repository. – Alejandro May 18 '21 at 21:40
2

You may want to use the Extensions method: I've found this answer in another question from the user ajm.

   static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}
1

copy.AddRange(mymodel) this code will do a shallow copy of mymodel, so "copy" variable will point to data in same memorylocation as of "mymodel" variable. To fix this the workaround is update above line of code with below copy.AddRange(JsonConvert.DeserializeObject<List>(JsonConvert.SerializeObject(mymodel))) This can be done using Reflections as well but above is the workaround.

    public async Task<MyResponseModel> Update(RequestModel request)
    {
        IEnumerable<MyModel> mymodel = await _myRepository.GetAsync(x => Ids.Contains(x.Id));
        List<MyModel> copy = new List<MyModel>();

        copy.AddRange(JsonConvert.DeserializeObject<List<MyModel>>(JsonConvert.SerializeObject(mymodel)))

        for (int i = 0; i < mymodel.Count; i++)
        {
            //some property update
        }
        myRepository.UpdateRange(mymodel);
        await _unitOfWork.CompleteAsync().ConfigureAwait(false);
    }