Потребление избыточной памяти рекурсивным компонентом Blazor на стороне сервера - PullRequest
0 голосов
/ 01 октября 2019

У меня есть этот компонент ChainableSelect, который выбран и основан на том, что он генерирует другой ChainableSelect, и так далее. Я заметил, что если я изменил выбор примерно в 7 ~ 10 раз, потребление памяти начинает расти без остановки, заставляя мой ноутбук зависать

Воспроизвести

Шаги для воспроизведения поведения:

  1. Использование этой версии ASP.NET Core '3.0' выпуска

  2. Выполнить этот код

 public class ChainableSelect<TEntity> : InputableBaseComponent where TEntity : DbModel
    {
        [Parameter]
        public IChainableSelectDataFetcher<TEntity> DataFetcher { get; set; }
        [Parameter]
        public string FilterText { get; set; } = "";
        [Parameter]
        public bool IsBusy { get; set; }
        [Parameter]
        public bool FilterChanged { get; set; } = true;
        [Parameter]
        public string ParentPropertyName { get; set; }
        [Parameter]
        public TEntity ParentEntity { get; set; }

        TEntity _localParentEntity;
        IViewProperty _localViewProperty;

        ReadOnlyObservableCollection<TEntity> _entities { get; set; }

        bool _updateEntities = true;

        protected override void OnInitialized()
        {
            base.OnInitialized();
            _localViewProperty = ViewPropertyFactory.CreateEmptyProperty("", null);
        }

        protected override async Task OnParametersSetAsync()
        {
            await base.OnParametersSetAsync();
            if (DataFetcher != null)
            {
                if (_localParentEntity != ParentEntity || ParentEntity == null || FilterChanged)
                {
                    _entities = await DataFetcher.GetDescendents(ParentEntity, ParentPropertyName);
                    _updateEntities = false;
                }
                _localViewProperty.PropertyValue = null;
                FilterChanged = true;
                StateHasChanged();
            }
            _localParentEntity = ParentEntity;
        }

        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            if (_entities == null)
                return;
            if (_entities.Count == 0)
                return;

            var seq = 0;
            base.BuildRenderTree(builder);


            builder.OpenElement(seq, "input");
            builder.AddAttribute(seq, "class", "form-control form-control-sm dropdown-input bg-white");
            builder.AddAttribute(seq, "value", _localViewProperty.PropertyValue ?? "--SELECT--");
            builder.AddAttribute(seq, "readonly", "readonly");
            builder.AddAttribute(seq, "onfocus", "document.getElementById('" + Id + "').style.zIndex=1000");
            builder.CloseElement();

            builder.OpenElement(++seq, "div");
            builder.AddAttribute(seq, "class", "dropdown-div border");
            builder.AddAttribute(seq, "onmouseover", "document.getElementById('" + Id + "').style.zIndex=1000");
            builder.AddAttribute(seq, "onmouseout", "document.getElementById('" + Id + "').style.zIndex=1");

            builder.OpenElement(++seq, "div");
            builder.AddAttribute(seq, "class", "p-1 m-1 bg-white");

            builder.OpenElement(++seq, "input");
            builder.AddAttribute(seq, "class", "form-control form-control-sm");
            builder.AddAttribute(seq, "oninput", new Action<ChangeEventArgs>(OnFilterChanged));
            builder.CloseElement();

            builder.OpenElement(++seq, "hr");
            builder.CloseElement();

            builder.CloseElement();

            builder.OpenElement(++seq, "a");
            builder.AddAttribute(seq, "onclick", new Action(() => OnSelect(null)));
            builder.AddContent(seq, "None");
            builder.CloseElement();

            if (_entities != null)
                foreach (var _entity in _entities)
                {
                    if (!_entity.ToString().Contains(FilterText, StringComparison.InvariantCultureIgnoreCase))
                        continue;
                    builder.OpenElement(++seq, "a");
                    builder.AddAttribute(seq, "onclick", new Action(() => OnSelect(_entity)));
                    if (_entity == _localViewProperty.PropertyValue)
                        builder.AddAttribute(seq, "class", "selected");
                    builder.AddContent(seq, _entity.ToString());
                    builder.CloseElement();
                }

            builder.CloseElement();
            builder.CloseElement();
            FilterChanged = false;

            if (_localViewProperty.PropertyValue != null && _localViewProperty.PropertyValue != ParentEntity)
            {
                builder.OpenComponent<ChainableSelect<TEntity>>(++seq);
                builder.AddAttribute(seq, "Parameters", Parameters);
                builder.AddAttribute(seq, "ParentEntity", ViewProperty.PropertyValue);
                builder.AddAttribute(seq, "ParentPropertyName", ParentPropertyName);
                builder.CloseComponent();
            }
        }

        private async void OnSelect(TEntity entity)
        {
            ViewProperty.PropertyValue = entity;
            _localViewProperty.PropertyValue = entity;
            FilterChanged = true;
            _updateEntities = false;
            StateHasChanged();
        }

        protected override bool ShouldRender()
        {
            return !IsBusy && FilterChanged;
        }

        CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
        private async void OnFilterChanged(ChangeEventArgs e)
        {
            _cancellationTokenSource.Cancel();
            _cancellationTokenSource = new CancellationTokenSource();
            var _cancelationToken = _cancellationTokenSource.Token;
            IsBusy = true;
            await Task.Delay(200, _cancelationToken).ContinueWith(t =>
            {
                if (!_cancelationToken.IsCancellationRequested)
                {
                    FilterText = e.Value.ToString();
                    FilterChanged = true;
                }
                IsBusy = false;
                InvokeAsync(StateHasChanged);
            });
        }

    }

ChainableDataFetcher

public class ChainableEntityDataFetcher<TEntity> : IChainableSelectDataFetcher<TEntity> where TEntity : DbModel
    {
        public Func<TEntity, string, Task<ReadOnlyObservableCollection<TEntity>>> GetDescendents { get; set; }
        private readonly IDataAccessLayer<TEntity> _dataAccessLayer;
        private PropertyInfo _parentProperty;

        public ChainableEntityDataFetcher(IDataAccessLayer<TEntity> dataAccessLayer)
        {
            _dataAccessLayer = dataAccessLayer;
            GetDescendents = DataFetcherAsync;
        }

        public void Dispose()
        {
            _dataAccessLayer.Dispose();
            GetDescendents = null;
            _parentProperty = null;
        }

        async Task<ReadOnlyObservableCollection<TEntity>> DataFetcherAsync(TEntity parentEntity,string parentPropertyName)
        {
            var _comparableValue = parentEntity == null ? " is NULL" : " = " + parentEntity.DbModelId.ToString();
            var _dataList = _dataAccessLayer.GetEntities(new Dapper.CommandDefinition(String.Format("select * from {0} where {1}{2}",
                _dataAccessLayer.TableName,
                parentPropertyName,
                _comparableValue)));

            ObservableCollection<TEntity> _entities = new ObservableCollection<TEntity>(_dataList);
            ReadOnlyObservableCollection<TEntity> _readOnlyEntites = new ReadOnlyObservableCollection<TEntity>(_entities);

            return _readOnlyEntites;
        }


    }

DataAccessLayer

 public class DataAccessLayer<TEntity> : IDataAccessLayer<TEntity> where TEntity : DbModel
    {
        public IDbConnectionBuilder DbConnectionBuilder { get; }
        private readonly string _tableName;

        ...
        public ICollection<TEntity> GetEntities(CommandDefinition command)
        {
            using (var _connection = DbConnectionBuilder.GetDbConnection())
            {
                _connection.Open();
                return _connection.Query<TEntity>(command).ToList();
            }
        }
        ...

Скриншоты

enter image description here

...