データコレクションをasp.netmvcにキャッシュし、キャッシュされたコレクションを変更せずにこのコレクションをフィルター処理したいと思います。
これを行うことは可能ですか?
今のところ、キャッシュされたコレクションを取得すると、そのコレクションへの参照が取得され、このコレクションのデータをフィルター処理すると、キャッシュされたコレクションが変更されます。
誰か知っている?
データコレクションをasp.netmvcにキャッシュし、キャッシュされたコレクションを変更せずにこのコレクションをフィルター処理したいと思います。
これを行うことは可能ですか?
今のところ、キャッシュされたコレクションを取得すると、そのコレクションへの参照が取得され、このコレクションのデータをフィルター処理すると、キャッシュされたコレクションが変更されます。
誰か知っている?
キャッシュされたリストから新しいリストにアイテムをコピーするだけです。インスタンスは同じですが、元の(キャッシュされた)リストは変更されません。このようなもの:
List<[CLASS]> collectionCached; //assuming this this your cached list
List<[CLASS]> collectionWorking = new List<[CLASS]>();
collectionWorking.AddRange(collectionCached);
これにより、元の(キャッシュされた)リストに触れることなく、必要なインスタンスを除外できます。
編集:
OPからさらに明確にした後、エンティティ自体のコピーを作成する必要があるようです。参照オブジェクトのコピーを作成する背後にある基本的な考え方は、「クローン作成」と呼ばれます。これにより、元のエンティティのコピーが作成され、元のインスタンスを変更せずにコピーを変更できます。SOにはいくつかの優れたリンクがあります。ここでは、クローン作成とLINQの両方の概念について説明しています。
c#でlinq / lambdaを使用して、参照の代わりにデータのコピーを取得するにはどうすればよいですか?
これにより、正しい軌道に乗ることができます。他にご不明な点がございましたら、お気軽にお問い合わせください。
完全なリストをキャッシュしてから、Linqを使用してコレクションをフィルター処理します。
var list = GetFromCache();
var filtered = list.Where(x => x.Name.Contains("rob"));
編集:
このフィルタリングされたリストを取得するとき、リスト内に含まれるオブジェクトを変更していますか?はい、それはキャッシュリストの同じインスタンスにも反映されます。しかし、その時点では、これはキャッシュするのに最適なデータではないようです。通常、キャッシュされたデータは変更されません。または、しばらくの間変更を確認せずに生きることができます。データの精度がより重要な場合は、キャッシュしないでください。
リスト内のアイテムの状態を変更する場合は、必要なものを簡単に取得できる可能性があります。
var filtered = list.Where(x => x.Name.Contains("rob")).Select(x => x.MemberwiseClone()).Cast<YourObjType>();
これは、浅いコピーを行うMemberwiseCloneを使用します。それでも不十分な場合は、コピーの保持を行うメソッドでいくつかの作業を行う必要があります。Objectを返すので、Cast拡張メソッドを使用して、元々リストにあったオブジェクトのタイプに戻します。
Malcolm Frexnerが言っていることは、問題を解決するための優れたクリーンな方法ですが、AutoMapperを使用すると大幅に簡略化できます。
DTOオブジェクトをプルしているアダプターがあるとします(または、データベースから直接プルしている場合はWCFを使用していない場合は、同じ概念が適用されます)
public class AccountServiceAdapter
{
private List<Accounts> _accounts
{
get
{
if(HttpContext.Current.Cache["accounts"] == null)
HttpContext.Current.Cache["accounts"] = default(List<Accounts>());
return (List<Accounts>)HttpContext.Current.Cache["accounts"];
}
set
{
HttpContext.Current.Cache["accounts"] = value;
}
}
public List<AccountViewModel> FetchAccounts()
{
if(_accounts == default(List<Accounts>))
{
//Do Fetch From Service or Persistence and set _accounts
}
return _accounts.Select(x => x.ConvertToViewModel()).ToList();
}
}
さて、それはあなたがおそらくすでに書いたあなたのキャッシングピースです。次のビットはあなたが少し楽しむことができるところです。AutoMapperのマッピングの面白さをすべて説明することはしません。これは、10億のリンクがあるためですが、実行するためのスニペットをいくつか紹介します。
https://github.com/AutoMapper/AutoMapper/wiki/Projection
NuGetを使用してAutoMapperへの参照を追加した後、ProjectionManagerクラスを作成する必要があります。
public static class ProjectionManager
{
ProjectionManager()
{
//The mapping exercise is AS SIMPLE as this if all you are doing is
//using the exact same property names, it'll autowire
//If you use different projections you can create complex maps see the link
//above.
Mapper.CreateMap<Account,AccountViewModel>();
Mapper.CreateMap<AccountViewModel,Account>();
}
//Make yourself some handy extension methods for syntactic sugar
public static AccountViewModel ConvertToViewModel(this Account x)
{
return Mapper.Map<Account,AccountViewModel>(x);
}
//Make yourself some handy extension methods for syntactic sugar
public static Account ConvertFromViewModel(this AccountViewModel x)
{
return Mapper.Map<AccountViewModel,Account>(x);
}
}
これらすべてを実行すると、キャッシュが非常にシンプルになり、起動するために、エンティティオブジェクトやDTOオブジェクトを乱雑にすることなく、検証、表示名などの優れた機能をすべて備えたビューモデルにデータをキャストできます。あなたのWCFサービスの!
これは機能するはずです:
IEnumerable<T> things = GetThings();
Cache["ThingCache"] = things;
T aThing = ((IEnumerable)Cache["ThingCache"]).First();
//Cache["ThingCache"] should still contain the original collection.
最終的に、各オブジェクトにClone()メソッドを作成し、キャッシュから取得した直後にメインオブジェクトでClone()メソッドを呼び出すことで問題を解決しました。
私の主なオブジェクトは、ドメインのリストを含むアカウントタイプであり、製品のリストなどを含みます。
AccountオブジェクトのCloneメソッドは次のとおりです。
public Account Clone()
{
// Copy all properties of the object in a new object account
var account = (Account)this.MemberwiseClone();
var domainsList = new List<Domain>();
foreach (var d in this.Domains)
{
domainsList.Add(d.Clone());
}
account.Domains = domainsList;
return account;
}
ご覧のとおり、MemberwiseClone()を使用して単純なプロパティをコピーしてから、すべての複雑なオブジェクトに対して再度Cloneを呼び出します。すべての複雑なオブジェクトで作成したカスタムメソッドであるクローン。
そして今、それはうまくいきます...
ビューでドメインモデルを直接使用したときに、これに遭遇しました。表示されたすべてのデータを専用のViewModelに配置するとすぐに、問題を簡単に回避できます。
DomainModel
public class PersonInfo
{
public int Age {get;set;}
public string Name {get;set;}
}
ViewModel
public class PersonViewModel
{
public PersonInfo PersonData {get;set;}
public bool Visible {get;set;}
}
これで、すべてのユーザーのプロパティ「Visible」を保存するリスクなしに、PersonInfoをキャッシュに保存できます。
オブジェクトのリストがある場合は少し難しくなりますが、このパターンはそこに適用できます。
最も簡単なのは、オブジェクトをキャッシュに入れる前にオブジェクトをjsonにシリアル化し、そこからオブジェクトを取り出すときに逆シリアル化することです。そうすれば、iclonableなどの実装など、クラスに変更を加える必要がありません。
Cache.Set(key, JsonConvert.SerializeObject(objectToCache), policy);
その後
var cachedObject = (string)Cache[ComposeKey(key, domain)];
return JsonConvert.DeserializeObject<T>(cachedObject);
これは、キャッシュポイズニングを処理するときの私の頼りになるコードです。
var list_from_cache = HttpContext.Current.Cache["females"] as List<Females>;
var females = new List<Females>();
females.AddRange(list_from_cache.Select(n => Cloner(n))); //clone all objects
//now we can change the objects as much as we want without changing the cache
females[0].Height=160;
クローン作成者のコード、これをどこかに配置します:(クローン化するクラスに[Serializable]を配置することを忘れないでください)
public static T Cloner<T>(T source) where T : class
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
//return (T)formatter.Deserialize(stream);
var clone = formatter.Deserialize(stream) as T;
return clone;
}
}
これは、System.Collections.Immutableを使用して解決できます。
あなたはImmutableListを探しています。
ここでnuget-packageを入手します:https ://www.nuget.org/packages/System.Collections.Immutable/ または:Install-Package System.Collections.Immutable