是否可以直接在 NHibernate 中设置引用的外键?
有一个项目从特定系统收集 XML 文件形式的数据(这些数据作为 Web 请求传入),将其转换为实体模型,并将其填充到数据库中以进行报告。
该项目使用以下软件(与此问题相关):
- C# 4.0 / .Net 4
- NHibernate 3.0(NuGet 中最新的)
- Fluent NHibernate(与 NH 3 一起使用的软件)
假设我有一个像这样的实体(简化) :
public class Incident : Entity
{
public virtual string OriginatorSite { get; set; }
public virtual string DestinationSite { get; set; }
public virtual IncidentType IncidentType { get; set; }
public virtual TimeSpan TotalWaitTime { get; set; }
public virtual TimeSpan TotalActionTime { get; set; }
public virtual DateTime RegisterTime { get; set; }
public virtual DateTime CloseTime { get; set; }
public virtual DateDimension DateDimension { get; set; }
}
这是这样映射的:
public class IncidentMap : ClassMap<Incident>
{
public IncidentMap()
{
Id(c => c.Id);
Map(c => c.OriginatorSite);
Map(c => c.DestinationSite);
Map(c => c.IncidentType).CustomType<IncidentType>();
Map(c => c.TotalWaitTime);
Map(c => c.TotalActionTime);
Map(c => c.RegisterTime);
Map(c => c.CloseTime);
References<DateDimension>(c => c.DateDimension);
}
}
(Id 来自 Entity 基类)
由于那些处理此类事情的人可能已经从代码中读取了内容,所以我尝试在这里进行一些维度建模。我对这个主题很陌生,很可能做错了(tm),但我希望至少能从这种做事方式中获得一些好处;每个事件都引用一个 DateDimension 对象,如下所示:
public class DateDimension : Entity
{
public virtual int DayOfMonth { get; set; }
public virtual int Weekday { get; set; }
public virtual string WeekdayName { get; set; }
public virtual int Week { get; set; }
public virtual int MonthPart { get; set; }
public virtual int Month { get; set; }
public virtual string MonthName { get; set; }
public virtual int Quarter { get; set; }
public virtual int Year { get; set; }
}
DateDimension 表已填充 - 我的系统从未真正在此处创建任何记录。这些是在实际使用之前生成的 - 系统中每个相关日期一行。这是我的系统的功能之一。它承诺每个可能的日期都会有一行。如果其中一个缺失,那将是一场灾难性的失败。
如果您是维度建模的新手,您可能会问为什么要这样做,就像我两天前撰写本文时一样。
嗯,我会有很多每个日期的事件记录。因此,DateDimension 表将比 Incident 表小很多,并允许我使用 NHibernate LINQ 执行原本会很困难的操作。例如这样的事情:
var IncidentsPerWeekday = _incidentRepository
.GroupBy(i => i.DateDimension.Weekday)
.Select(g => new Tuple<int,int>(g.Key, g.Count()))
.ToList();
给我一份组列表,告诉我事件如何在工作日中划分。当然,可以在此处添加许多不同的维度,使我能够围绕一堆不同的参数旋转报告并创建有趣的报告。
然而,有一个小烦恼,我们终于到达真正的问题了。
DateDimension 有一个主键,它基本上是以特定格式表示的日期。对于 2011 年 4 月 30 日,它看起来像这样:
20110430
这是通过在 NHibernate 中使用自定义 IIdentifierGenerator 来完成的。由于每个日期我们只有一条记录,因此我认为这是一种相当干净的方法。
此外,这也是让新事件实体知道其相关 DateDimension 引用的外键的快速方法。有些工厂会知道我们从 Incident.RegisterTime DateTime 中提取此密钥,然后将其填充到 DateDimension_id 列中。
然而,这就是我似乎无法找到的办法。 Incident.DateDimension 正确地要求实体引用。这意味着我必须从数据库加载它(对吗?)。在某些导入场景中,我需要在尽可能短的时间内将大量事件实体插入数据库,这可能会太慢。
当然,对于这个特定的示例,我可能可以通过每次插入实体时执行一些自定义 SQL 来做到这一点,并允许 NULL 引用。这并不理想,但它可以工作。
但是,有没有办法直接指定 DateDimension 引用的外键,而不是使用对真实的存储引用对象的引用来设置它?
这肯定能让我摆脱巨大的头痛!
预先感谢您的任何见解!
Got a project that collects data as XML files from a particular system (these come in as web requests), converts it to an entity model, and stuffs it in a database for reporting.
The project uses the following software (relevant to this question):
- C# 4.0 / .Net 4
- NHibernate 3.0 (the latest available in NuGet)
- Fluent NHibernate (the one that goes along with NH 3)
Say I have an entity like this (simplified):
public class Incident : Entity
{
public virtual string OriginatorSite { get; set; }
public virtual string DestinationSite { get; set; }
public virtual IncidentType IncidentType { get; set; }
public virtual TimeSpan TotalWaitTime { get; set; }
public virtual TimeSpan TotalActionTime { get; set; }
public virtual DateTime RegisterTime { get; set; }
public virtual DateTime CloseTime { get; set; }
public virtual DateDimension DateDimension { get; set; }
}
and this is mapped thusly:
public class IncidentMap : ClassMap<Incident>
{
public IncidentMap()
{
Id(c => c.Id);
Map(c => c.OriginatorSite);
Map(c => c.DestinationSite);
Map(c => c.IncidentType).CustomType<IncidentType>();
Map(c => c.TotalWaitTime);
Map(c => c.TotalActionTime);
Map(c => c.RegisterTime);
Map(c => c.CloseTime);
References<DateDimension>(c => c.DateDimension);
}
}
(The Id comes from the Entity base class)
As those who deal with such things have probably already read from the code, I am trying to do some dimensional modeling here. I am new to the subject, and in all likelyhood Doing It Wrong (tm), but I am hoping at least to get some benefits out of this way of doing it; Each Incident references a DateDimension object that looks like this:
public class DateDimension : Entity
{
public virtual int DayOfMonth { get; set; }
public virtual int Weekday { get; set; }
public virtual string WeekdayName { get; set; }
public virtual int Week { get; set; }
public virtual int MonthPart { get; set; }
public virtual int Month { get; set; }
public virtual string MonthName { get; set; }
public virtual int Quarter { get; set; }
public virtual int Year { get; set; }
}
The DateDimension table is already populated - my system never really creates any records here. These are generated well ahead of actually being used - one row for each relevant date in the system. That is one of the features of my system. It promises one row will be there for each date possible. If one was missing, that would be a catastrophic failure.
Why do this, you might ask, if you're new to dimensional modeling, as I was this two days ago as of writing this.
Well, I will have a LOT of Incident records for each date. So the DateDimension table will be a lot smaller than the Incident table, and allow me to do things with NHibernate LINQ that would otherwise be difficult. For instance something like this:
var IncidentsPerWeekday = _incidentRepository
.GroupBy(i => i.DateDimension.Weekday)
.Select(g => new Tuple<int,int>(g.Key, g.Count()))
.ToList();
Giving me a list of groups telling me how the incidents divide themselves among the weekdays. There are of course many different dimensions that can be added here, allowing me to pivot reports around a bunch of different parameters and create interesting reports.
However, there is one minor annoyance, and here we finally arrive at the real question.
The DateDimension has a primary key which is basically the date it represents in a specific format. For April 30th 2011, it would look like this:
20110430
This is done by using a custom IIdentifierGenerator in NHibernate. Since we'll only have a single record for each date, this is a fairly clean way to do it in my opinion.
Also, it would be a quick way to let new Incident entities know the foreign key of the reference to their relevant DateDimension. Some factory would know that we extract this key from say the Incident.RegisterTime DateTime, then stuff it in the DateDimension_id column.
However, this is what I can't seem to find a way to do. Incident.DateDimension rightfully demands an entity reference. Which means I have to load it from the database (right?). Which could be too slow in certain import scenarios where I need to insert a LOT of Incident entities into the database in the shortest amount of time possible.
Sure, I could probably do this for this particular example by executing some custom SQL each time I insert an entity, and allow NULL references. It's not ideal, but it could work.
However, is there a way to specify the foreign key of the DateDimension reference directly instead of setting it with a reference to a real, stored reference object?
That would certainly rid me of a huge headache!
Thanks in advance for any insight!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您可以使用 ISession.Load 方法来执行此操作。
这将为 DateDimension 创建代理,从而避免数据库访问。顺便说一句,如果您访问代理上除密钥之外的任何属性,它将被加载。
You can do this using the ISession.Load method.
This will create a proxy for DateDimension thus avoiding a database trip. If you access any properties other than the key on the proxy it will be loaded, by the way.
不能 100% 肯定这会起作用,但您可以尝试将引用设置为具有适当 ID 的 DateDimension 类的新实例。这假设您在 Incident 和 DateDimension 之间没有任何级联,我猜这是一个正确的假设,因为 DateDimensions 在您的数据模型中可能是不可变的。
或者,您也可以在映射中公开 FK 列并直接设置它。
Not 100% positive that this will work, but you can try to set your reference to a new instance of a DateDimension class that has the appropriate Id. This assumes you don't have any cascading between Incident and DateDimension, which I'm guessing is a correct assumption since DateDimensions are likely immutable in your data model.
Alternatively you can expose the FK column in your mapping and directly set that as well.