Change name, type and value of a page or block property in Episerver - episerver

We often need to refactor existing pages and blocks due to new requirements. Usually one or more properties are to be replaced by one or more new properties. Existing pages and blocks need to keep on working and it is not uncommon that settings made using the old properties need to affect the settings of the new properties. Creating new pages or blocks to replace existing ones is not an option - unless it can be automated.
Simple example #1: The old Boolean property UseDarkBackgroundColor is replaced by the new string property BackgroundColor. If UseDarkBackgroundColor was set, BackgroundColor should be set to a specific color.
Simple example #2: The old Boolean property IsWide is replaced by the new property IsNarrow (basically flipping name and value). If IsWide was true, IsNarrow should be false and vice versa.
In the past we have solved this by updating the Episerver database using an SQL script. I find this kind of scary and I would therefore like to find a better approach.
The following article gives me some hope: https://world.episerver.com/documentation/developer-guides/CMS/Content/Refactoring-content-type-classes#API
The information under the section "Renaming a content type by API" is easy to understand and when testing it the result was as expected. However, the text under the section "Changing the type for a property" is next to gibberish to me. I don't even know where to start, even after a lot of googling.
Does anybody know of an example that shows how to change the type (and value) of a property in Episerver without an SQL script?

We never change types for an existing property, but rather we introduce a new property, hide the old one from the UI, and (if applicable) implement a custom getter for the new property so that it uses the old property as "fallback" until an editor has published content with the new property.
Sometimes we migrate the old property value to the new property through some type of batch job, and then delete the old property.
We never manipulate the database directly.
Also check out migration steps, commonly used if you for example need to rename a property: https://www.jondjones.com/learn-episerver-cms/episerver-developers-tutorials/importing-content-into-episerver-programmatically/episerver-migration-steps-explained/

Just want to add to Ted's answer that you can hide the old property and use an existing value for the new by providing a fallback/default value in the getter:
[ScaffoldColumn(false)]
[Obsolete("BackgroundColor should be used now")]
public virtual bool UseDarkBackgroundColor { get; set; }
public virtual string BackgroundColor
{
get { return this.GetPropertyValue(block => block.BackgroundColor, UseDarkBackgroundColor ? "Black" : null); }
set { this.SetPropertyValue(block => block.BackgroundColor, value); }
}

Related

Entity Framework 6 query generates NullReferenceException

Edit: This is not a problem with ignorance of basic programming (such as trying to dereference a null object reference).
Edit: Added the stack trace from EF within Linqpad.
Using EF6, I have a very simple query :
var menu = dbcontext.Tree.Where(t => t.Left > 2 && t.Right < 10).ToList();
This worked right up until it mysteriously stopped. dbcontext.Tree is a view in SQL Server 2012. Using Linqpad5, I get the results I expect using its built-in connection. Setting up an EF connection to my project, I get the NRE. Checking the SQL, I can copy and paste that into a SQL query window and get the proper results. I get an NRE without the Where call, also.
I've tried updating my model from database to refresh anything. I've tried removing the view from the model and updating. I've tried deleting the model entirely and recreating it. I've restarted Visual Studio AND my computer. I get the same NRE for the query. I don't know what else I can try, and this NRE makes no sense to me at all, given I get the results I expect using everything but EF. I'd chalk it up to a bug with EF if I didn't see it working previously.
Has anyone dealt with this? Searching online for this specific set of circumstances has produced nothing.
Stack Trace :
at System.Data.Entity.Core.EntityKey.AddHashValue(Int32 hashCode, Object keyValue)
at System.Data.Entity.Core.EntityKey.GetHashCode()
at System.Collections.Generic.GenericEqualityComparer`1.GetHashCode(T obj)
at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
at System.Data.Entity.Core.Objects.ObjectStateManager.TryGetEntityEntry(EntityKey key, EntityEntry& entry)
at System.Data.Entity.Core.Objects.ObjectStateManager.FindEntityEntry(EntityKey key)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.HandleEntityAppendOnly[TEntity](Func`2 constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator`1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnumerator.MoveNext()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
The problem is that your Model (Tree in your example above) has one ore more properties that are not nullable (and possibly also marked as not nullable in the mapping) but the corresponding column in the data store is nullable. This exception would only manifest itself as soon as there was a record being retrieved that had a null value for one of those column(s).
Model fix - When updating the model be sure to use Nullable<T> or ? for nullable value types and if you have mappings defined (either via attributes or in types that inherit EntityTypeConfiguration) also specify that the property is optional there.
Data store fix - Alternatively change the data store schema and data to align it with what is expected in the model.

RavenDB - How to backpopulate "old" documents after adding new property to POCO?

I'm just starting to learn about NoSQL/Document storage this morning. I am used to EntityFramework/SQLServer.
My questions is the following: If I have a bunch of "documents" stored and somewhere down the line I add a property to my class that is needed by my app, how do I back-populate the already existing records?
If you change the model after the fact then you have a few options.
If you have a default value for the additional field and can wait until the next time that entity is saved for the database then you can simply add the new property and set the value to the defaultv value in the constructor.
You can use a IDocumentConversionListener (http://ayende.com/blog/66563/ravendb-migrations-rolling-updates)
You can also use https://github.com/khalidabuhakmeh/RavenMigrations which I have never used but it seems like it would do what you want.

Do Fluent conventions break lazy loading? (uNhAddIns)

I have a simple entity class in a WPF application that essentially looks like this:
public class Customer : MyBaseEntityClass
{
private IList<Order> _Orders;
public virtual IList<Order> Orders
{
get { return this._Orders; }
set {this._Orders = new ObservableCollection<Order>(value);}
}
}
I'm also using the Fluent automapper in an offline utility to create an NHibernate config file which is then loaded at runtime. This all works fine but there's an obvious performance hit due to the fact that I'm not passing the original collection back to NHibernate, so I'm trying to add a convention to get NHibernate to create the collection for me:
public class ObservableListConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
Type collectionType =
typeof(uNhAddIns.WPF.Collections.Types.ObservableListType<>)
.MakeGenericType(instance.ChildType);
instance.CollectionType(collectionType);
}
}
As you can see I'm using one of the uNhAddIns collections which I understand is supposed to provide support for both the convention and INotification changes, but for some reason doing this seems to break lazy-loading. If I load a custom record like this...
var result = this.Session.Get<Customer>(id);
...then the Orders field does get assigned an instance of type PersistentObservableGenericList but its EntityId and EntityName fields are null, and attempting to expand the orders results in the dreaded "illegal access to loading collection" message.
Can anyone tell me what I'm doing wrong and/or what I need to do to get this to work? Am I correct is assuming that the original proxy object (which normally contains the Customer ID needed to lazy-load the Orders member) is being replaced by the uNhAddIns collection item which isn't tracking the correct object?
UPDATE: I have created a test project demonstrating this issue, it doesn't reference the uNhAddins project directly but the collection classes have been added manually. It should be pretty straightforward how it works but basically it creates a database from the domain, adds a record with a child list and then tries to load it back into another session using the collection class as the implementation for the child list. An assert is thrown due to lazy-loading failing.
I FINALLY figured out the answer to this myself...the problem was due to my use of ObservableListType. In NHibernate semantics a list is an ordered collection of entities, if you want to use something for IList then you want an unordered collection i.e. a Bag.
The Eureka moment for me came after reading the answer to another StackOverflow question about this topic.

How to know if you're Dropping in the same app instance where you made the Drag in WPF?

the title pretty much explains my issue:
I am right now taking care of drag & drop in my app. I can have many instances of my app running at the same time, and I can drag from one instance to the other without trouble.
Now, I would like to know if I'm drag & dropping "internally" (i.e: the drop occurs in the same instance as the drag) or "externally" (the opposite)
I went this far: I need to add to my dragged data a unique ID (something like a PUID) that identifies the app where I'm making the drag. Then I can just compare this id to the one I have locally on the drop and see if it is the same.
I have no problem transferring such info in my drag Data, the issue is more to find this UId.
I have been thinking using the Process.GetCurrentProcess().MainWindowHandle; but I'm not sure if this is a good idea.
What option(s) do I have to make this work?
I would simply create a readonly Guid that gets set when you start your app.
You can put this wherever your main logic lives (MainWindow or ViewModel).
Here is a snippet:
public class MyViewModel
{
private readonly Guid mUID = Guid.NewGuid();
// In case you want a property for it
public string UniqueApplicationID
{
get { return mUID; }
}
public void OnDropHandler(MyViewModel objectBeingDropped)
{
if (objectBeingDropped.UniqueApplicationID == mUID)
return;
// Handle drop normally here
}
}
The D-n-D is much like an UI activity, than an internal one.
I would distinguish two contexts: dropping a file, and dropping some object (e.g. VS designer). In the first context there's no problem at all, because it doesn't matter where you pull the data. In the second case, you should know what object has been chosen. For instance, you have a listbox with many items (e.g. the alphabet chars), once the user D-n-D any of those items, the internal operation is a simple reference to the selected object. By pulling the data from another app, you won't be able to find your object, because the source is different.
In case of structs or strings, you may wrap them with a GUID, as you correctly proposed.

Passing parameters to a WPF Page via its Uri

In the context of a navigation-style WPF application (NavigationWindow, not XBAP):
Is it possible for a Hyperlink's NavigateUri to contain extra parameters, like path data or a querystring? E.g., is there some way I could set my NavigateUri to /Product.xaml/123 or /Product.xaml?id=123, and have my Product.xaml page be able to see that it was called with a parameter of 123?
You can do this. See http://www.paulstovell.com/wpf-navigation:
Although it's not obvious, you can
pass query string data to a page, and
extract it from the path. For example,
your hyperlink could pass a value in
the URI:
<TextBlock>
<Hyperlink NavigateUri="Page2.xaml?Message=Hello">Go to page 2</Hyperlink>
</TextBlock>
When the page is loaded, it can
extract the parameters via
NavigationService.CurrentSource, which
returns a Uri object. It can then
examine the Uri to pull apart the
values. However, I strongly recommend
against this approach except in the
most dire of circumstances.
A much better approach involves using
the overload for
NavigationService.Navigate that takes
an object for the parameter. You can
initialize the object yourself, for
example:
Customer selectedCustomer = (Customer)listBox.SelectedItem;
this.NavigationService.Navigate(new CustomerDetailsPage(selectedCustomer));
This assumes the page constructor
receives a Customer object as a
parameter. This allows you to pass
much richer information between pages,
and without having to parse strings.
Another way is to create a public variable on the destiny page and use a get/set property to assign a value to it.
On Page:
private Int32 pMyVar;
public Int32 MyVar
{
get { return this.pMyVar; }
set { this.pMyVar = value; }
}
When navigating to it:
MyPagePath.PageName NewPage = new MyPagePath.PageName();
NewPage.MyVar = 10;
this.MainFrameName.NavigationService.Navigate(NewPage);
When NewPage is loaded, the integer MyVar will be equal to 10.
MainFrameName is the frame you are using in case you are working with frame, but if not, the navigate command remains the same regardless.
Its my opinion, but it seems easier to track it that way, and more user friendly to those who came from C# before WPF.
Customer selectedCustomer = (Customer)listBox.SelectedItem;
this.NavigationService.Navigate(new CustomerDetailsPage(selectedCustomer));
Paul Stovell I think that using your suggestion will make your pages not garbage collected
because the whole instance will remain on Journal.

Resources