GlassMapper SC nullable boolean fields causing Failed item resolve - sitecore

We have recently upgraded from Glass.Sitecore.Mapper 2.0.12.0 to Glass.Mapper.Sc 3.3.1.53 (we can't upgrade any higher as we are using Sitecore 6).
We are having problem with saving items with nullable boolean fields
i.e.
[SitecoreType(TemplateId = Templates.Category)]
public class Category : AggregateBase
{
...
[SitecoreField(FieldName = CategoryFields.Requires360)]
public virtual bool? Requires360 { get; set; }
[SitecoreField(FieldName = CategoryFields.RequiresCatwalk)]
public virtual bool? RequiresCatwalk { get; set; }
}
Where AggregateBase contains the standard sitecore fields
e.g.
[SitecoreType]
public class AggregateBase
{
[SitecoreId]
public virtual Guid Id { get; set; }
[SitecoreInfo(SitecoreInfoType.Name)]
public virtual string Name { get; set; }
...
}
If the nullable bool field is null it saves without issue.
If the nullable field has a value we get the following exception
"Failed item resolve - You cannot save a class that does not contain a
property that represents the item ID. Ensure that at least one
property has been marked to contain the Sitecore ID. Type:
System.Boolean"
This configuration worked previously and seems to work with other nullable types e.g. int?
The underlying storage in the template field is "Single-Line Text".
Does anyone have any ideas?
Regards,
Mark

Related

Map Droplist item to complextype using Glass.Mapper

I have created two Sitecore templatates MenuItem and MainNavigation.
I have created two interfaces wich are based on these templates:
[SitecoreType(TemplateId = "{C824E484-F4A6-475C-AFAF-308FF4BBA5A9}", AutoMap = true)]
public interface IMenuItem
{
string Title { get; set; }
IEnumerable<IMenuItem> SubMenuItems { get; set; }
}
[SitecoreType(TemplateId = "{68947CC0-7658-4188-889D-4E88B84F3BC2}", AutoMap = true)]
public interface IMainNavigation
{
IMenuItem MenuHeaderItem { get; set; }
IEnumerable<IMenuItem> MenuItems { get; set; }
}
The mapping MenuItems from an MultiList is working.
The mapping MenuHeaderItem from an Droplist is not.
In the template I have provided an query in the datasource so only MenuItems can be selected.
How can I make this mapping work?
I've tried v4.0.5.54 and now I have updated to version 4.2.1.188
I have found the answer myself. In the template I have to use sitecore type Droplink instead of DropList.
Droplist only stores the selected item name as a string. Droplink also stores the GUID.
What if you try to put this Annotation (see below) for the property that is not being populated:
[SitecoreField(FieldName = "MenuHeaderItem", FieldType = SitecoreFieldType.Droplist)]

EF6 Code First: How can I centralize content for separate sections of my site?

I want to store all our site's content in one central Content table but relate it to each section of the site. Something like:
Content (for the actual content byte[] and basic info all sections use)
ResearchArticleContent (basically has the related ContentId from the content table and extra cols for info specific to ResearchArticles)
ResearchArticle
ExecutiveContent (basically has the related ContentID from Content table and extra cols for specific data for Executives)
Executive
...and so on.
I'm having trouble understanding the whole code first approach as it pertains to ForeignKeys and InverseProperties. That's the real issue.
So, say I have these two classes as an example:
public class Content
{
[Key]
public int ContentId { get; set; }
public int ContentType { get; set; }
public byte[] ContentBytes { get; set; }
public DateTime AddedDate { get; set; }
[**`InverseProperty or ForeignKey???`**("ResearchArticleContent")]
public virtual ResearchArticleContent ResearchArticleContent { get; set; }
}
and:
public class ResearchArticleContent
{
[Key]
public int ResearchArticleContentId { get; set; }
[ForeignKey("ContentId")]
public virtual Content Content {get;set;}
public int ResearchArticleId { get; set; }
[ForeignKey("ResearchArticleId")]
public virtual ResearchArticle RelatedArticle { get; set; }
}
Where do I put the ForeignKeys / InverseProperties to relate these correctly. Because ideally, I will have Executivecontent, ResearchArticlecontent and so on for each section of the site. (I am following the precedent already laid out in a Data-First prj that I am mimicking so this is the way I have do this, fyi.)
Entity framework requires a type identifier field when you store compound objects in a single table; however, you can get around this pretty easily using views. To use views, create a single content table and a > base < class. Do not apply the TableAttribute data annotation to the base class. All other data annotations are fine.
public class ContentBase
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ContentId { get; set; }
public string Content { get; set; }
...
}
Then, you can create derived classes that more closely represent the content and apply the TableAttribute data annotation to those. For example,
[Table("ResearchArticleView")]
public class ResearchArticle : ContentBase
{
...you can add more properties here that are included in the view...
...and not necessarily the underlying table, like from a joined table...
...or just use the class as is, so that you have a better name...
}
To use this, set up a view called ResearchArticleView that includes the columns in the base class, as well as any computed or joined columns you want, then add a DbSet to your context that represents the view.
I recommend having content tables for each type of content and then use the method I've described for derived types for each content type. For example, create a base for research articles and a base for execute content. Because, when your database gets big and full of content, having one monolithic content table may cause you backup and optimization issues.

Sitecore Glass data model inheritence

I am using the Glass Mapper on a Sitecore instance where I have a basic data template structure of
Base
BaseWithList
BaseWithExtraContent
BaseWithExtraContentAndCallToActionLink
I have added model classes in my project to follow this structure too. My class names match my template names.
[SitecoreType(TemplateId = "{5D19BD92-799E-4DC1-9A4E-1DDE3AD68DAD}", AutoMap = true)]
public class Base
{
public virtual string Title {get;set;}
public virtual string Content {get;set;}
}
[SitecoreType(TemplateId = "{0491E3D6-EBAA-4E21-B255-80F0607B176D}", AutoMap = true)]
public class BaseWithExtraContent : Base
{
public virtual string ExtraContent {get;set;}
}
[SitecoreType(TemplateId = "{95563412-7A08-46A3-98CB-ABC4796D57D4}", AutoMap = true)]
public class BaseWithExtraContentAndCallToActionLink : BaseWithExtraContent
{
public virtual string CallToActionLink {get;set;}
}
These data models are used from another class that has a list of base type, I want to be able to store any derived type in here so I added attributes as detailed in this tutorial
[SitecoreType(AutoMap = true)]
public class HomePage
{
[SitecoreChildren(InferType = true)]
[SitecoreField(FieldName = "Widgets")]
public virtual IEnumerable<Base> Widgets { get; set; }
}
According to the tutorial this should work. However the list of widget just contains class of the base type.
I then found a later tutorial that said that if you have separated out the models to a different assemblies than the one Glass is installed in you have to add an AttributeConfigurationLoader pointing to the assembly your models are in. The base and derived types are all in the same assembly so I wasn't sure this would solve the issue, but I tried it anyway.
My custom loader config looks like this:
public static class GlassMapperScCustom
{
public static void CastleConfig(IWindsorContainer container)
{
var config = new Config {UseWindsorContructor = true};
container.Install(new SitecoreInstaller(config));
}
public static IConfigurationLoader[] GlassLoaders()
{
var attributes = new AttributeConfigurationLoader("Project.Data");
return new IConfigurationLoader[] {attributes};
}
public static void PostLoad(){
//Remove the comments to activate CodeFist
/* CODE FIRST START
var dbs = Sitecore.Configuration.Factory.GetDatabases();
foreach (var db in dbs)
{
var provider = db.GetDataProviders().FirstOrDefault(x => x is GlassDataProvider) as GlassDataProvider;
if (provider != null)
{
using (new SecurityDisabler())
{
provider.Initialise(db);
}
}
}
* CODE FIRST END
*/
}
}
Upon doing the custom loader config I now get an "Ambiguous match found" exception. I have checked to see if there are any other non Glass attributes set in the classes in that assembly and there aren't.
Any ideas? I guess there are 2 questions.
Why does using the inferred type attribute not load the correct types and only the base types?
Why when I attempt to solve this by adding a custom attribute loader do I get the exception?
Widgets property has two attributes - it's either mapped to the children elements of the item, or a field, can't be both.

Getting an Internal Link with Glass.Mapper

I've got an Internal Link set up in Sitecore, and I'm trying to map the field using Glass.Mapper, but it just keeps coming back empty, and I'm not sure what I'm doing wrong.
The template in Sitecore is pretty simple:
The Source of the link is set to a folder that only allows content based on the 'System' template to be created.
In my code, I have an object set up:
namespace Playground.GlassObjects
{
public partial class Status
{
public virtual string Description { get; set; }
public virtual string StatusCode { get; set; }
public virtual Glass.Mapper.Sc.Fields.Link System { get; set; }
}
}
Which is being used basically like this:
public void DoStuff(Sitecore.Data.Items.Item item)
{
var status = item.GlassCast<Status>();
this.DoOtherStuff(status);
}
What I'm running into is glassObj.Description, and glassObj.StatusCode are being wired up exactly like I want/expect, but glassObj.System is not.
Can anyone tell me what I'm doing wrong here? I'm at a loss right now, with all the magic that's going on behind the scenes.
The Glass.Mapper.Sc.Fields.Link class is designed to work with the General Link field. The internal link field stores values as paths e.g /sitecore/content/home/events. This means it isn't compatible with the Link class.
Instead you should map it to another class you have created.
public partial class Status
{
public virtual string Description { get; set; }
public virtual string StatusCode { get; set; }
public virtual MySystem System { get; set; }
}
public class MySystem{
public virtual string Url { get; set; }
public virtual string MyField { get; set; }
}
Fast forward to 2022 Internal Link field seems to be working with Glassmapper without any extra effort. All you have to do is add Internal Link case to GlassGenerator.tt file on the project where you will generate the template.
This will ensure your model will have Link field like this:
[SitecoreField(FieldId = "{D2CF138A-0A1C-4766-B250-F56E9458B624}")]
Link InternalLinkField{ get; set; }
It will have some info populated and most of the other properties will be null. The ones that will help you are:
Url (full path to the internal link item)
TargetId (ID of the internal link item)
Here are the available properties:
There is an alternative to that, you can get the link from fields like this:
yourGlassItem.Item.Fields["InternalLinkFieldName"]
You will get the entire Internal link Item. You can use Value or InheritedValue property to get the path of the linked item.

many-to-one ForeignKeys in SQL CE db developed with EF Code First

This is my first foray in either SQL CE or EF, so I may have a lot of misunderstandings. I've searched a lot of blog entries but still can't seem to get this right.
I have an MVC3 web site for registrations for a race we're running. I have a RaceEvents table, and a Runners table, where each RaceEvent will have many runners registered it for it, i.e., Many-to-One. Here are the POCO's with extraneous data stripped out:
public class RaceEvent
{
[Required]
public int Id { get; set; }
public virtual ICollection<Runner> Runners { get; set; }
}
public class Runner
{
[Required]
public int Id { get; set; }
[Required]
public int RaceEventId { get; set;}
[ForeignKey("RaceEventId")]
public RaceEvent RaceEvent { get; set; }
}
Which, as much as I can figure out, ought to work. As I understand it, it should figure out by convention that RaceEventId is a foreign key to RaceEvents. And if that's not good enough, I'm telling it with the ForeignKey attribute.
However, it doesn't seem to be working. Whenever I insert a new runner, it is also inserting a new entry in the RaceEvents table. And when I look at the table diagram in ServerExplorer, it shows two gold keys at the top of the Runners table diagram, one for Id, identified in the properties as a PrimaryKey, and the other for RaceEventId, not identified as a PrimaryKey, but indicated in the properties to be for table Runners, rather than for table RaceEvents. I would expect a gold key for Id, but a silver ForeignKey for RaceEventId.
FWIW, I don't really care about the ICollection in the RaceEvent, but the blog entries all seemed to imply that it was necessary.
Can anybody help me get this right?
Thanks.
Ok,
Sorry I did not read your question in enough detail. In our project this is how we would represent what your doing. I looked in SSMS and it is not showing said grey key, but it does not create a race event every time you add a runner. Although you do need to make sure when you create a runner that you set the race event property.
public class DB : DbContext
{
public DB()
: base("Data Source=(local);Initial Catalog=DB;Integrated Security=True")
{
}
public IDbSet<Runner> Runners { get; set; }
public IDbSet<RaceEvent> RaceEvents { get; set; }
}
public class RaceEvent
{
[Key]
public int RaceEventID { get; set; }
}
public class Runner
{
[Key]
public int RunnerID { get; set; }
[Required]
public virtual RaceEvent RaceEvent { get; set; }
}
Any question let me know.
You need to override the model creating in the DbContext. Below is a sample for AnsNet_User & AspNet_Roles N:N relationship
protected override void OnModelCreating(DbModelBuilder dbModelBuilder)
{
dbModelBuilder.Entity<aspnet_Users>().HasMany(a => a.aspnet_Roles).WithMany(b =>
b.aspnet_Users).Map(
m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
m.ToTable("aspnet_UsersInRoles");
});
base.OnModelCreating(dbModelBuilder);
}