Empty content item after create/edit in Orchard - asp.net

I'm using the guide to creating n-to-n relations in Orchard (ocs.orchardproject.net/Documentation/Creating-1-n-and-n-n-relations) with some slight modifications. While the sample code works well my own content part is always blank after I create or edit the item. I can't figure it out, because I swear my code is almost identical to theirs (with the exception of content parts having more/less unrelated fields).
I suspect it might have to do with the Prefix in the Driver. I don't really know what the prefix is supposed to do, but setting it to one value produces a runtime error on create/edit, other values just produce result with all fields blank.
The original sample works fine, so it has to be something I did or didn't do, but I just can't figure out what it is.
Some relevant classes:
using System.Linq;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using ArealAds.Models;
using ArealAds.Services;
using ArealAds.ViewModels;
namespace ArealAds.Drivers {
[UsedImplicitly]
public class StreetPartDriver : ContentPartDriver<StreetPart> {
private readonly IStreetService _streetService;
private const string TemplateName = "Parts/Street";
public StreetPartDriver(IStreetService streetService) {
_streetService = streetService;
}
// this one gives a runtime error with blank description,
// other values produce result with all fields blank
protected override string Prefix {
get { return "Area"; }
}
protected override DriverResult Display(StreetPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Street",
() => shapeHelper.Parts_Street(
ContentPart: part,
Name: part.Name,
Areas: part.Areas,
Districts: part.Districts));
}
protected override DriverResult Editor(StreetPart part, dynamic shapeHelper) {
return ContentShape("Parts_Street_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: TemplateName,
Model: BuildEditorViewModel(part),
Prefix: Prefix));
}
protected override DriverResult Editor(StreetPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new EditStreetViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
if (part.ContentItem.Id != 0) {
_streetService.UpdateAreasForContentItem(part.ContentItem, model.Areas);
}
return Editor(part, shapeHelper);
}
private EditStreetViewModel BuildEditorViewModel(StreetPart part) {
var itemAreas = part.Areas.ToLookup(r => r.Id);
return new EditStreetViewModel {
Areas = _streetService.GetAreas().Select(r => new AreaEntry {
Area = r,
IsChecked = itemAreas.Contains(r.Id)
}).ToList()
};
}
}
}
using System.Collections.Generic;
using System.Linq;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Data;
using ArealAds.Models;
using ArealAds.ViewModels;
namespace ArealAds.Services {
public interface IStreetService : IDependency {
void UpdateAreasForContentItem(ContentItem item, IEnumerable<AreaEntry> areas);
IEnumerable<AreaRecord> GetAreas();
}
public class StreetService : IStreetService {
private readonly IRepository<AreaRecord> _areaRepository;
private readonly IRepository<StreetAreaRecord> _streetAreaRepository;
public StreetService(
IRepository<AreaRecord> areaRepository,
IRepository<StreetAreaRecord> streetAreaRepository) {
_areaRepository = areaRepository;
_streetAreaRepository = streetAreaRepository;
}
public void UpdateAreasForContentItem(ContentItem item, IEnumerable<AreaEntry> areas) {
var record = item.As<StreetPart>().Record;
var oldAreas = _streetAreaRepository.Fetch(
r => r.StreetRecord == record);
var lookupNew = areas
.Where(e => e.IsChecked)
.Select(e => e.Area)
.ToDictionary(r => r, r => false);
// Delete the areas that are no longer there and mark the ones that should stay
foreach(var streetAreaRecord in oldAreas) {
if (lookupNew.ContainsKey(streetAreaRecord.AreaRecord)) {
lookupNew[streetAreaRecord.AreaRecord] = true;
}
else {
_streetAreaRepository.Delete(streetAreaRecord);
}
}
// Add the new areas
foreach(var area in lookupNew.Where(kvp => !kvp.Value).Select(kvp => kvp.Key)) {
_streetAreaRepository.Create(new StreetAreaRecord {
StreetRecord = record,
AreaRecord = area
});
}
}
public IEnumerable<AreaRecord> GetAreas() {
return _areaRepository.Table.ToList();
}
}
}
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace ArealAds.Models {
public class StreetAreaRecord : ContentPartRecord {
public virtual StreetRecord StreetRecord { get; set; }
public virtual AreaRecord AreaRecord { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Data;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
using ArealAds.Models;
namespace ArealAds {
public class Migrations : DataMigrationImpl {
public int Create() {
//
// Street-Area-District
//
SchemaBuilder.CreateTable("DistrictRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
);
ContentDefinitionManager.AlterPartDefinition(
typeof(DistrictPart).Name, cfg => cfg.Attachable());
ContentDefinitionManager.AlterTypeDefinition(
"District", cfg => cfg
.WithPart("CommonPart")
.WithPart("DistrictPart")
.Creatable()
);
SchemaBuilder.CreateTable("AreaRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
.Column<int>("DistrictRecord_Id")
);
ContentDefinitionManager.AlterPartDefinition(
typeof(AreaPart).Name, cfg => cfg.Attachable());
ContentDefinitionManager.AlterTypeDefinition(
"Area", cfg => cfg
.WithPart("CommonPart")
.WithPart("AreaPart")
.Creatable()
);
SchemaBuilder.CreateTable("StreetRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
);
ContentDefinitionManager.AlterPartDefinition(
typeof(StreetPart).Name, cfg => cfg.Attachable());
ContentDefinitionManager.AlterTypeDefinition(
"Street", cfg => cfg
.WithPart("CommonPart")
.WithPart("StreetPart")
.Creatable()
);
SchemaBuilder.CreateTable("StreetAreaRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<int>("StreetRecord_Id")
.Column<int>("AreaRecord_Id")
);
//
// Address-Ad
//
SchemaBuilder.CreateTable("AddressRecord", table => table
.ContentPartRecord()
.Column<int>("StreetRecord_Id")
.Column<int>("Building")
.Column<int>("Kor")
.Column<int>("Str")
.Column<int>("Vl")
.Column<string>("Note")
.Column<int>("AreaRecord_Id")
.Column<int>("DistrictRecord_Id")
.Column<string>("Phone1")
.Column<string>("Phone2")
.Column<string>("Phone3")
);
ContentDefinitionManager.AlterPartDefinition(
typeof(AddressPart).Name, cfg => cfg.Attachable());
return 1;
}
}
}
#model ArealAds.ViewModels.EditStreetViewModel
<fieldset>
<legend>Улица</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Street.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Street.Name)
#Html.ValidationMessageFor(model => model.Street.Name)
</div>
<ul>
#for (int i = 0; i < Model.Areas.Count; i++) {
<li>
<input type="hidden" value="#Model.Areas[i].Area.Id"
name="#Html.FieldNameFor(m => m.Areas[i].Area.Id)"/>
<label for="#Html.FieldNameFor(m => m.Areas[i].IsChecked)">
<input type="checkbox" value="true"
name="#Html.FieldNameFor(m => m.Areas[i].IsChecked)"
id="#Html.FieldNameFor(m => m.Areas[i].IsChecked)"
#if (Model.Areas[i].IsChecked) {<text>checked="checked"</text>}/>
#Model.Areas[i].Area.Name
</label>
</li>
}
</ul>
</fieldset>
I had been beating my head against the wall on this for days, please make any suggestions you feel might theoretically help 'cos I'm desperate :(
UPD: The StreetHandler class:
using ArealAds.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
namespace ArealAds.Handlers {
public class StreetHandler : ContentHandler {
public StreetHandler(IRepository<StreetRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}
There is an exception on the log:
2012-04-10 00:07:58,515 [7] Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator - IdentifierGenerationException thrown from IContentPartDriver by ArealAds.Drivers.StreetPartDriver
NHibernate.Id.IdentifierGenerationException: attempted to assign id from null one-to-one property: ContentItemRecord
â NHibernate.Id.ForeignGenerator.Generate(ISessionImplementor sessionImplementor, Object obj)
â NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
â NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
â NHibernate.Event.Default.DefaultSaveEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
â NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
â NHibernate.Event.Default.DefaultSaveEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
â NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
â NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
â NHibernate.Impl.SessionImpl.Save(Object obj)
â Orchard.Data.Repository`1.Create(T entity) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\Data\Repository.cs:ñòðîêà 96
â Orchard.Data.Repository`1.Orchard.Data.IRepository<T>.Create(T entity) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\Data\Repository.cs:ñòðîêà 36
â ArealAds.Services.StreetService.UpdateAreasForContentItem(ContentItem item, IEnumerable`1 areas) â c:\Users\Mom\Teritoriya\Modules\ArealAds\Services\Street.cs:ñòðîêà 46
â ArealAds.Drivers.StreetPartDriver.Editor(StreetPart part, IUpdateModel updater, Object shapeHelper) â c:\Users\Mom\Teritoriya\Modules\ArealAds\Controllers\Street.cs:ñòðîêà 47
â System.Dynamic.UpdateDelegates.UpdateAndExecute4[T0,T1,T2,T3,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
â Orchard.ContentManagement.Drivers.ContentPartDriver`1.Orchard.ContentManagement.Drivers.IContentPartDriver.UpdateEditor(UpdateEditorContext context) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\ContentManagement\Drivers\ContentPartDriver.cs:ñòðîêà 30
â Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator.<>c__DisplayClass10.<UpdateEditor>b__f(IContentPartDriver driver) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\ContentManagement\Drivers\Coordinators\ContentPartDriverCoordinator.cs:ñòðîêà 61
â Orchard.InvokeExtensions.Invoke[TEvents](IEnumerable`1 events, Action`1 dispatch, ILogger logger) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\InvokeExtensions.cs:ñòðîêà 19
EDIT: some model classes:
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace ArealAds.Models {
public class AreaRecord : ContentPartRecord {
public virtual string Name { get; set; }
public virtual DistrictRecord DistrictRecord { get; set; }
}
public class AreaPart : ContentPart<AreaRecord> {
[Required]
public string Name {
get { return Record.Name; }
set { Record.Name = value; }
}
[Required]
public DistrictRecord DistrictRecord {
get { return Record.DistrictRecord; }
set { Record.DistrictRecord = value; }
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace ArealAds.Models {
public class StreetRecord : ContentPartRecord {
public virtual string Name { get; set; }
public virtual IList<StreetAreaRecord> Areas { get; set; }
public StreetRecord() {
Areas = new List<StreetAreaRecord>();
}
}
public class StreetPart : ContentPart<StreetRecord> {
[Required]
public string Name {
get { return Record.Name; }
set { Record.Name = value; }
}
public IEnumerable<AreaRecord> Areas {
get {
return Record.Areas.Select (r => r.AreaRecord);
}
}
public IEnumerable<DistrictRecord> Districts {
get {
return Record.Areas.Select (r => r.AreaRecord.DistrictRecord).Distinct();
}
}
}
}
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace ArealAds.Models {
public class StreetAreaRecord : ContentPartRecord {
public virtual StreetRecord StreetRecord { get; set; }
public virtual AreaRecord AreaRecord { get; set; }
}
}
using ArealAds.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
namespace ArealAds.Handlers {
public class AreaHandler : ContentHandler {
public AreaHandler(IRepository<AreaRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}
using ArealAds.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
namespace ArealAds.Handlers {
public class StreetHandler : ContentHandler {
public StreetHandler(IRepository<StreetRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}

Your StreetAreaRecord model is missing an Id property. Since it's not a ContentPartRecord, you have to set up the property manually.
public virtual int Id { get; set; }

The prefix is to ensure a unique id attribute in the html fields in the part editor. You might have multiple parts within a single content type with a "Name" field. Without the prefix the html would be invalid and postback wouldn't work because there would be two fields with id="Name". You can simply set the prefix to be the name of the part.
I'm not sure the prefix is what's preventing your part from saving. Have you checked the Handler for your part? Make sure it sets up the filter for the StreetPartRecord, that is often the cause of a new Part not saving on postback.

Related

Inherited child tables/fields are not generated in migrations

I'm trying to model and generate a database with inheritance, using entity framework core 3.0.
I'm following the guide in https://www.learnentityframeworkcore.com/inheritance/table-per-hierarchy
Im my code, I have 3 classes: Parent is "Source", and to inherited children "Publication" and "Feeder"
The guide tells that all field from child classes will be in the global table. But they are not.
My model is the following (one parent class, two children)
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Legal.Models {
public enum SourceType { Publication, Feeder }
// An issuer can have one or more sources. Every source has a type among 3 types
[Table("Source")]
public class Source {
// PK
public int SourceId { get; set; }
// Attributes
public SourceType SourceType { get; set; }
public string Description { get; set; }
}
[Table("Source")]
public class Publication : Source {
[MaxLength(13)]
public string ISSN;
[MaxLength(2048)]
public string IssueTocUrl;
// Generate FK in Issues
// public List<Source> Sources { get; } = new List<Source>();
}
[Table("Source")]
public class Feeder : Source {
[MaxLength(2048)]
public string FeederUrl;
}
}
I generate the migration using the following command line
dotnet ef migrations add Version_0_0
It generates the following designer code (that seems correct - check source, feeder and publication entities)
// <auto-generated />
using System;
using Legal.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Legal.Migrations
{
[DbContext(typeof(LegalDbContext))]
[Migration("20191022181406_Version_0_0")]
partial class Version_0_0
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Legal.Models.Issuer", b =>
{
b.Property<int>("IssuerId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Description")
.HasColumnType("varchar(64)")
.HasMaxLength(64);
b.Property<string>("LandPageUrl")
.HasColumnType("longtext")
.HasMaxLength(2048);
b.HasKey("IssuerId");
b.ToTable("Issuer");
});
modelBuilder.Entity("Legal.Models.Source", b =>
{
b.Property<int>("SourceId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("IssuerId")
.HasColumnType("int");
b.Property<int>("SourceType")
.HasColumnType("int");
b.HasKey("SourceId");
b.HasIndex("IssuerId");
b.ToTable("Source");
b.HasDiscriminator<string>("Discriminator").HasValue("Source");
});
modelBuilder.Entity("Legal.Models.Feeder", b =>
{
b.HasBaseType("Legal.Models.Source");
b.ToTable("Source");
b.HasDiscriminator().HasValue("Feeder");
});
modelBuilder.Entity("Legal.Models.Publication", b =>
{
b.HasBaseType("Legal.Models.Source");
b.ToTable("Source");
b.HasDiscriminator().HasValue("Publication");
});
modelBuilder.Entity("Legal.Models.Source", b =>
{
b.HasOne("Legal.Models.Issuer", null)
.WithMany("Sources")
.HasForeignKey("IssuerId");
});
#pragma warning restore 612, 618
}
}
}
But the generation script only include the source table WITHOUT any of the child fields
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Legal.Migrations
{
public partial class Version_0_0 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Issuer",
columns: table => new
{
IssuerId = table.Column<int>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Description = table.Column<string>(maxLength: 64, nullable: true),
LandPageUrl = table.Column<string>(maxLength: 2048, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Issuer", x => x.IssuerId);
});
migrationBuilder.CreateTable(
name: "Source",
columns: table => new
{
SourceId = table.Column<int>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
SourceType = table.Column<int>(nullable: false),
Description = table.Column<string>(nullable: true),
Discriminator = table.Column<string>(nullable: false),
IssuerId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Source", x => x.SourceId);
table.ForeignKey(
name: "FK_Source_Issuer_IssuerId",
column: x => x.IssuerId,
principalTable: "Issuer",
principalColumn: "IssuerId",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Source_IssuerId",
table: "Source",
column: "IssuerId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Source");
migrationBuilder.DropTable(
name: "Issuer");
}
}
}
NOTICE:
I have tried
- With no annotatios
- With data annotations configuring 3 tables (Source, Publication and Feeder)
- With data annotations configuring only 1 table (Source - current example)
- With DbSet per every class
- Overriding OnModelCreating
// Define the table based collections
public DbSet<Issuer> Issuers { get; set; }
// public DbSet<Source> Sources { get; set; }
public DbSet<Publication> Publications { get; set; }
public DbSet<Feeder> Feeders { get; set; }
// Force creation of child Sources
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Publication>();
modelBuilder.Entity<Feeder>();
}
you have the same name for all the table attributes: [Table("Source")]
sorry to make it unclear, but this should bring you some informations https://learn.microsoft.com/de-de/ef/core/modeling/relational/inheritance -> bascially all your tables get generated into the "Source" table and are only separated by the Discriminator flag.
See -> Discriminator = table.Column<string>(nullable: false), in the generated Source Table
Example from the Link, where the Inherited Items are separated by the Discriminator:
if you give them all different table names like i stated above you can just query them by the table name -> SELECT * FROM Sources WHERE Discriminator = "Source" would give you all data from the "Source" table
with the dbset like you had
public DbSet<Publication> Publications { get; set; }
public DbSet<Feeder> Feeders { get; set; }
and you query on them you will get the right items from the database.
just change the [Table("Source")] to something useful
I have found my (stupid) error.
I forgot to declare the setter/getter. The process only generate fields with get/set.
This is the right code
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Legal.Models {
public enum SourceType { Publication, Feeder }
// An issuer can have one or more sources. Every source has a type among 3 types
public abstract class Source {
// PK
public int SourceId { get; set; }
// Attributes
public SourceType SourceType { get; set; }
[MaxLength(256)]
public string Description { get; set; }
[MaxLength(1024)]
public string ServiceClass { get; set; }
// Parent/Many to One relation shipp
public int IssuerId { get; set; }
public Issuer Issuer { get; set; }
}
public class Publication : Source {
[MaxLength(13)]
public string ISSN { get; set; }
[MaxLength(2048)]
public string IssueTocUrl { get; set; }
// Generate FK in Issues
public List<PublicationIssue> PublicationIssues { get; set; }
}
public class Feeder : Source {
[MaxLength(2048)]
public string FeederUrl { get; set; }
}
}

Automapper, AfterMap not triggered when mapping on existing object if ConstructUsing is mentionned

I'm facing an issue when AfterMap is not triggered.
Here are the conditions for this issue to occur:
I want to map values on an existing object from its abstract base type
ConstructUsing is provided as it is here where the concrete type will be chosen
Here the whole xunit (after doing dotnet new xunit -o PbAutoMapper) test:
using System;
using AutoMapper;
using Xunit;
namespace PbAutoMapper
{
public class UnitTest1
{
[Fact]
public void Test1()
{
var mapperConfig = new MapperConfiguration(config =>
{
config.CreateMap<SourceClass, BaseDestinationClass>()
.ConstructUsing(this.Construct)
.ForMember(i => i.MyNewProperty, o => o.Ignore())
.AfterMap(this.AfterMap);
config.CreateMap<SourceClass, DestinationClass>();
});
var mapper = mapperConfig.CreateMapper();
var src = new SourceClass { MyProperty = 1 };
var dest = mapper.Map<BaseDestinationClass>(src);
Assert.Equal("1", dest.MyNewProperty); // This works...
dest = new DestinationClass();
mapper.Map(src, dest);
Assert.Equal("1", dest.MyNewProperty); // This failed because AfterMap wasn't triggered
}
private DestinationClass Construct(SourceClass s, ResolutionContext ctx)
{
var d = new DestinationClass();
ctx.Mapper.Map(s, d, ctx);
return d;
}
private void AfterMap(SourceClass s, BaseDestinationClass d)
{
d.MyNewProperty = s.MyProperty.ToString();
}
}
public class SourceClass
{
public int MyProperty { get; set; }
}
public abstract class BaseDestinationClass
{
public string MyNewProperty { get; set; }
}
public class DestinationClass : BaseDestinationClass
{
}
}
Note: this code that seems to be implementable differently is actually over simplified to reveal my problem in the easiest way.

persisting a simple tree with Fluent Nhibernate

I have a tree stucture model (used composite pattern).
it's class diagram is like this:
database diagram:
and sample of tree:
the problem rise when I want to persist CombatElement tree which its depth is more than one, when I try to persist such object, NHibernate only save the objects which are in the 1st level and ignores the objects which connected to 2nd level object and so:
if I create this tree :
CombatElement fe = new Formation() { Name = "Alpha Company" };
fe.Add(new Soldier()
{
Name = "Joe",
Rank = 1
});
fe.Add(new Soldier()
{
Name = "Jack",
Rank = 2
});
CombatElement platoon =
new Formation();
platoon.Name = "1st Platoon";
fe.Add(platoon);
platoon.Add(
new Soldier()
{
Name = "Adam",
Rank = 2
});
platoon.Add(
new Soldier()
{
Name = "Arthur",
Rank = 3
});
only "Joe", "1st Platoon" and "Jack" will be saved into the database and "Arthur" and "Adam" which are the subelemnts of 1st Platoon will be ignored and won't be saved!!
here is mapping classes:
public class CombatElementMap:ClassMap<CombatElement>
{
public CombatElementMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Not.Nullable().Length(100);
}
}
///////////////////////////
public class FormationMap:ClassMap<Formation>
{
public FormationMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
HasMany(x => x.Elements).Cascade.AllDeleteOrphan();
}
}
///////////////////////////
public class SoldierMap:ClassMap<Soldier>
{
public SoldierMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Rank);
}
}
I have cascaded the Formation objects, but the problem is still persist.
why this happens? It just confusing me!!
You are using inheritance in your class structure, to properly instrcut nHibernate to store the base class and sub class properties, you'll have to redefine your mappings and maybe the objects a little bit
Basically you should use SubClassMap instead of ClassMap for all sub classes and only define new properties in those sub class mappings.
I've added some code for the elements (best guess according to your diagram)
public abstract class CombatElement
{
public CombatElement()
{
Elements = new List<CombatElement>();
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<CombatElement> Elements { get; set; }
public virtual void Add(CombatElement element)
{
Elements.Add(element);
}
}
public class Formation : CombatElement
{
}
public class Soldier : CombatElement
{
public virtual int Rank { get; set; }
}
And the new mapping would look like this:
public class CombatElementMap : ClassMap<CombatElement>
{
public CombatElementMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Not.Nullable().Length(100);
HasMany(x => x.Elements)
.AsBag()
.Fetch.Join()
.Cascade.AllDeleteOrphan();
}
}
public class FormationMap : SubclassMap<Formation>
{
public FormationMap()
{
//Id(x => x.Id).GeneratedBy.GuidComb();
}
}
public class SoldierMap : SubclassMap<Soldier>
{
public SoldierMap()
{
//Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Rank);
}
}
also make sure you call .Flush after saving your entities, otherwise it might not get stored in your database.
session.Save(fe);
session.Flush();

ASP.net MVC: How do I define a razor inline template in code?

I'm using razor inline template to define the grid columns format.
This work great if you are defining the inline template inside a razor view.
How can I do the same (defining the column list with inline templates) in code in the controller?
Here is the sample code. You can also get view code using this. See also this for extending the grid,
public class HomeController : Controller
{
public class Employee
{
public string Name { get; set; }
public string Description { get; set; }
public int Price { get; set; }
}
public ActionResult Index()
{
var myClasses = new List<Employee>{
new Employee { Name="A" , Price=1, Description="A A"},
new Employee { Name="B" , Price=2, Description="B B"},
new Employee { Name="C" , Price=3, Description="C C"}};
var grid = new WebGrid(source: myClasses);
var html = grid.GetHtml(
columns: grid.Columns(
grid.Column("Name", "Product", style: "product"),
grid.Column("Description", format: item => new System.Web.WebPages.HelperResult(writer =>
{
WriteLiteralTo(writer, "<i>");
WriteTo(writer, item.Description);
WriteLiteralTo(writer, "</i>");
})),
grid.Column("Price", format: item => new System.Web.WebPages.HelperResult(wrtier =>
{
WriteLiteralTo(wrtier, "$");
WriteTo(wrtier, item.Price);
}))
)
);
return View();
}
private void WriteLiteralTo(TextWriter writer, object content)
{
writer.Write(HttpUtility.HtmlEncode(content));
}
public static void WriteTo(TextWriter writer, object content)
{
writer.Write(HttpUtility.HtmlEncode(content));
}
}

Self referencing model in EF Code First 4.1 throws 'Collection was modified' exception

I am trying to model the following using EF 4.1 and cannot get past this exception ("Collection was modified; enumeration operation may not execute").
Models:
public class Workflow
{
public List<WorkflowStage> Stages { get; set; }
}
public class WorkflowStage
{
public virtual List<WorkflowNextStage> WorkflowNextStages { get; set; }
}
public abstract class WorkflowNextStage
{
public virtual WorkflowStage NextStage { get; set; }
}
public class SuccessStage : WorkflowNextStage
{
}
public class FailureStage : WorkflowNextStage
{
}
Configuration:
modelBuilder.Entity<WorkflowStage>()
.HasMany(x => x.WorkflowNextStages)
.WithRequired()
.Map(m => m.MapKey("CurrentStageId"));
modelBuilder.Entity<WorkflowNextStage>()
.HasRequired(x => x.NextStage)
.WithMany()
.Map(x => x.MapKey("NextStageId"))
.WillCascadeOnDelete(false);
Failing code:
using (var ctx = new SBContext())
{
var workflow = new Workflow();
var stage = new WorkflowStage();
stage.WorkflowNextStages = new List<WorkflowNextStage>
{
new SuccessStage() {NextStage = stage},
new FailureStage() {NextStage = stage}
};
workflow.Stages = new List<WorkflowStage> {stage};
ctx.Workflows.Add(workflow);
ctx.SaveChanges();
}
Setting the 'new SuccessStage' above to a different stage works just fine.
I am a bit stumped on this one...anyone have any ideas?
Do you have a foreach loop where you are iterating through the collection you are modifying? If so this may cause this error.

Resources