ObjectRepository - .NET የማህደረ ትውስታ ማከማቻ ንድፍ ለቤትዎ ፕሮጀክቶች

ለምንድነው ሁሉንም ውሂብ በማህደረ ትውስታ ውስጥ ያከማቻል?

ድህረ ገጽን ወይም የጀርባ መረጃን ለማከማቸት የብዙ ጤናማ ሰዎች የመጀመሪያ ፍላጎት የ SQL ዳታቤዝ መምረጥ ይሆናል። 

ነገር ግን አንዳንድ ጊዜ ሀሳቡ ወደ አእምሮው ይመጣል የውሂብ ሞዴል ለ SQL ተስማሚ አይደለም: ለምሳሌ, ፍለጋ ወይም ማህበራዊ ግራፍ ሲገነቡ, በእቃዎች መካከል ውስብስብ ግንኙነቶችን መፈለግ ያስፈልግዎታል. 

በጣም መጥፎው ሁኔታ በቡድን ውስጥ ሲሰሩ እና የስራ ባልደረባዎ ፈጣን መጠይቆችን እንዴት እንደሚገነቡ አያውቅም. በዋናው ገጽ ላይ ያለው SELECT በተመጣጣኝ ጊዜ እንዲጠናቀቅ የ N+1 ችግሮችን ለመፍታት እና ተጨማሪ ኢንዴክሶችን በመገንባት ምን ያህል ጊዜ አሳልፈዋል?

ሌላው ታዋቂ አቀራረብ NoSQL ነው. ከበርካታ አመታት በፊት በዚህ ርዕስ ዙሪያ ብዙ ማበረታቻዎች ነበሩ - ለማንኛውም ምቹ አጋጣሚ MongoDB ን ያሰማሩ እና በ json ሰነዶች መልክ በመልሶቹ ተደስተዋል (በነገራችን ላይ በሰነዶቹ ውስጥ ባሉት ክብ ማያያዣዎች ምክንያት ስንት ክራንች ማስገባት ነበረብህ?).

ሌላ ለመሞከር ሀሳብ አቀርባለሁ አማራጭ ዘዴ - ለምን ሁሉንም ውሂብ በመተግበሪያ ማህደረ ትውስታ ውስጥ ለማከማቸት, በየጊዜው ወደ የዘፈቀደ ማከማቻ (ፋይል, የርቀት ዳታቤዝ) ያስቀምጡት? 

ማህደረ ትውስታ ርካሽ ሆኗል እና ለአብዛኛዎቹ ትናንሽ እና መካከለኛ ፕሮጀክቶች ማንኛውም ሊሆን የሚችል መረጃ ከ 1 ጂቢ ማህደረ ትውስታ ጋር ይጣጣማል። (ለምሳሌ፣ የእኔ ተወዳጅ የቤት ፕሮጀክት ነው። የፋይናንስ መከታተያለአንድ ዓመት ተኩል የዕለት ተዕለት ስታቲስቲክስ እና የወጪዎቼን፣ የሒሳቦቼን እና የግብይቶቼን ታሪክ የሚያቆይ፣ የሚፈጀው 45 ሜባ ማህደረ ትውስታ ብቻ ነው።)

ምርቶች

  • የውሂብ መዳረሻ ቀላል ይሆናል - ስለ መጠይቆች መጨነቅ አያስፈልገዎትም, ሰነፍ ጭነት, ORM ባህሪያት, እርስዎ ተራ C # ነገሮች ጋር ይሰራሉ;
  • ከተለያዩ ክሮች ከመድረስ ጋር የተያያዙ ምንም ችግሮች የሉም;
  • በጣም ፈጣን - ምንም የአውታረ መረብ ጥያቄዎች የለም ፣ ኮድ ወደ መጠይቅ ቋንቋ የለም ፣ የነገሮችን ተከታታይነት (de) አያስፈልግም;
  • በማንኛውም መልኩ መረጃን ማከማቸት ተቀባይነት አለው - በኤክስኤምኤል በዲስክ ፣ ወይም በ SQL አገልጋይ ፣ ወይም በ Azure Table Storage ውስጥ።

Cons:

  • አግድም ልኬት ጠፍቷል, እና በውጤቱም, ዜሮ የእረፍት ጊዜ ማሰማራት አይቻልም;
  • አፕሊኬሽኑ ከተሰናከለ ውሂቡን በከፊል ሊያጡ ይችላሉ። (ነገር ግን የእኛ መተግበሪያ በጭራሽ አይበላሽም ፣ አይደል?)

ይህ የሚሠራው እንዴት ነው?

ስልተ ቀመር እንደሚከተለው ነው

  • በመነሻ ጊዜ ከውሂብ ማከማቻ ጋር ግንኙነት ይመሰረታል, እና ውሂብ ይጫናል;
  • የነገሮች ሞዴል፣ የመጀመሪያ ደረጃ ኢንዴክሶች እና ተዛማጅ ኢንዴክሶች (1፡1፣ 1፡ብዙ) ተገንብተዋል።
  • በነገር ንብረቶች ላይ ለሚደረጉ ለውጦች (INotifyPropertyChanged) እና ክፍሎችን ለመጨመር ወይም ለማስወገድ (INotifyCollectionChanged) ተፈጠረ።
  • የደንበኝነት ምዝገባው ሲቀሰቀስ, የተለወጠው ነገር ወደ የውሂብ ማከማቻ ለመጻፍ ወደ ወረፋው ይጨመራል;
  • በክምችት ላይ የተደረጉ ለውጦች በየጊዜው (በሰዓት ቆጣሪ ላይ) በጀርባ ክር ውስጥ ይቀመጣሉ;
  • ከመተግበሪያው ሲወጡ ለውጦች እንዲሁ ወደ ማከማቻው ይቀመጣሉ።

የኮድ ምሳሌ

አስፈላጊ የሆኑትን ጥገኞች መጨመር

// Основная библиотека
Install-Package OutCode.EscapeTeams.ObjectRepository
    
// Хранилище данных, в котором будут сохраняться изменения
// Используйте то, которым будете пользоваться.
Install-Package OutCode.EscapeTeams.ObjectRepository.File
Install-Package OutCode.EscapeTeams.ObjectRepository.LiteDb
Install-Package OutCode.EscapeTeams.ObjectRepository.AzureTableStorage
    
// Опционально - если нужно хранить модель данных для Hangfire
// Install-Package OutCode.EscapeTeams.ObjectRepository.Hangfire

በማከማቻው ውስጥ የሚከማቸውን የውሂብ ሞዴል እንገልፃለን

public class ParentEntity : BaseEntity
{
    public ParentEntity(Guid id) => Id = id;
}
    
public class ChildEntity : BaseEntity
{
    public ChildEntity(Guid id) => Id = id;
    public Guid ParentId { get; set; }
    public string Value { get; set; }
}

ከዚያ የነገሩ ሞዴል፡-

public class ParentModel : ModelBase
{
    public ParentModel(ParentEntity entity)
    {
        Entity = entity;
    }
    
    public ParentModel()
    {
        Entity = new ParentEntity(Guid.NewGuid());
    }
    
    public Guid? NullableId => null;
    
    // Пример связи 1:Many
    public IEnumerable<ChildModel> Children => Multiple<ChildModel>(x => x.ParentId);
    
    protected override BaseEntity Entity { get; }
}
    
public class ChildModel : ModelBase
{
    private ChildEntity _childEntity;
    
    public ChildModel(ChildEntity entity)
    {
        _childEntity = entity;
    }
    
    public ChildModel() 
    {
        _childEntity = new ChildEntity(Guid.NewGuid());
    }
    
    public Guid ParentId
    {
        get => _childEntity.ParentId;
        set => UpdateProperty(() => _childEntity.ParentId, value);
    }
    
    public string Value
    {
        get => _childEntity.Value;
        set => UpdateProperty(() => _childEntity.Value, value
    }
    
    // Доступ с поиском по индексу
    public ParentModel Parent => Single<ParentModel>(ParentId);
    
    protected override BaseEntity Entity => _childEntity;
}

እና በመጨረሻም ፣ መረጃን ለማግኘት የማከማቻ ክፍል ራሱ፡-

public class MyObjectRepository : ObjectRepositoryBase
{
    public MyObjectRepository(IStorage storage) : base(storage, NullLogger.Instance)
    {
        IsReadOnly = true; // Для тестов, позволяет не сохранять изменения в базу
    
        AddType((ParentEntity x) => new ParentModel(x));
        AddType((ChildEntity x) => new ChildModel(x));
    
        // Если используется Hangfire и необходимо хранить модель данных для Hangfire в ObjectRepository
        // this.RegisterHangfireScheme(); 
    
        Initialize();
    }
}

የ ObjectRepository ምሳሌ ይፍጠሩ፡

var memory = new MemoryStream();
var db = new LiteDatabase(memory);
var dbStorage = new LiteDbStorage(db);
    
var repository = new MyObjectRepository(dbStorage);
await repository.WaitForInitialize();

ፕሮጀክቱ HangFireን የሚጠቀም ከሆነ

public void ConfigureServices(IServiceCollection services, ObjectRepository objectRepository)
{
    services.AddHangfire(s => s.UseHangfireStorage(objectRepository));
}

አዲስ ነገር ማስገባት;

var newParent = new ParentModel()
repository.Add(newParent);

በዚህ ጥሪ, እቃው የወላጅ ሞዴል በሁለቱም የአካባቢ መሸጎጫ እና ወደ የውሂብ ጎታ ለመጻፍ ወረፋው ላይ ተጨምሯል. ስለዚህ, ይህ ክዋኔ O (1) ይወስዳል, እና ይህ ነገር ወዲያውኑ ሊሠራ ይችላል.

ለምሳሌ፣ ይህንን ዕቃ በማከማቻው ውስጥ ለማግኘት እና የተመለሰው ነገር ተመሳሳይ ምሳሌ መሆኑን ለማረጋገጥ፡-

var parents = repository.Set<ParentModel>();
var myParent = parents.Find(newParent.Id);
Assert.IsTrue(ReferenceEquals(myParent, newParent));

ምን እየተፈጠረ ነው? አዘጋጅ () ይመለሳል የጠረጴዛ መዝገበ ቃላት, በውስጡ የያዘው ተመሳሳይ መዝገበ ቃላት እና የመጀመሪያ እና ሁለተኛ ደረጃ ኢንዴክሶች ተጨማሪ ተግባራትን ያቀርባል. ይህ በሁሉም ነገሮች ላይ ሙሉ በሙሉ ሳይደጋገሙ በመታወቂያ (ወይም ሌላ የዘፈቀደ የተጠቃሚ ኢንዴክሶች) የመፈለጊያ ዘዴዎችን እንዲኖርዎት ያስችልዎታል።

እቃዎችን ወደ ላይ ሲጨምሩ ObjectRepository ንብረታቸውን ለመለወጥ የደንበኝነት ምዝገባ ይታከላል፣ ስለዚህ በንብረት ላይ የሚደረግ ማንኛውም ለውጥ ይህ ነገር ወደ ፅሁፍ ወረፋ እንዲታከል ያደርገዋል። 
ከውጪ የሚመጡ ንብረቶችን ማዘመን ከPOCO ነገር ጋር አንድ አይነት ይመስላል፡-

myParent.Children.First().Property = "Updated value";

አንድን ነገር በሚከተሉት መንገዶች መሰረዝ ይችላሉ።

repository.Remove(myParent);
repository.RemoveRange(otherParents);
repository.Remove<ParentModel>(x => !x.Children.Any());

ይህ ደግሞ ዕቃውን ወደ ስረዛ ወረፋ ያክላል።

ቁጠባ እንዴት ይሰራል?

ObjectRepository ቁጥጥር የሚደረግባቸው ነገሮች ሲቀየሩ (ወይ መጨመር ወይም መሰረዝ ወይም ንብረቶችን ሲቀይሩ) አንድ ክስተት ያስነሳል። ሞዴል ተቀይሯል።ተመዝግቧል ማከማቻ. ትግበራዎች ማከማቻ አንድ ክስተት ሲከሰት ሞዴል ተቀይሯል። ለውጦች በ 3 ወረፋዎች ውስጥ ይቀመጣሉ - ለመጨመር ፣ ለማዘመን እና ለመሰረዝ።

እንዲሁም ትግበራዎች ማከማቻ ሲጀመር በየ 5 ሰከንድ ለውጦች እንዲቀመጡ የሚያደርግ ሰዓት ቆጣሪ ይፈጥራሉ። 

በተጨማሪም፣ የማስቀመጫ ጥሪን ለማስገደድ ኤፒአይ አለ፡- ObjectRepository.አስቀምጥ().

ከእያንዳንዱ ቆጣቢ በፊት ትርጉም የለሽ ክዋኔዎች በመጀመሪያ ከወረፋው ይወገዳሉ (ለምሳሌ ፣ የተባዙ ክስተቶች - አንድ ነገር ሁለት ጊዜ ሲቀየር ወይም በፍጥነት ሲጨመር ወይም ሲወገድ) እና ከዚያ በኋላ ራሱ ብቻ። 

በሁሉም ሁኔታዎች, አሁን ያለው ነገር በሙሉ ተቀምጧል, ስለዚህ እቃዎች ወደ ወረፋው ከተጨመሩበት ጊዜ ይልቅ አዳዲስ ስሪቶችን ጨምሮ, ከተቀየሩት በተለየ ቅደም ተከተል ሊቀመጡ ይችላሉ.

ሌላ ምን አለ?

  • ሁሉም ቤተ-መጻሕፍት በ NET Standard 2.0 ላይ የተመሰረቱ ናቸው. በማንኛውም ዘመናዊ የ NET ፕሮጀክት ውስጥ መጠቀም ይቻላል.
  • ኤፒአይ ደህንነቱ የተጠበቀ ነው። የውስጥ ክምችቶች በመሠረት ይተገበራሉ ተመሳሳይ መዝገበ ቃላት፣ የክስተት ተቆጣጣሪዎች ወይ መቆለፊያ አላቸው ወይም አያስፈልጋቸውም። 
    ሊታወስ የሚገባው ብቸኛው ነገር መደወል ነው ObjectRepository.አስቀምጥ();
  • የዘፈቀደ ኢንዴክሶች (ልዩነት ያስፈልገዋል)

repository.Set<ChildModel>().AddIndex(x => x.Value);
repository.Set<ChildModel>().Find(x => x.Value, "myValue");

ማነው የሚጠቀመው?

በግሌ ይህንን አካሄድ በሁሉም የትርፍ ጊዜ ማሳለፊያ ፕሮጀክቶች ውስጥ መጠቀም ጀመርኩ ምክንያቱም ምቹ እና የመረጃ ተደራሽነት ንብርብር ለመፃፍ ወይም ከባድ መሠረተ ልማትን ለማሰማራት ብዙ ወጪዎችን አያስፈልገውም። በግሌ፣ በlitb ወይም ፋይል ውስጥ ውሂብ ማከማቸት ብዙ ጊዜ ለእኔ በቂ ነው። 

ነገር ግን ቀደም ባሉት ጊዜያት፣ አሁን የቆመው ጅምር EscapeTeams (እ.ኤ.አ.)እዚህ ነው ብዬ አሰብኩ ገንዘብ - ግን አይሆንም, እንደገና ልምድ) - በ Azure Table Storage ውስጥ መረጃን ለማከማቸት ያገለግላል.

ለወደፊቱ እቅድ

የዚህ አቀራረብ ዋና ዋና ጉዳቶችን አንዱን ማስተካከል እፈልጋለሁ - አግድም አግድም. ይህንን ለማድረግ ወይም የተከፋፈሉ ግብይቶች (sic!) ያስፈልግዎታል ወይም ከተለያዩ አጋጣሚዎች ተመሳሳይ መረጃ መለወጥ እንደሌለበት በጠንካራ ፍላጎት ውሳኔ ያድርጉ ወይም “የመጨረሻው ትክክለኛ ነው” በሚለው መርህ እንዲቀይሩ ያድርጉ።

ከቴክኒካዊ እይታ አንጻር የሚከተለውን እቅድ በተቻለ መጠን አያለሁ፡-

  • ከነገር ሞዴል ይልቅ EventLog እና Snapshot ያከማቹ
  • ሌሎች ሁኔታዎችን ፈልግ (የሁሉም አጋጣሚዎች የመጨረሻ ነጥቦችን ወደ ቅንጅቶቹ ጨምር? udp ግኝት? ዋና/ባሪያ?)
  • እንደ RAFT ባሉ በማንኛውም የጋራ ስምምነት ስልተ ቀመር በ EventLog አጋጣሚዎች መካከል ይድገሙት።

እኔን የሚያሳስበኝ ሌላ ችግር አለ - ካስኬድ መሰረዝ ወይም ከሌሎች ነገሮች ጋር ግንኙነት ያላቸው ነገሮች የተሰረዙ ጉዳዮችን መለየት። 

ምንጭ ኮድ

እስከዚህ ድረስ አንብበው ከሆነ የቀረው ኮዱን ማንበብ ብቻ ነው፡ በ GitHub ላይ ሊገኝ ይችላል፡-
https://github.com/DiverOfDark/ObjectRepository

ምንጭ: hab.com

አስተያየት ያክሉ