Unity 容器性能优于直接数据访问 - 一个很大的区别

Unity container performance over direct data access - a big difference

我有一个包含大约 30 个项目的解决方案,其中大部分使用 Microsoft Unity 作为容器。

对于此测试,我在不同区域和不同网络中使用远程 Azure SQL 数据库,因此我预计响应会延迟,但这不会影响此测试。

让我们计算使用 Unity 的数据访问时间和使用 DbContext 的直接数据访问时间,这里是平均计算时间(以毫秒为单位):

Unity Container
8749
5757
7225
7072
7256
8791
7016
7465
8449
10741
7852.1 (average)

DbContext
3599
2239
2902
2378
1898
1682
1692
1522
2773
2054
2273.9 (average)

因此,使用统一容器访问数据花费了 7852.1(平均)毫秒,同时使用 DbContext 访问数据花费了 2273.9(平均)毫秒。这是一个很大的性能瓶颈,你不觉得吗?

让我分享一些代码片段,这将展示我如何在项目中使用 Unity。

项目中的 Unity 配置如下所示:

public class UnityConfig
    {
        private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            RegisterTypes(container);
            return container;
        });

        public static IUnityContainer GetConfiguredContainer()
        {
            return container.Value;
        }

        public static void RegisterTypes(IUnityContainer container)
        {
            //// Repositories
            container.RegisterType<ICartRepository, CartRepository>();
            // .... total 50 repositories registrations ....

            //// Services
            container.RegisterType<ICartService, CartService>();
            // .... total 72 services registrations ....
        }
    }


public static class UnityWebActivator
{
    public static void Start()
    {
        var container = UnityConfig.GetConfiguredContainer();
        FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
        FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));
        DependencyResolver.SetResolver(new UnityDependencyResolver(container));

        // TODO: Uncomment if you want to use PerRequestLifetimeManager
        // Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
    }

    public static void Shutdown()
    {
        var container = UnityConfig.GetConfiguredContainer();
        container.Dispose();
    }
}

这是与 Unity 一起使用的示例存储库和示例服务:

public interface ICartRepository
{
    Cart Get(string id);
    IEnumerable<Cart> GetAll();
    // more codes
}

public class CartRepository : ICartRepository
{
    [Dependency]
    public ApplicationData db { get; set; }

    public Cart Get(string id)
    {
        return db.Carts.AsNoTracking().Where(i => i.Id == id && i.IsDeleted == 0).FirstOrDefault();
    }

    public IEnumerable<Cart> GetAll()
    {
        return db.Carts.AsNoTracking().Where(i => i.IsDeleted == 0);
    }
    // more codes
}

public interface ICartService
{
    Cart Get(string id, string orgid);
    IEnumerable<Cart> GetAll(string orgid);
    // more codes
}

public class CartService : ICartService
{
    private ICartRepository cartRepository;

    public CartService(ICartRepository _cartRepository)
    {
        cartRepository = _cartRepository;
    }

    public Cart Get(string id, string orgid)
    {
        return cartRepository.GetAll().Where(i => i.OrganizationId == orgid && i.Id == id).FirstOrDefault();
    }

    public IEnumerable<Cart> GetAll(string orgid)
    {
        return cartRepository.GetAll().Where(i => i.OrganizationId == orgid);
    }
    // more codes
}

在我使用它们的项目中:

public class HomeController : Controller
{
    private ICartService cartService;

    public HomeController(ICartService _cartService)
    {
        cartService = _cartService;
    }

    public ActionResult Index()
    {
        // through unity
        var item = cartService.Get("id", "org_id");

        // direct DbContext
        ApplicationData data = new ApplicationData();
        var item1 = data.Carts.AsNoTracking().Where(i => i.Id == "id" && i.OrganizationId == "org_id").FirstOrDefault();
        // more code

        return View();
    }
}

这就是我们在应用程序中使用的所有内容。您是否看到可以更改以提高性能的任何内容?

原因是您比较的两种方式完全不同,而不是因为 Unity。第一种方法:

var item = cartService.Get("id", "org_id");

实现为

return cartRepository.GetAll().Where(i => i.OrganizationId == orgid && i.Id == id).FirstOrDefault()

其中 GetAll 是:

public IEnumerable<Cart> GetAll()
{
    return db.Carts.AsNoTracking().Where(i => i.IsDeleted == 0);
}

因为 GetAll return 类型是 IEnumerable<Cart> - cartRepository.GetAll().Where(...) 不会 过滤掉数据库中的购物车。相反,整个 Cart table 被 SQL 查询拉入内存,如 select * from Cart where IsDeleted = 0。然后执行 Where 并在内存 中找到目标购物车(按组织和 ID)。当然,这是非常低效的(因为它将整个 table 从您的远程数据库传输到您的机器)并且比另一个 appoach 花费更多的时间,后者:

data.Carts.AsNoTracking().Where(i => i.Id == "id" && i.OrganizationId == "org_id").FirstOrDefault();

这个产生 SQL 你期望的,就像 select top 1 * from Cart where IsDeleted = 0 and Id = @id and OrganizationId = @org_id,所有过滤都发生在数据库中,然后通过网络只传输一行。

要修复 - 将您的 GetAll(和其他类似方法)更改为 return IQueryable:

public IQueryable<Cart> GetAll()
{
    return db.Carts.AsNoTracking().Where(i => i.IsDeleted == 0);
}