ObjectRepository -. Patrwm cadwrfa mewn cof NET ar gyfer eich prosiectau cartref

Pam storio'r holl ddata yn y cof?

Er mwyn storio data gwefan neu gefn, awydd cyntaf y rhan fwyaf o bobl gall fydd dewis cronfa ddata SQL. 

Ond weithiau daw'r meddwl i'r meddwl nad yw'r model data yn addas ar gyfer SQL: er enghraifft, wrth adeiladu graff chwilio neu gymdeithasol, mae angen i chi chwilio am berthnasoedd cymhleth rhwng gwrthrychau. 

Y sefyllfa waethaf yw pan fyddwch chi'n gweithio mewn tîm ac nid yw cydweithiwr yn gwybod sut i adeiladu ymholiadau cyflym. Faint o amser wnaethoch chi ei dreulio yn datrys problemau N+1 ac adeiladu mynegeion ychwanegol fel y byddai'r SELECT ar y brif dudalen yn cwblhau mewn cyfnod rhesymol o amser?

Dull poblogaidd arall yw NoSQL. Sawl blwyddyn yn ôl roedd llawer o hype o gwmpas y pwnc hwn - ar gyfer unrhyw achlysur cyfleus fe wnaethant ddefnyddio MongoDB ac roeddent yn hapus gyda'r atebion ar ffurf dogfennau json (gyda llaw, faint o faglau oedd yn rhaid i chi eu gosod oherwydd y dolenni cylchol yn y dogfennau?).

Rwy'n awgrymu rhoi cynnig ar ddull amgen arall - beth am geisio storio'r holl ddata yng nghof y cymhwysiad, gan ei gadw o bryd i'w gilydd i storfa ar hap (ffeil, cronfa ddata o bell)? 

Mae'r cof wedi dod yn rhad, a bydd unrhyw ddata posibl ar gyfer y rhan fwyaf o brosiectau bach a chanolig yn ffitio i mewn i 1 GB o gof. (Er enghraifft, fy hoff brosiect cartref yw traciwr ariannol, sy'n cadw ystadegau dyddiol a hanes fy nhreuliau, balansau, a thrafodion am flwyddyn a hanner, yn defnyddio dim ond 45 MB o gof.)

Manteision:

  • Mae mynediad at ddata yn dod yn haws - nid oes angen i chi boeni am ymholiadau, llwytho diog, nodweddion ORM, rydych chi'n gweithio gyda gwrthrychau C# cyffredin;
  • Nid oes unrhyw broblemau'n gysylltiedig â mynediad o wahanol edafedd;
  • Cyflym iawn - dim ceisiadau rhwydwaith, dim cyfieithu cod i iaith ymholiad, dim angen (dad)cyfresoli gwrthrychau;
  • Mae'n dderbyniol storio data mewn unrhyw ffurf - boed yn XML ar ddisg, neu yn SQL Server, neu yn Azure Table Storage.

Cons:

  • Mae graddio llorweddol yn cael ei golli, ac o ganlyniad, ni ellir defnyddio sero amser segur;
  • Os bydd y cais yn chwalu, efallai y byddwch yn colli data yn rhannol. (Ond nid yw ein cais byth yn damwain, iawn?)

Sut mae'n gweithio?

Mae'r algorithm fel a ganlyn:

  • Ar y dechrau, sefydlir cysylltiad â'r storfa ddata, a chaiff data ei lwytho;
  • Mae model gwrthrych, mynegeion cynradd, a mynegeion perthynol (1:1, 1: Llawer) yn cael eu hadeiladu;
  • Mae tanysgrifiad yn cael ei greu ar gyfer newidiadau mewn priodweddau gwrthrych (INotifyPropertyChanged) ac ar gyfer ychwanegu neu ddileu elfennau i'r casgliad (INotifyCollectionChanged);
  • Pan fydd y tanysgrifiad yn cael ei sbarduno, mae'r gwrthrych wedi'i newid yn cael ei ychwanegu at y ciw ar gyfer ysgrifennu i'r storfa ddata;
  • Mae newidiadau i'r storfa yn cael eu cadw o bryd i'w gilydd (ar amserydd) mewn edefyn cefndir;
  • Pan fyddwch chi'n gadael y rhaglen, mae newidiadau hefyd yn cael eu cadw i'r storfa.

Enghraifft Cod

Ychwanegu'r dibyniaethau angenrheidiol

// Основная библиотека
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

Rydym yn disgrifio'r model data a fydd yn cael ei storio yn y storfa

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; }
}

Yna model y gwrthrych:

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;
}

Ac yn olaf, y dosbarth ystorfa ei hun ar gyfer cyrchu data:

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();
    }
}

Creu enghraifft ObjectRepository:

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

Os bydd y prosiect yn defnyddio HangFire

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

Wrthi'n mewnosod gwrthrych newydd:

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

Gyda'r alwad hon, y gwrthrych ParentModel yn cael ei ychwanegu at y storfa leol a'r ciw ar gyfer ysgrifennu i'r gronfa ddata. Felly, mae'r llawdriniaeth hon yn cymryd O(1), a gellir gweithio gyda'r gwrthrych hwn ar unwaith.

Er enghraifft, i ddod o hyd i'r gwrthrych hwn yn y gadwrfa a gwirio bod y gwrthrych a ddychwelwyd yr un peth:

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

Beth sy'n digwydd? Gosod () yn dychwelyd Geiriadur Tabl, sy'n cynnwys Geiriadur Cydamserol ac yn darparu ymarferoldeb mynegeion cynradd ac uwchradd ychwanegol. Mae hyn yn caniatáu ichi gael dulliau ar gyfer chwilio yn ôl Id (neu fynegeion defnyddwyr mympwyol eraill) heb ailadrodd yn llwyr dros yr holl wrthrychau.

Wrth ychwanegu gwrthrychau at ObjectRepository ychwanegir tanysgrifiad i newid eu priodweddau, felly mae unrhyw newid mewn priodweddau hefyd yn golygu bod y gwrthrych hwn yn cael ei ychwanegu at y ciw ysgrifennu. 
Mae diweddaru eiddo o'r tu allan yn edrych yr un peth â gweithio gyda gwrthrych POCO:

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

Gallwch ddileu gwrthrych yn y ffyrdd canlynol:

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

Mae hyn hefyd yn ychwanegu'r gwrthrych at y ciw dileu.

Sut mae arbed yn gweithio?

ObjectRepository pan fydd gwrthrychau a fonitrir yn newid (naill ai ychwanegu neu ddileu, neu newid priodweddau), yn codi digwyddiad Model Wedi'i Newidtanysgrifio i IStorio. Gweithrediadau IStorio pan fydd digwyddiad yn digwydd Model Wedi'i Newid mae newidiadau yn cael eu rhoi mewn 3 ciw - ar gyfer ychwanegu, ar gyfer diweddaru, ac ar gyfer dileu.

Hefyd gweithrediadau IStorio ar gychwyn, maent yn creu amserydd sy'n achosi newidiadau i gael eu cadw bob 5 eiliad. 

Yn ogystal, mae API i orfodi galwad arbed: ObjectRepository.Cadw().

Cyn pob arbediad, mae gweithrediadau diystyr yn cael eu tynnu o'r ciwiau yn gyntaf (er enghraifft, digwyddiadau dyblyg - pan gafodd gwrthrych ei newid ddwywaith neu ychwanegu / tynnu gwrthrychau'n gyflym), a dim ond wedyn y arbediad ei hun. 

Ym mhob achos, mae'r gwrthrych cerrynt cyfan yn cael ei gadw, felly mae'n bosibl bod gwrthrychau yn cael eu cadw mewn trefn wahanol i'r hyn y cawsant eu newid, gan gynnwys fersiynau mwy newydd o wrthrychau nag ar yr adeg y cawsant eu hychwanegu at y ciw.

Beth arall sydd yna?

  • Mae pob llyfrgell yn seiliedig ar .NET Standard 2.0. Gellir ei ddefnyddio mewn unrhyw brosiect NET modern.
  • Mae'r API yn edau ddiogel. Gweithredir casgliadau mewnol yn seiliedig ar Geiriadur Cydamserol, mae gan y rhai sy'n trin digwyddiadau gloeon neu nid oes eu hangen arnynt. 
    Yr unig beth sy'n werth ei gofio yw galw ObjectRepository.Save();
  • Mynegeion mympwyol (angen unigrywiaeth):

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

Pwy sy'n ei ddefnyddio?

Yn bersonol, dechreuais ddefnyddio'r dull hwn ym mhob prosiect hobi oherwydd ei fod yn gyfleus ac nid oes angen treuliau mawr ar gyfer ysgrifennu haen mynediad data neu ddefnyddio seilwaith trwm. Yn bersonol, mae storio data mewn liteb neu ffeil fel arfer yn ddigon i mi. 

Ond yn y gorffennol, pan oedd y cwmni newydd sydd bellach wedi darfod, EscapeTeams (Roeddwn i'n meddwl dyma fo, arian - ond na, profiad eto) - a ddefnyddir i storio data yn Azure Table Storage.

Cynlluniau ar gyfer y dyfodol

Hoffwn drwsio un o brif anfanteision y dull hwn - graddio llorweddol. I wneud hyn, mae angen naill ai trafodion dosranedig (sic!), neu wneud penderfyniad cryf na ddylai'r un data o wahanol achosion newid, neu adael iddynt newid yn unol â'r egwyddor “pwy sydd olaf sy'n iawn.”

O safbwynt technegol, rwy’n gweld y cynllun canlynol mor bosibl:

  • Storio EventLog a Snapshot yn lle model gwrthrych
  • Dod o hyd i achosion eraill (ychwanegu diweddbwyntiau pob achos at y gosodiadau? darganfod udp? meistr/caethwas?)
  • Dyblygwch rhwng achosion EventLog trwy unrhyw algorithm consensws, megis RAFT.

Mae problem arall hefyd sy'n fy mhoeni - dileu rhaeadru, neu ganfod achosion o ddileu gwrthrychau sydd â chysylltiadau o wrthrychau eraill. 

Cod ffynhonnell

Os ydych chi wedi darllen yr holl ffordd i yma, yna'r cyfan sydd ar ôl yw darllen y cod; mae i'w gael ar GitHub:
https://github.com/DiverOfDark/ObjectRepository

Ffynhonnell: hab.com

Ychwanegu sylw