05 January 2009

LINQ для об’єктної моделі SharePoint, або інших IDisposable об’єктів

Об’єктна модель SharePoint більш-менш ієрархічна. Тобто є Web Application а в ньому Site-и, у сайта – Web-и, у веба - List-и, тощо. Так вже виходить, що не рідко з’являється потреба по цій ієрархії бігати. Ось невеличкий приклад, пошуку потрібного Event Receiver-у.

   1: foreach (SPSite site in GetApplicationByUrl("http://test/").Sites){
   2:     using (site){
   3:         foreach (SPWeb web in site.AllWebs){
   4:             using (web){
   5:                 foreach (SPList list in web.Lists){
   6:                     foreach (SPEventReceiverDefinition eventReceiver in list.EventReceivers)
   7:                     {
   8:                         if (eventReceiver.Assembly == "Test"
   9:                             && eventReceiver.Class == "Test")
  10:                         {
  11:                             Console.WriteLine("Site: {0}; Web: {1}; List: {2}; Receiver: {3}",
  12:                                               site.Url, web.Name, list.Title, eventReceiver.Class);
  13:                         }
  14:                     }
  15:                 }
  16:             }
  17:         }
  18:     }            
  19: }

Бридко! Читати не реально. Розбити на декілька методів, теж не реально. І це я повикидав чи не половину перевірок! В реальному житті там був просто жах!

Ось в таких випадках і може згодитися LINQ, дивіться:

   1: var results = from site in GetApplicationByUrl("http://test/").Sites.Cast<SPSite>()
   2:               from web in site.AllWebs.Cast<SPWeb>()
   3:               from list in web.Lists.Cast<SPList>()
   4:               from eventReceiver in list.EventReceivers.Cast<SPEventReceiverDefinition>()
   5:               where eventReceiver.Assembly == "Test"
   6:                     && eventReceiver.Class == "Test"
   7:               select new
   8:                          {
   9:                              SiteUrl = site.Url,
  10:                              WebName = web.Name,
  11:                              ListTitle = list.Title,
  12:                              ReceiverClass = eventReceiver.Class
  13:                          };
  14:  
  15: foreach (var result in results)
  16: {
  17:     Console.WriteLine("Site: {0}; Web: {1}; List: {2}; Receiver: {3}",
  18:         result.SiteUrl, result.WebName, result.ListTitle, result.ReceiverClass);
  19: }

Той самий функціонал, але значно читабільніше!

Але тут виникає інша проблема, в SharePoint майже вся об’єктна модель це обгортка навколо COM, та що тут скажеш, великих COM об’єктів. Єдина можливість з цим жити це IDisposable… Нажаль IDisposable не дуже вкладається в рамки LINQ. Ось я і вирішив це виправити.

   1: SPWebApplication webApplication = GetApplicationByUrl("http://test/");
   2:  
   3: var results = from site in webApplication.Sites.Cast<SPSite>()
   4:               from web in site.WithDisposabler(site.AllWebs.Cast<SPWeb>()) // <- Зміна тут
   5:               from list in site.WithDisposabler(web.Lists.Cast<SPList>()) // <- та тут
   6:               from eventReceiver in list.EventReceivers.Cast<SPEventReceiverDefinition>()
   7:               where eventReceiver.Assembly == "Test"
   8:                     && eventReceiver.Class == "Test"
   9:               select new
  10:               {
  11:                   SiteUrl = site.Url,
  12:                   WebName = web.Name,
  13:                   ListTitle = list.Title,
  14:                   ReceiverClass = eventReceiver.Class
  15:               };
  16:  
  17: foreach (var result in results)
  18: {
  19:     Console.WriteLine("Site: {0}; Web: {1}; List: {2}; Receiver: {3}",
  20:         result.SiteUrl, result.WebName, result.ListTitle, result.ReceiverClass);
  21: }

Додався маджік з WithDisposabler. Це невеличкий метод-“розширення” який огортає IEnumerable дргугого аргументу в спеціальний адаптер для IEnumerable<T> та IEnumerator<T>, який, в свою чергу, просто викликає метод Dispose у першого аргументу. В нашому випадку це і є ота обгортка над COM.

Код методу WithDisposabler.

   1: public static class EnumerableExtensions
   2: {
   3:     public static IEnumerable<TItem> WithDisposabler<TItem>(this IDisposable disposable, IEnumerable<TItem> items)
   4:     {
   5:         if (disposable == null)
   6:         {
   7:             throw new ArgumentNullException("disposable");
   8:         }
   9:  
  10:         if (items == null)
  11:         {
  12:             throw new ArgumentNullException("items");
  13:         }
  14:         
  15:         return new DisposableEnumerable<TItem>(disposable, items);
  16:     }
  17:  
  18:     private class DisposableEnumerator<TItem> : IEnumerator<TItem>
  19:     {
  20:         private readonly IDisposable disposable;
  21:         private readonly IEnumerator<TItem> items;
  22:  
  23:         public DisposableEnumerator(IDisposable disposable, IEnumerator<TItem> items)
  24:         {
  25:             this.disposable = disposable;
  26:             this.items = items;
  27:         }
  28:  
  29:         public void Dispose()
  30:         {

31: this.items.Dispose();

  32:             this.disposable.Dispose();
  33:         }
  34:  
  35:         public bool MoveNext()
  36:         {
  37:             return this.items.MoveNext();
  38:         }
  39:  
  40:         public void Reset()
  41:         {
  42:             this.items.Reset();                
  43:         }
  44:  
  45:         public TItem Current
  46:         {
  47:             get
  48:             {
  49:                 return this.items.Current;
  50:             }
  51:         }
  52:  
  53:         object IEnumerator.Current
  54:         {
  55:             get
  56:             {
  57:                 return Current;
  58:             }
  59:         }
  60:     }
  61:  
  62:     private class DisposableEnumerable<TItem> : IEnumerable<TItem>
  63:     {
  64:  
  65:         private readonly IDisposable disposable;
  66:         private readonly IEnumerable<TItem> items;
  67:  
  68:         public DisposableEnumerable(IDisposable disposable, IEnumerable<TItem> items)
  69:         {
  70:             this.disposable = disposable;
  71:             this.items = items;
  72:         }          
  73:  
  74:         public IEnumerator<TItem> GetEnumerator()
  75:         {
  76:             return new DisposableEnumerator<TItem>(this.disposable, items.GetEnumerator());
  77:         }
  78:         
  79:         IEnumerator IEnumerable.GetEnumerator()
  80:         {
  81:             return this.GetEnumerator();
  82:         }
  83:     }
  84: }

Щось схоже пішло в продакшен. Подивимось!

Помічено як: , , , ,
 

Коментарі

# slash said:

Миша, а почему DisposableEnumerator не реализует IDisposable и где код который вызывает метод Dispose, в Finalizer к примеру, если это было удалено для уменьшения размера кода в примере, тогда понятно)))

06 January 09 at 4:15 AM
# Mike Chaliy said:

>> почему DisposableEnumerator не реализует IDisposable

Навіщо? IEnumerator<T> вже це робить. Я просто користуюсь тим що є.

>>  где код который вызывает метод Dispose, в Finalizer к примеру

Тут не потрібен фіналайзер. Фіналазер потрібен тільки коли є пряме посилання на неконтрольовані ресурси. Коли воно в обгортці то це не потрібно.

>>  если это было удалено для уменьшения размера кода в примере, тогда понятно)))

Це продакшен код, дотогож працюючий зі складними, з точки зору рнесурсів, об'єктами ШареПоінт. А отже я тричі первірив що все ок.

06 January 09 at 7:04 AM
Анонімні коментарі деактивовані. Увійдіть або Зареєструйтесь щоб мати доступ до ресурсів Спільноти.

About Mike Chaliy

Цікавлюсь DDD. Більшість часу витрачаю на доньку. А ще вел!