1对n和n对n关系
我们创建一个地址元件,他包含地址、邮编、城市、州数据。
地址元件
using Orchard.ContentManagement;
namespace RelationSample.Models {
public class AddressPart : ContentPart<AddressPartRecord> {
public string Address {
get { return Retrieve(r => r.Address); }
set { Store(r => r.Address, value); }
}
public string City {
get { return Retrieve(r => r.City); }
set { Store(r => r.City, value); }
}
public StateRecord State {
get { return Retrieve(r => r.StateRecord); }
set { Store(r => r.StateRecord, value); }
}
public string Zip {
get { return Retrieve(r => r.Zip); }
set { Store(r => r.Zip, value); }
}
}
}
创建Record,用于记录元件的数据:
using Orchard.ContentManagement.Records;
namespace RelationSample.Models {
public class AddressPartRecord : ContentPartRecord {
public virtual string Address { get; set; }
public virtual string City { get; set; }
public virtual StateRecord StateRecord { get; set; }
public virtual string Zip { get; set; }
}
}
州数据模型:
namespace RelationSample.Models {
public class StateRecord {
public virtual int Id { get; set; }
public virtual string Code { get; set; }
public virtual string Name { get; set; }
}
}
类图如下:
通过数据迁移命令,我们创建Migration.cs文件:
public int Create() {
SchemaBuilder.CreateTable("AddressPartRecord",
table => table
.ContentPartRecord()
.Column<string>("Address")
.Column<string>("City")
.Column<int>("StateRecord_Id")
.Column<string>("Zip")
);
SchemaBuilder.CreateTable("StateRecord",
table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<string>("Code", column => column.WithLength(2))
.Column<string>("Name")
);
ContentDefinitionManager.AlterPartDefinition("AddressPart",
builder => builder.Attachable());
return 1;
}
数据迁移文件,包含了对AddressPartRecord表和StateRecord表的定义。注意AddressPartRecord表中的StateRecord_Id字段,他的数据类型和StateRecord表的Id相同,系统会自动建立AddressPartRecord表StateRecord_Id字段和StateRecord表的Id字段的关联。外键的名称是固定的,首先是属性名称,然后接下划线,再接关联的列名。
我们在数据迁移类中,注入州的数据操作仓储:
private readonly IRepository<StateRecord> _stateRepository;
[...]
public RelationSampleDataMigration(IRepository<StateRecord> stateRepository) {
_stateRepository = stateRepository;
}
准备初始化数据
private readonly IEnumerable<StateRecord> _states =
new List<StateRecord> {
new StateRecord {Code = "AL", Name = "Alabama"},
new StateRecord {Code = "AK", Name = "Alaska"},
[...]
new StateRecord {Code = "WS", Name = "Western Australia"},
};
保存初始化数据
public int UpdateFrom1() {
if (_stateRepository == null)
throw new InvalidOperationException("Couldn't find state repository.");
foreach (var state in _states) {
_stateRepository.Create(state);
}
return 2;
}
注入了地址元件操作仓储
using Orchard.Data;
using Orchard.ContentManagement.Handlers;
using RelationSample.Models;
namespace RelationSample.Handlers {
public class AddressPartHandler : ContentHandler {
public AddressPartHandler(IRepository<AddressPartRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using RelationSample.Models;
using RelationSample.Services;
using RelationSample.ViewModels;
namespace RelationSample.Drivers {
[UsedImplicitly]
public class AddressPartDriver : ContentPartDriver<AddressPart> {
private readonly IAddressService _addressService;
private const string TemplateName = "Parts/Address";
public AddressPartDriver(IAddressService addressService) {
_addressService = addressService;
}
protected override string Prefix {
get { return "Address"; }
}
protected override DriverResult Display(
AddressPart part,
string displayType,
dynamic shapeHelper) {
return ContentShape("Parts_Address",
() => shapeHelper.Parts_Address(
ContentPart: part,
Address: part.Address,
City: part.City,
Zip: part.Zip,
StateCode: part.State.Code,
StateName: part.State.Name));
}
protected override DriverResult Editor(
AddressPart part,
dynamic shapeHelper) {
return ContentShape("Parts_Address_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: TemplateName,
Model: BuildEditorViewModel(part),
Prefix: Prefix));
}
protected override DriverResult Editor(
AddressPart part,
IUpdateModel updater,
dynamic shapeHelper) {
var model = new EditAddressViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
if (part.ContentItem.Id != 0) {
_addressService.UpdateAddressForContentItem(
part.ContentItem, model);
}
return Editor(part, shapeHelper);
}
private EditAddressViewModel BuildEditorViewModel(AddressPart part) {
var avm = new EditAddressViewModel {
Address = part.Address,
City = part.City,
Zip = part.Zip,
States = _addressService.GetStates()
};
if (part.State != null) {
avm.StateCode = part.State.Code;
avm.StateName = part.State.Name;
}
return avm;
}
}
}
在前端显示的时候,我们通过Parts_Adress Shape,来显示内容项的州和市。
在后台编辑页面,我们会创建另外一个Shape,他具有一个viewModel作为数据类型。viewModel和Record很相似,主要是为了MVC的数据模型绑定,他包含了必须的数据,以及州列表:
using System.Collections.Generic;
using RelationSample.Models;
namespace RelationSample.ViewModels {
public class EditAddressViewModel {
public string Address { get; set; }
public string City { get; set; }
public string StateCode { get; set; }
public string StateName { get; set; }
public string Zip { get; set; }
public IEnumerable<StateRecord> States { get; set; }
}
}
另外需要注意,我们通过updater.TryUpdateModel()方法,可以将前端传回的数据填充进ViewModel中。
地址服务类,注入了州仓储,用于获取州的完整列表。他具有UpdateAddressForContentItem 方法,将viewModel中的数据复制到地址元件中,实现数据持久化。
using System.Collections.Generic;
using System.Linq;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Data;
using RelationSample.Models;
using RelationSample.ViewModels;
namespace RelationSample.Services {
public interface IAddressService : IDependency {
void UpdateAddressForContentItem(
ContentItem item, EditAddressViewModel model);
IEnumerable<StateRecord> GetStates();
}
public class AddressService : IAddressService {
private readonly IRepository<StateRecord> _stateRepository;
public AddressService(IRepository<StateRecord> stateRepository) {
_stateRepository = stateRepository;
}
public void UpdateAddressForContentItem(
ContentItem item,
EditAddressViewModel model) {
var addressPart = item.As<AddressPart>();
addressPart.Address = model.Address;
addressPart.City = model.City;
addressPart.Zip = model.Zip;
addressPart.State = _stateRepository.Get(
s => s.Code == model.StateCode);
}
public IEnumerable<StateRecord> GetStates() {
return _stateRepository.Table.ToList();
}
}
}
前端视图
前端视图显示了Shape的各个属性信息
<p class="adr">
<div class="street-address">@Model.Address</div>
<span class="locality">@Model.City</span>,
<span class="region">@Model.StateCode</span>
<span class="postal-code">@Model.Zip</span>
</p>
编辑视图
创建了各个属性的编辑框
@model RelationSample.ViewModels.EditAddressViewModel
<fieldset>
<legend>Address</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Address, T("Street Address"))
</div>
<div class="editor-field">
@Html.TextAreaFor(model => model.Address)
@Html.ValidationMessageFor(model => model.Address)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.City, T("City"))
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.City)
@Html.ValidationMessageFor(model => model.City)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.StateCode, T("State"))
</div>
<div class="editor-field">
@Html.DropDownListFor(model => model.StateCode,
Model.States.Select(s => new SelectListItem {
Selected = s.Code == Model.StateCode,
Text = s.Code + " " + s.Name,
Value = s.Code
}),
"Choose a state...")
@Html.ValidationMessageFor(model => model.StateCode)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Zip, T("Zip"))
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.Zip)
@Html.ValidationMessageFor(model => model.Zip)
</div>
</fieldset>
定位文件(Placement.info)
最后,我们需要创建一个定位文件,来决定元件在内容类型中的位置:
<Placement>
<Place Parts_Address_Edit="Content:10"/>
<Place Parts_Address="Content:10"/>
</Placement>
使用地址元件
首先,需要启用RelationSample特性。然后,你就可以在内容类型管理页面,创建一个新的类型,比如"Customer",添加Address元件到该类型:
创建Customer内容类型:
前端显示效果如下:
创建 N-N 关系
n-n关系的创建方式,和1-n关系的创建方式很相似。最大的区别,就在于会多一个外键。
我们下面的例子,会创建一个Rewards元件,用于保存折扣和项目的关系。
Rewards元件模型
折扣元件记录
using System.Collections.Generic;
using Orchard.ContentManagement.Records;
namespace RelationSample.Models {
public class RewardsPartRecord : ContentPartRecord {
public RewardsPartRecord() {
Rewards = new List<ContentRewardProgramsRecord>();
}
public virtual IList<ContentRewardProgramsRecord> Rewards { get; set; }
}
}
折扣元件
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
namespace RelationSample.Models {
public class RewardsPart : ContentPart<RewardsPartRecord> {
public IEnumerable<RewardProgramRecord> Rewards {
get {
return Record.Rewards.Select(r => r.RewardProgramRecord);
}
}
}
}
折扣项目
namespace RelationSample.Models {
public class RewardProgramRecord {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual double Discount { get; set; }
}
}
关联记录
我们创建一张表,用于保存元件和折扣项的关系:
namespace RelationSample.Models {
public class ContentRewardProgramsRecord {
public virtual int Id { get; set; }
public virtual RewardsPartRecord RewardsPartRecord { get; set; }
public virtual RewardProgramRecord RewardProgramRecord { get; set; }
}
}
数据库表映射
public int UpdateFrom2() {
SchemaBuilder.CreateTable("RewardsPartRecord",
table => table
.ContentPartRecord()
);
SchemaBuilder.CreateTable("RewardProgramRecord",
table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<string>("Name")
.Column<double>("Discount")
);
SchemaBuilder.CreateTable("ContentRewardProgramsRecord",
table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<int>("RewardsPartRecord_Id")
.Column<int>("RewardProgramRecord_Id")
);
ContentDefinitionManager.AlterPartDefinition(
"RewardsPart",
builder => builder.Attachable());
return 3;
}
上面的代码,实现了我们的3个数据模型的数据持久化。同时,配置Rewards元件可以附加到其他的内容类型。
注意观察外键名称,关联的属性名称+下划线+关联的列名。
初始化数据
和地址元件一样,我们在Migrations 文件中,加入初始化数据代码:
private readonly IRepository<RewardProgramRecord> _rewardProgramRepository;
[...]
private readonly IEnumerable<RewardProgramRecord> _rewardPrograms =
new List<RewardProgramRecord> {
new RewardProgramRecord {Name = "Senior", Discount = 0.05},
new RewardProgramRecord {Name = "Family", Discount = 0.10},
new RewardProgramRecord {Name = "Member", Discount = 0.15},
};
[...]
public int UpdateFrom3() {
if (_rewardProgramRepository == null)
throw new InvalidOperationException(
"Couldn't find reward program repository.");
foreach (var rewardProgram in _rewardPrograms) {
_rewardProgramRepository.Create(rewardProgram);
}
return 4;
}
元件句柄
using Orchard.Data;
using Orchard.ContentManagement.Handlers;
using RelationSample.Models;
namespace RelationSample.Handlers {
public class RewardsPartHandler : ContentHandler {
public RewardsPartHandler(IRepository<RewardsPartRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}
元件Driver
using System.Linq;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using RelationSample.Models;
using RelationSample.Services;
using RelationSample.ViewModels;
namespace RelationSample.Drivers {
[UsedImplicitly]
public class RewardsPartDriver : ContentPartDriver<RewardsPart> {
private readonly IRewardService _rewardService;
private const string TemplateName = "Parts/Rewards";
public RewardsPartDriver(IRewardService rewardService) {
_rewardService = rewardService;
}
protected override string Prefix {
get { return "Rewards"; }
}
protected override DriverResult Display(
RewardsPart part,
string displayType,
dynamic shapeHelper) {
return ContentShape("Parts_Rewards",
() => shapeHelper.Parts_Rewards(
ContentPart: part,
Rewards: part.Rewards));
}
protected override DriverResult Editor(
RewardsPart part,
dynamic shapeHelper) {
return ContentShape("Parts_Rewards_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: TemplateName,
Model: BuildEditorViewModel(part),
Prefix: Prefix));
}
protected override DriverResult Editor(
RewardsPart part,
IUpdateModel updater,
dynamic shapeHelper) {
var model = new EditRewardsViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
if (part.ContentItem.Id != 0) {
_rewardService.UpdateRewardsForContentItem(
part.ContentItem, model.Rewards);
}
return Editor(part, shapeHelper);
}
private EditRewardsViewModel BuildEditorViewModel(RewardsPart part) {
var itemRewards = part.Rewards.ToLookup(r => r.Id);
return new EditRewardsViewModel {
Rewards = _rewardService.GetRewards().Select(
r => new RewardProgramEntry {
RewardProgram = r,
IsChecked = itemRewards.Contains(r.Id)
}).ToList()
};
}
}
}
和地址元件一样,我们获取了所有的折扣项,将其赋值给viewmodel,以便创建下拉列表的选项。在BuildEditorViewModel 方法中,我们将元件的数据赋值给了页面模型。
页面模型:
using System.Collections.Generic;
using RelationSample.Models;
namespace RelationSample.ViewModels {
public class EditRewardsViewModel {
public IList<RewardProgramEntry> Rewards { get; set; }
}
public class RewardProgramEntry {
public RewardProgramRecord RewardProgram { get; set; }
public bool IsChecked { get; set; }
}
}
Rewards服务
在服务里面,实现了Rewards关系表的维护
using System.Collections.Generic;
using System.Linq;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Data;
using RelationSample.Models;
using RelationSample.ViewModels;
namespace RelationSample.Services {
public interface IRewardService : IDependency {
void UpdateRewardsForContentItem(
ContentItem item,
IEnumerable<RewardProgramEntry> rewards);
IEnumerable<RewardProgramRecord> GetRewards();
}
public class RewardService : IRewardService {
private readonly IRepository<RewardProgramRecord>
_rewardProgramRepository;
private readonly IRepository<ContentRewardProgramsRecord>
_contentRewardRepository;
public RewardService(
IRepository<RewardProgramRecord> rewardProgramRepository,
IRepository<ContentRewardProgramsRecord> contentRewardRepository) {
_rewardProgramRepository = rewardProgramRepository;
_contentRewardRepository = contentRewardRepository;
}
public void UpdateRewardsForContentItem(
ContentItem item,
IEnumerable<RewardProgramEntry> rewards) {
var record = item.As<RewardsPart>().Record;
var oldRewards = _contentRewardRepository.Fetch(
r => r.RewardsPartRecord == record);
var lookupNew = rewards
.Where(e => e.IsChecked)
.Select(e => e.RewardProgram)
.ToDictionary(r => r, r => false);
// Delete the rewards that are no longer there
// and mark the ones that should stay
foreach(var contentRewardProgramsRecord in oldRewards) {
if (lookupNew.ContainsKey(
contentRewardProgramsRecord.RewardProgramRecord)) {
lookupNew[contentRewardProgramsRecord.RewardProgramRecord]
= true;
}
else {
_contentRewardRepository.Delete(contentRewardProgramsRecord);
}
}
// Add the new rewards
foreach(var reward in lookupNew.Where(kvp => !kvp.Value)
.Select(kvp => kvp.Key)) {
_contentRewardRepository.Create(new ContentRewardProgramsRecord {
RewardsPartRecord = record,
RewardProgramRecord = reward
});
}
}
public IEnumerable<RewardProgramRecord> GetRewards() {
return _rewardProgramRepository.Table.ToList();
}
}
}
创建视图
前端视图
<h4>@T("Rewards"):</h4>
<ul>
@foreach (var reward in Model.Rewards) {
<li>@string.Format("{0} ({1:P1})", reward.Name, -reward.Discount)</li>
}
</ul>
编辑视图
@model RelationSample.ViewModels.EditRewardsViewModel
<fieldset><legend>@T("Rewards")</legend>
<ul>
@{
var rewardIndex = 0;
}
@foreach (var reward in Model.Rewards) {
<li><input type="hidden" value="@reward.RewardProgram.Id"
name="@Html.FieldNameFor(m => m.Rewards[rewardIndex].RewardProgram.Id)"/>
<label for="@Html.FieldNameFor(m => m.Rewards[rewardIndex].IsChecked)">
<input type="checkbox" value="true"
name="@Html.FieldNameFor(m => m.Rewards[rewardIndex].IsChecked)"
id="@Html.FieldNameFor(m => m.Rewards[rewardIndex].IsChecked)"
@if (reward.IsChecked) {<text>checked="checked"</text>}/>
@string.Format("{0} ({1:P1})",
reward.RewardProgram.Name,
-reward.RewardProgram.Discount))
</label>
@{rewardIndex++;}
</li>
}
</ul>
</fieldset>
定位文件
<Placement>
<Place Parts_Address_Edit="Content:10"/>
<Place Parts_Address="Content:10"/>
<Place Parts_Rewards_Edit="Content:11"/>
<Place Parts_Rewards="Content:11"/>
</Placement>
使用 Rewards 元件
在模块管理页面,找到RelationSample特性,点击升级(如果使用VisualStudio开发,重新运行站点的时候,会自动升级)。然后,你就可以在内容类型里面,添加Rewards元件了。
编辑已有的内容类型,我们发现,多了Reward元件
前端显示效果:
内容项之间的关联关系
Our third example will establish a relation between content items, which is a step up from our previous examples which were establishing relations between records. Doing the same thing with items is not fundamentally very different, but there are a couple of caveats that justify a specific example.
The example that we will build is a Sponsor part that records that a specific customer was sponsored by another.
Sponsor元件
Sponsor元件只包含了一个Sponsor属性。我们将这个字段定义为懒加载字段,这样,只有到用到的时候,他才会加载:
using Orchard.ContentManagement;
using Orchard.Core.Common.Utilities;
namespace RelationSample.Models {
public class SponsorPart : ContentPart<SponsorPartRecord> {
private readonly LazyField<IContent> _sponsor = new LazyField<IContent>();
public LazyField<IContent> SponsorField { get { return _sponsor; } }
public IContent Sponsor {
get { return _sponsor.Value; }
set { _sponsor.Value = value; }
}
}
}
元件数据(record):
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace RelationSample.Models {
public class SponsorPartRecord : ContentPartRecord {
public virtual ContentItemRecord Sponsor { get; set; }
}
}
创建表
编辑Migrations.cs文件
public int UpdateFrom4() {
SchemaBuilder.CreateTable("SponsorPartRecord",
table => table
.ContentPartRecord()
.Column<int>("Sponsor_Id")
);
ContentDefinitionManager.AlterPartDefinition(
"SponsorPart", builder => builder.Attachable());
return 5;
}
Sponsor句柄
因为懒加载字段的存在,所以句柄文件和上面的例子有点不一样:
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.ContentManagement.Handlers;
using RelationSample.Models;
namespace RelationSample.Handlers {
public class SponsorPartHandler : ContentHandler {
private readonly IContentManager _contentManager;
public SponsorPartHandler(
IRepository<SponsorPartRecord> repository,
IContentManager contentManager) {
Filters.Add(StorageFilter.For(repository));
_contentManager = contentManager;
OnInitializing<SponsorPart>(PropertySetHandlers);
OnLoaded<SponsorPart>(LazyLoadHandlers);
}
void LazyLoadHandlers(LoadContentContext context, SponsorPart part) {
// add handlers that will load content just-in-time
part.SponsorField.Loader(() =>
part.Record.Sponsor == null ?
null : _contentManager.Get(part.Record.Sponsor.Id));
}
static void PropertySetHandlers(
InitializingContentContext context, SponsorPart part) {
// add handlers that will update records when part properties are set
part.SponsorField.Setter(sponsor => {
part.Record.Sponsor = sponsor == null
? null
: sponsor.ContentItem.Record;
return sponsor;
});
// Force call to setter if we had already set a value
if (part.SponsorField.Value != null)
part.SponsorField.Value = part.SponsorField.Value;
}
}
}
在handler类里面,我们通过storage.filter方法,注入了sponsor的仓储。然后调用OnInitializing 和 OnLoaded event 事件句柄,来加载sponsor字段。他们分别会在设置sponsor字段数据和加载sponsor字段的时候触发。
Driver
页面模型:
using System.Collections.Generic;
using Orchard.ContentManagement;
using RelationSample.Models;
namespace RelationSample.ViewModels {
public class EditSponsorViewModel {
public int CustomerId { get; set; }
public int SponsorId { get; set; }
public IEnumerable<CustomerViewModel> Customers { get; set; }
}
public class CustomerViewModel {
public int Id { get; set;}
public string Name { get; set;}
}
}
Driver代码:
using System.Linq;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using RelationSample.Models;
using RelationSample.Services;
using RelationSample.ViewModels;
namespace RelationSample.Drivers {
[UsedImplicitly]
public class SponsorPartDriver : ContentPartDriver<SponsorPart> {
private readonly ICustomerService _customerService;
private const string TemplateName = "Parts/Sponsor";
public SponsorPartDriver(ICustomerService customerService) {
_customerService = customerService;
}
protected override string Prefix {
get { return "Sponsor"; }
}
protected override DriverResult Display(
SponsorPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Sponsor",
() => shapeHelper.Parts_Sponsor(
ContentPart: part,
Sponsor: part.Sponsor,
SponsorName: _customerService.GetCustomerName(part.Sponsor)
));
}
protected override DriverResult Editor(
SponsorPart part, dynamic shapeHelper) {
return ContentShape("Parts_Sponsor_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: TemplateName,
Model: BuildEditorViewModel(part),
Prefix: Prefix));
}
protected override DriverResult Editor(
SponsorPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new EditSponsorViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
if (part.ContentItem.Id != 0) {
_customerService.UpdateSponsorForContentItem(
part.ContentItem, model);
}
return Editor(part, shapeHelper);
}
private EditSponsorViewModel BuildEditorViewModel(SponsorPart part) {
var itemSponsor = new EditSponsorViewModel {
CustomerId = part.ContentItem.Id,
Customers = _customerService.GetCustomers()
};
if (part.Sponsor != null) {
itemSponsor.SponsorId = part.Sponsor.Id;
}
return itemSponsor;
}
}
}
服务
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Data;
using RelationSample.Models;
using RelationSample.ViewModels;
namespace RelationSample.Services {
public interface ICustomerService : IDependency {
void UpdateSponsorForContentItem(
ContentItem item, EditSponsorViewModel model);
string GetCustomerName(IContent customer);
IEnumerable<CustomerViewModel> GetCustomers();
}
public class CustomerService : ICustomerService {
private readonly IContentManager _contentManager;
public CustomerService(IContentManager contentManager) {
_contentManager = contentManager;
}
public void UpdateSponsorForContentItem(
ContentItem item, EditSponsorViewModel model) {
var sponsorPart = item.As<SponsorPart>();
sponsorPart.Sponsor = _contentManager.Get(model.SponsorId);
}
public string GetCustomerName(IContent customer) {
return customer.ContentItem.Parts
.SelectMany(p => p.Fields)
.Where(f => f.Name == "Name")
.First()
.Storage.Get<string>(null);
}
public IEnumerable<CustomerViewModel> GetCustomers() {
return _contentManager
.Query("Customer")
.List()
.Select(ci => new CustomerViewModel {
Id = ci.Id,
Name = GetCustomerName(ci)
});
}
}
}
注意,在上面的例子中,我们假定"cutomer"内容类型具有Name字段
视图
编辑视图
@model RelationSample.ViewModels.EditSponsorViewModel
<fieldset>
<legend>Sponsor</legend>
<div class="editor-field">
@Html.DropDownListFor(model => model.SponsorId,
Model.Customers
.Where(c => c.Id != Model.CustomerId)
.Select(c => new SelectListItem {
Selected = c.Id == Model.SponsorId,
Text = c.Name,
Value = c.Id.ToString()
}),
"Choose a customer...")
@Html.ValidationMessageFor(model => model.SponsorId)
</div>
</fieldset>
前端视图
@if (Model.Sponsor != null) {
<text>Customer sponsored by @Model.SponsorName.</text>
}
定位文件:
<Placement>
<Place Parts_Address_Edit="Content:10"/>
<Place Parts_Address="Content:10"/>
<Place Parts_Rewards_Edit="Content:11"/>
<Place Parts_Rewards="Content:11"/>
<Place Parts_Sponsor_Edit="Content:12"/>
<Place Parts_Sponsor="Content:12"/>
</Placement>
在Customer类型中加入Sponsor元件:
前端显示效果
直接编辑NHibernate配置
一般情况下,模型间的关系会在Migration.cs文件中自动维护。但是,对于复杂的模型,我们需要更加的定制化配置,这时,你可以通过实现ISessionConfigurationEvents 接口来实现这个需求:
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using NHibernate.Cfg;
using Orchard.Data;
using Orchard.Utility;
namespace DataMapping.Example
{
public class Mappings : ISessionConfigurationEvents
{
public void Created(FluentConfiguration cfg, AutoPersistenceModel defaultModel)
{
//Many-to-many mapping example
defaultModel.Override<ClassA>(x => x.HasManyToMany(y => y.ClassB).Cascade.All()
.Table("DataMapping_Example_ClassAClassBCrossReferenceTable"));
//One-to-One example
defaultModel.Override<ClassA>(x => x.HasOne(y => y.ClassB));
}
public void Prepared(FluentConfiguration cfg) {}
public void Building(Configuration cfg) {}
public void Finished(Configuration cfg) {}
public void ComputingHash(Hash hash) {}
}
}