简体   繁体   English

NHibernate和上下文实体

[英]NHibernate and contextual entities

I'm trying to use NHibernate for a new app with a legacy database. 我正在尝试将NHibernate用于带有旧数据库的新应用程序。 It's going pretty well but I'm stuck and can't find a good solution for a problem. 它运行得很好,但我陷入困境,无法找到解决问题的好方法。

Let's say I have this model : 假设我有这个模型:

  • a Service table (Id, ServiceName..) 服务表(Id,ServiceName ..)
  • a Movie table (Id, Title, ...) 电影表(Id,Title,...)
  • a Contents table which associates a service and a movie (IdContent, Name, IdMovie, IdService) 内容表,用于关联服务和电影(IdContent,Name,IdMovie,IdService)

So I mapped this and it all went good. 所以我绘制了这个并且一切顺利。 Now I can retrieve a movie, get all the contents associated, ... My app is a movies shop "generator". 现在我可以检索一部电影,获取所有相关的内容,...我的应用程序是电影商店“发电机”。 Each "service" is in fact a different shop, when a user enter my website, he's redirected to one of the shops and obviously, I must show him only movies available for his shop. 每个“服务”实际上是一个不同的商店,当用户进入我的网站时,他被重定向到其中一个商店,显然,我必须向他展示他的商店可用的电影。 The idea is : user comes, his service is recognized, I present him movies which have contents linked to his service. 这个想法是:用户来了,他的服务得到认可,我向他展示了与他的服务相关的内容。 I need to be able to retrieve all contents for a movie for the backoffice too. 我需要能够为后台检索电影的所有内容。 I'm trying to find the most transparent way to accomplish this with NHibernate. 我正在尝试用NHibernate找到最透明的方法来实现这一点。 I can't really make changes to the db model. 我无法真正对db模型进行更改。

I thought about a few solutions : 我想了几个解决方案:

  • Add the service condition into all my queries. 将服务条件添加到我的所有查询中。 Would work but it's a bit cumbersome. 会工作,但它有点麻烦。 The model is very complex and has tons of tables/queries.. 该模型非常复杂,有大量的表/查询..

  • Use nhibernate filter. 使用nhibernate过滤器。 Seemed ideal and worked pretty good, I added the filter on serviceid in all my mappings and did the EnableFilter as soon as my user's service was recognized but.. nhibernate filtered collections don't work with 2nd lvl cache (redis in my case) and 2nd lvl cache usage is mandatory. 看似理想并且工作得很好,我在所有映射中添加了serviceid上的过滤器,并在我的用户服务被识别后立即执行了EnableFilter但是... nhibernate过滤的集合不适用于第二个lvl缓存(在我的情况下是redis)和第二个lvl缓存使用是必需的。

  • Add computed properties to my object like Movie.PublishedContents(Int32 serviceId). 将计算属性添加到我的对象,如Movie.PublishedContents(Int32 serviceId)。 Probably would work but requires to write a lot of code and "pollutes" my domain. 可能会工作但需要编写大量代码并“污染”我的域名。

  • Add new entities inheriting from my nhibernate entity like a PublishedMovie : Movie wich only presents the contextual data 添加从我的nhibernate实体继承的新实体,如PublishedMovie:Movie,它只显示上下文数据

None of these really satisfies me. 这些都不能让我满意。 Is there a good way to do this ? 有没有办法做到这一点?

Thanks ! 谢谢 !

You're asking about multi-tenancy with all the tenants in the same database. 您询问的是同一数据库中所有租户的多租户问题。 I've handled that scenario effectively using Ninject dependency injection. 我使用Ninject依赖注入有效地处理了这种情况。 In my application the tenant is called "manual" and I'll use that in the sample code. 在我的应用程序中,租户被称为“手动”,我将在示例代码中使用它。

The route needs to contain the tenant eg 路线需要包含租户,例如

{manual}/{controller}/{action}/{id}

A constraint can be set on the tenant to limit the allowed tenants. 可以在租户上设置约束以限制允许的租户。

I use Ninject to configure and supply the ISessionFactory as a singleton and ISession in session-per-request strategy. 我使用Ninject配置和提供ISessionFactory作为单个和ISession in session-per-request策略。 This is encapsulated using Ninject Provider classes. 这是使用Ninject Provider类封装的。

I do the filtering using lightweight repository classes, eg 我使用轻量级存储库类进行过滤,例如

public class ManualRepository
{
    private readonly int _manualId;
    private readonly ISession _session;

    public ManualRepository(int manualId, ISession session)
    {
        _manualId = manualId;
        _session = session;
    }

    public IQueryable<Manual> GetManual()
    {
        return _session.Query<Manual>().Where(m => m.ManualId == _manualId);
    }
}

If you want pretty urls you'll need to translate the tenant route parameter into its corresponding database value. 如果你想要漂亮的网址,你需要将租户路由参数转换为相应的数据库值。 I have these set up in web.config and I load them into a dictionary at startup. 我在web.config中设置了这些,并在启动时将它们加载到字典中。 An IRouteConstraint implementation reads the "manual" route value, looks it up, and sets the "manualId" route value. IRouteConstraint实现读取“手动”路由值,查找它,并设置“manualId”路由值。

Ninject can handle injecting the ISession into the repository and the repository into the controller. Ninject可以处理将ISession注入存储库并将存储库注入控制器。 Any queries in the controller actions must be based on the repository method so that the filter is applied. 控制器操作中的任何查询都必须基于存储库方法,以便应用过滤器。 The trick is injecting the manualId from the routing value. 诀窍是从路由值注入manualId。 In NinjectWebCommon I have two methods to accomplish this: 在NinjectWebCommon中,我有两种方法可以完成此任务:

private static int GetManualIdForRequest()
{
    var httpContext = HttpContext.Current;
    var routeValues = httpContext.Request.RequestContext.RouteData.Values;
    if (routeValues.ContainsKey("manualId"))
    {
        return int.Parse(routeValues["manualId"].ToString());
    }
    const string msg = "Route does not contain 'manualId' required to construct object.";
    throw new HttpException((int)HttpStatusCode.BadRequest, msg);
}

/// <summary>
/// Binding extension that injects the manualId from route data values to the ctor.
/// </summary>
private static void WithManualIdConstructor<T>(this IBindingWithSyntax<T> binding)
{
    binding.WithConstructorArgument("manualId", context => GetManualIdForRequest());
}

And the repository bindings are declared to inject the manualId. 并声明存储库绑定以注入manualId。 There may be a better way to accomplish this through conventions. 通过惯例可能有更好的方法来实现这一目标。

kernel.Bind<ManualRepository>().ToSelf().WithManualIdConstructor();

The end result is that queries follow the pattern 最终结果是查询遵循模式

var manual = _manualRepository
    .GetManual()
    .Where(m => m.EffectiveDate <= DateTime.Today)
    .Select(m => new ManualView
    {
        ManualId = m.ManualId,
        ManualName = m.Name
    }).List();

and I don't need to worry about filtering per tenant in my queries. 我不需要担心在我的查询中过滤每个租户。

As for the 2nd level cache, I don't use it in this app but my understanding is that you can set the cache region to segregate tenants. 至于二级缓存,我不在这个应用程序中使用它,但我的理解是你可以设置缓存区域来隔离租户。 This should get you started: http://ayende.com/blog/1708/nhibernate-caching-the-secong-level-cache-space-is-shared 这应该可以帮助您入门: http//ayende.com/blog/1708/nhibernate-caching-the-secong-level-cache-space-is-shared

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM