2

如何在 Xamarin Forms 中使用 Prism/DryIoC 处理和重新实例化单例?

我正在使用 Azure 移动应用程序来获取离线数据。偶尔,我需要删除本地的sqlite数据库并重新初始化它。不幸的是,MobileServiceClient 偶尔会保持数据库连接打开,并且没有公开方法来关闭它。建议的解决方案 ( https://github.com/Azure/azure-mobile-apps-net-client/issues/379 ) 是处置 MobileServiceClient。唯一的问题是它在 DryIoC 中注册为单例。

我对 DryIoC 或 Prism 和 Forms 并不太熟悉……但就我的一生而言,我看不到这样做的方法。

我确实制定了一个非常复杂的计划,几乎奏效了。

在我的 ViewModel 方法中,当我需要释放数据库时,我触发了一个事件 -

_eventAggregator.GetEvent<RegisterDatabaseEvent>().Publish(false);

然后在 App.xaml.cs 中,我像这样连接了一个侦听器和一个处理程序 -

_eventAggregator.GetEvent<RegisterDatabaseEvent>().Subscribe(OnRegisterDatabaseEventPublished);
private void OnRegisterDatabaseEventPublished()
{
    Container.GetContainer().Unregister<IAppMobileClient>();
    Container.GetContainer().Unregister<IMobileServiceClient>();
    Container.GetContainer().Register<IMobileServiceClient, AppMobileClient>(new SingletonReuse());
    Container.GetContainer().Register<IAppMobileClient, AppMobileClient>(new SingletonReuse());

    _eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Publish(register);
}

最后,回到 ViewModel 构造函数中,我有一个最终侦听器,它处理从 App.xaml 返回的事件并完成处理。

_eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Subscribe(OnRegisterDatabaseCompletedEventPublished);

所以令人惊奇的是,这奏效了。数据库能够被删除,一切都很好。但后来我导航到不同的页面和 BOOM。DryIoC 表示它无法为该页面连接 ViewModel。我假设取消注册/注册会为所有注入提升 DryIoC ......那么我怎样才能完成需要做的事情呢?

最终解决方案

非常感谢爸爸抽出时间提供帮助。你当然是一个集体行为,我现在正在考虑在其他地方使用 DryIoC。

对于任何偶然发现此问题的人,我将在下面发布最终解决方案。我会尽可能详细地避免任何混淆。

首先,在我的 App.xaml.cs 中,我添加了一个注册数据库的方法。

public void RegisterDatabase(IContainer container)
{
    container.RegisterMany<AppMobileClient>(Reuse.Singleton,
        setup: Setup.With(asResolutionCall: true),
        ifAlreadyRegistered: IfAlreadyRegistered.Replace,
        serviceTypeCondition: type =>
            type == typeof(IMobileServiceClient) || type == typeof(IAppMobileClient));
}

我只是在 RegisterTypes 中添加对该方法的调用,而不是直接在其中注册类型。

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.GetContainer().Rules.WithoutEagerCachingSingletonForFasterAccess();
...
    RegisterDatabase(containerRegistry.GetContainer());
...
}

还要注意根据 dadhi 为急切缓存添加的规则。

稍后当我需要在 ViewModel 中释放数据库时...我通过重置本地 db 变量并向 App.xaml.cs 发送事件来开始

_client = null;
_eventAggregator.GetEvent<RegisterDatabaseEvent>().Publish(true);

在 App.xaml.cs 中,我订阅了该事件并将其绑定到以下方法。

private void OnRegisterDatabaseEventPublished()
    {
        RegisterDatabase(Container.GetContainer());

        _eventAggregator.GetEvent<RegisterDatabaseCompletedEvent>().Publish(register);
    }

这里我只是再次调用 RegisterMany,和我在应用程序启动时所做的完全一样。无需注销任何东西。使用 setup 和 ifAlreadyRegistered 参数(谢谢,dadhi!),DryIoC 允许替换对象。然后我向 VM 发起一个事件,让它知道数据库已被释放。

最后,回到 ViewModel,我正在监听完成的事件。该事件的处理程序像这样更新对象的本地副本。

_client = ((PrismApplication)App.Current).Container.Resolve<IAppMobileClient>();

然后我可以根据需要使用新对象。这是关键。如果没有在上面将 _client 设置为 null 并在此处再次解决它,我实际上最终得到了 2 个对象副本,并且对方法的调用被击中了 2 倍。

希望对希望发布其 Azure 移动应用数据库的其他人有所帮助!

4

1 回答 1

1

I am not sure how exactly XF handles these things.

But in DryIoc in order for service to be fully deleted or replaced it need to be registered with setup: Setup.With(asResolutionCall: true). Read here for more details: https://bitbucket.org/dadhi/dryioc/wiki/UnregisterAndResolutionCache#markdown-header-unregister-and-resolution-cache

Update

Here are two options and considerations that work in pure DryIoc and may not work XF. But it probably may help with solution.

    public class Foo
    {
        public IBar Bar { get; private set; }
        public Foo(IBar bar) { Bar = bar; }
    }

    public interface IBar {}
    public class Bar : IBar {}
    public class Bar2 : IBar { }

    [Test]
    public void Replace_singleton_dependency_with_asResolutionCall()
    {
        var c = new Container(rules => rules.WithoutEagerCachingSingletonForFasterAccess());

        c.Register<Foo>();
        //c.Register<Foo>(Reuse.Singleton); // !!! If the consumer of replaced dependency is singleton, it won't work
                                            // cause the consumer singleton should be replaced too

        c.Register<IBar, Bar>(Reuse.Singleton,
            setup: Setup.With(asResolutionCall: true));        // required

        var foo = c.Resolve<Foo>();
        Assert.IsInstanceOf<Bar>(foo.Bar);

        c.Register<IBar, Bar2>(Reuse.Singleton,
            setup: Setup.With(asResolutionCall: true),         // required
            ifAlreadyRegistered: IfAlreadyRegistered.Replace); // required

        var foo2 = c.Resolve<Foo>();
        Assert.IsInstanceOf<Bar2>(foo2.Bar);
    }

    [Test]
    public void Replace_singleton_dependency_with_UseInstance()
    {
        var c = new Container();

        c.Register<Foo>();
        //c.Register<Foo>(Reuse.Singleton); // !!! If the consumer of replaced dependency is singleton, it won't work
                                            // cause the consumer singleton should be replaced too
        c.UseInstance<IBar>(new Bar());
        var foo = c.Resolve<Foo>();
        Assert.IsInstanceOf<Bar>(foo.Bar);

        c.UseInstance<IBar>(new Bar2());
        var foo2 = c.Resolve<Foo>();
        Assert.IsInstanceOf<Bar2>(foo2.Bar);
    }
于 2018-01-20T16:17:09.930 回答