How to register custom FileSystemBinaryLargeObject - codefluent

How to configure codefluent runtime in odrder to plug in a custom class which inherits from FileSystemBinaryLargeObject ?
In our use case we have an entity with more than 100k files. I need to figure out how to chunk them in subdirectories..
Thanks

For all entities:
<configuration>
<configSections>
<section name="MyDefaultNamespace" type="CodeFluent.Runtime.CodeFluentConfigurationSectionHandler, CodeFluent.Runtime" />
</configSections>
<MyDefaultNamespace ... other attributes ...
binaryServicesTypeName="CustomBinaryServices, Assembly"
binaryServicesEntityConfigurationTypeName="CustomBinaryServicesConfiguration, Assembly"
/>
</configuration>
For a specific entity:
<configuration>
<configSections>
<section name="MyDefaultNamespace" type="CodeFluent.Runtime.CodeFluentConfigurationSectionHandler, CodeFluent.Runtime" />
</configSections>
<MyDefaultNamespace ... other attributes ... >
<binaryServices entityFullTypeName="MyDefaultNamespace.MyNamespace.MyEntity"
binaryServicesTypeName="CustomBinaryServices, Assembly"
binaryServicesEntityConfigurationTypeName="CustomBinaryServicesConfiguration, Assembly"/>
</MyDefaultNamespace>
</configuration>

I believe what you propose doesn't work. I've tested it to see my custom blob class is not getting instantiated.
According to the code below #binaryServicesTypeName attribute accepts only 4 predefined string values
: azure, azure2, filesystem, skydrive.
Otherwise the default "SqlServerBinaryLargeObject" blob object is set.
/// <summary>
/// Gets or sets the type name of the class deriving from the BinaryLargeObject class.
/// Based on the 'typeName' Xml attributes from the configuration file.
/// The default is SqlServerBinaryLargeObject.
///
/// </summary>
///
/// <value>
/// The configured type name.
/// </value>
public virtual string TypeName
{
get
{
if (this._typeName == null)
{
string defaultValue = (string) null;
if (this._configuration.PersistenceHook != null)
defaultValue = this._configuration.PersistenceHook.BinaryLargeObjectTypeName;
if (string.IsNullOrEmpty(defaultValue))
defaultValue = typeof (SqlServerBinaryLargeObject).AssemblyQualifiedName;
this._typeName = XmlUtilities.GetAttribute((XmlNode) this.Element, "binaryServicesTypeName", defaultValue);
if (this._typeName == null)
this._typeName = XmlUtilities.GetAttribute((XmlNode) this.Element, "typeName", defaultValue);
if (this._typeName != null && string.Compare(this._typeName, "azure", StringComparison.OrdinalIgnoreCase) == 0)
this._typeName = "CodeFluent.Runtime.Azure.AzureBinaryLargeObject, CodeFluent.Runtime.Azure";
if (this._typeName != null && string.Compare(this._typeName, "azure2", StringComparison.OrdinalIgnoreCase) == 0)
this._typeName = "CodeFluent.Runtime.Azure.AzureBinaryLargeObject, CodeFluent.Runtime.Azure2";
if (this._typeName != null && string.Compare(this._typeName, "filesystem", StringComparison.OrdinalIgnoreCase) == 0)
this._typeName = typeof (FileSystemBinaryLargeObject).AssemblyQualifiedName;
if (this._typeName != null && string.Compare(this._typeName, "skydrive", StringComparison.OrdinalIgnoreCase) == 0)
this._typeName = typeof (SkyDriveBinaryLargeObject).AssemblyQualifiedName;
}
return this._typeName;
}
set
{
this._typeName = value;
}
}
other ideas ?
PS: here is app.config demo file using generated schema for codefluent runtime
app.config

Related

#Mapping on mapstruct with List<> Of List<>

I've recently started to use mapstruct, while coding I stuck with a scenario. In order to solve the ambiguity between the below default methods, I'm trying to use the "qualifiedBy" on a List<
#Mapping(qualifiedBy = LineTestBO). Line => 1
List<ReturnABO> toCaptureLineItemsBOs(List<LineDTO> lineDTO);
#Named("LineTestBO")
default ReturnABO map(LineDTO lineDTO) {
if (lineDTO.getCurrency() != null && lineDTO.getNationalPermit() != null &&
lineDTO.getAmount() != null && lineDTO != null)
return this.toBO(lineDTO);
return null;
}
default returnABO toBO(LineDTO lineDTO) {
// To do here
}
But Line 1 shows the error as it needs to "target" to be specified. I'm not sure what should be the target here since Line is a collection object. Even If I don't use the #mapping the mapstuct generates the mapper implementation. I read the mapstuct documentation but could not follow much for this scenario. How the Named annotation can be used on List to explicity denote that this is name to be used ? Can someone help me? Thanks.
the target must be specified, so if your POJOs look like this
class A {
private List<ReturnABO> x;
}
class B {
private List<LineDTO> x;
}
in your mapper, you could have something like this
interface AMapper {
#Mapping(target="x", source="x", qualifiedByName = "LineTestBO")
A toA(B b);
#Named("LineTestBO")
default List<ReturnABO> lineTestBO (List<LineDTO> lines) {
return lines.stream().map(this::map).collect(Collectors.toList())
}
default ReturnABO map(LineDTO lineDTO) {
if (lineDTO.getCurrency() != null && lineDTO.getNationalPermit() != null &&
lineDTO.getAmount() != null && lineDTO != null)
return this.toBO(lineDTO);
return null;
}
}
The reason for the error is that using #Mapping on iterable mapping method doesn't make much sense.
What you are looking for is IterableMapping#qualifiedBy.
So in your case the mapper needs to look like:
#Mapper
public interface MyMapper {
#IterableMapping(qualifiedByName = "LineTestBO")
List<ReturnABO> toCaptureLineItemsBOs(List<LineDTO> lineDTO);
#Named("LineTestBO")
default ReturnABO map(LineDTO lineDTO) {
if (lineDTO.getCurrency() != null && lineDTO.getNationalPermit() != null && lineDTO.getAmount() != null && lineDTO != null) {
return this.toBO(lineDTO);
}
return null;
}
default returnABO toBO(LineDTO lineDTO) {
// To do here
}
}

MDrivenServer - how to expose viewmodel as a REST service

I use standalone MDrivenServer.
Question - is it possible to expose a viewmodel as a REST service in this configuration without MDrivenTurnkey installation, e.g. using url like ...Rest/Get?command=vmname&id=rootobjref ?
Thank you!
No the MDrivenServer has only the persistence mapper api's exposed. In order to simulate a viewmodel driven rest get-service you can go like this in a MVC application:
Derive your controller from ModelDrivenControllerBase<ESType>
/// <summary>
/// command should match a viewmodel, id should be external id for root or $null$
/// will try and match extra params to viewmodel variables
/// will execute actions on root level
/// will return complete vm on success as json except if string attribute RawJSon is found - then this is returned instead
/// </summary>
public virtual ActionResult Get(string command, string id)
{
Eco.ObjectRepresentation.IModifiableVariableList vars = FindAdditionalRequestParamsAndTreatAsVars();
if (string.IsNullOrEmpty(id))
id = ObjectId.nulltoken;
SaveVariablesToSessionState(command, id, vars);
VMClass onlinevm = CreateVMClassFromName(command, id);
if (onlinevm != null)
{
if (!CheckRestAllowed(onlinevm)) <-- turnkey checks the rest allowed flag
return Content("Must set RestAllowed on VM " + command);
foreach (var col in onlinevm.ViewModelClass.Columns)
{
if (col.IsAction)
{
col.ViewModel.ExecuteAction(col.ViewModelClass.RuntimeName, col.RuntimeName);
}
}
return GetJsonFromVm(onlinevm); <-- this can be implemented with the Tajson concept: https://wiki.mdriven.net/index.php/Tajson
}
else
return Content("Access denied");
}
/// <summary>
/// targetViewRootObject may be both string and IEcoObject.
/// If the newrootAsObject happens to be a guid string - and the IClass has a property guid - then we will try and PS-resolve the guid and use it as root
/// </summary>
protected VMClass CreateVMClassFromName(string targetViewName, object targetViewRootObject)
{
EnsureEcoSpace();
if (targetViewRootObject is string)
targetViewRootObject = SafeObjectForId((string)targetViewRootObject);
VMClass createdVMClass = ViewModelHelper.CreateFromViewModel(targetViewName, EcoSpace, targetViewRootObject as IEcoObject, false);
IEcoObject root = targetViewRootObject as IEcoObject;
if (root == null && createdVMClass.ViewModelClass.IClass is IClass && targetViewRootObject is string)
{
// Handle special case of a GUID as target
// Used to act on direct links for an object
if (targetViewRootObject is string)
{
root = EcoSpace.ExternalIds.ObjectForUnkownId(targetViewRootObject as string, createdVMClass.ViewModelClass.IClass as IClass);
if (root != null)
createdVMClass.Content = root.AsIObject();
}
}
bool vm_visibleDueToAccessGroups = createdVMClass.ViewModelClass.ViewModel.VisibleDueToAccessGroups(EcoSpace, root != null ? root.AsIObject() : null);
if (!vm_visibleDueToAccessGroups)
{
return null;
}
LoadVariablesFromSessionStateIfAvailable(targetViewName, SafeIDForObject(targetViewRootObject), createdVMClass.Variables);
return createdVMClass;
}

Managing persistent navigation views (MVVM, WPF)

I'm slowly learning MVVM in WPF. I code a GBA game editor. The editor consist of a main window (Editor.xaml) and depending which editor is selected in the menu, I'd like to display the corresponding persistent view (that is not destroyer when switching).
I'm trying to get working the TabControlEx class found in a few posts on SO such as here and here.
However I run into two problem: first, tabControlEx selectedItem does not change (fixed see edit) and second it seems on the TabItem OnMouseOver I lose the View (having a color Background property for the TabItem will switch to white color).
Now I'm pretty sure I'm missing something more or less obvious, but being new to MVVM I don't really know where to look. So here the code breakdown.
TabControlEx.cs
[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class TabControlEx : System.Windows.Controls.TabControl
{
// Holds all items, but only marks the current tab's item as visible
private Panel _itemsHolder = null;
// Temporaily holds deleted item in case this was a drag/drop operation
private object _deletedObject = null;
public TabControlEx()
: base()
{
// this is necessary so that we get the initial databound selected item
this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
/// <summary>
/// if containers are done, generate the selected item
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
UpdateSelectedItem();
}
}
/// <summary>
/// get the ItemsHolder and generate any children
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel;
UpdateSelectedItem();
}
/// <summary>
/// when the items change we remove any generated panel children and add any new ones as necessary
/// </summary>
/// <param name="e"></param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (_itemsHolder == null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
_itemsHolder.Children.Clear();
if (base.Items.Count > 0)
{
base.SelectedItem = base.Items[0];
UpdateSelectedItem();
}
break;
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
// Search for recently deleted items caused by a Drag/Drop operation
if (e.NewItems != null && _deletedObject != null)
{
foreach (var item in e.NewItems)
{
if (_deletedObject == item)
{
// If the new item is the same as the recently deleted one (i.e. a drag/drop event)
// then cancel the deletion and reuse the ContentPresenter so it doesn't have to be
// redrawn. We do need to link the presenter to the new item though (using the Tag)
ContentPresenter cp = FindChildContentPresenter(_deletedObject);
if (cp != null)
{
int index = _itemsHolder.Children.IndexOf(cp);
(_itemsHolder.Children[index] as ContentPresenter).Tag =
(item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
}
_deletedObject = null;
}
}
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
_deletedObject = item;
// We want to run this at a slightly later priority in case this
// is a drag/drop operation so that we can reuse the template
this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
new Action(delegate ()
{
if (_deletedObject != null)
{
ContentPresenter cp = FindChildContentPresenter(_deletedObject);
if (cp != null)
{
this._itemsHolder.Children.Remove(cp);
}
}
}
));
}
}
UpdateSelectedItem();
break;
case NotifyCollectionChangedAction.Replace:
throw new NotImplementedException("Replace not implemented yet");
}
}
/// <summary>
/// update the visible child in the ItemsHolder
/// </summary>
/// <param name="e"></param>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateSelectedItem();
}
/// <summary>
/// generate a ContentPresenter for the selected item
/// </summary>
void UpdateSelectedItem()
{
if (_itemsHolder == null)
{
return;
}
// generate a ContentPresenter if necessary
TabItem item = GetSelectedTabItem();
if (item != null)
{
CreateChildContentPresenter(item);
}
// show the right child
foreach (ContentPresenter child in _itemsHolder.Children)
{
child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
}
}
/// <summary>
/// create the child ContentPresenter for the given item (could be data or a TabItem)
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
ContentPresenter CreateChildContentPresenter(object item)
{
if (item == null)
{
return null;
}
ContentPresenter cp = FindChildContentPresenter(item);
if (cp != null)
{
return cp;
}
// the actual child to be added. cp.Tag is a reference to the TabItem
cp = new ContentPresenter();
cp.Content = (item is TabItem) ? (item as TabItem).Content : item;
cp.ContentTemplate = this.SelectedContentTemplate;
cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
cp.ContentStringFormat = this.SelectedContentStringFormat;
cp.Visibility = Visibility.Collapsed;
cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
_itemsHolder.Children.Add(cp);
return cp;
}
/// <summary>
/// Find the CP for the given object. data could be a TabItem or a piece of data
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
ContentPresenter FindChildContentPresenter(object data)
{
if (data is TabItem)
{
data = (data as TabItem).Content;
}
if (data == null)
{
return null;
}
if (_itemsHolder == null)
{
return null;
}
foreach (ContentPresenter cp in _itemsHolder.Children)
{
if (cp.Content == data)
{
return cp;
}
}
return null;
}
/// <summary>
/// copied from TabControl; wish it were protected in that class instead of private
/// </summary>
/// <returns></returns>
protected TabItem GetSelectedTabItem()
{
object selectedItem = base.SelectedItem;
if (selectedItem == null)
{
return null;
}
if (_deletedObject == selectedItem)
{
}
TabItem item = selectedItem as TabItem;
if (item == null)
{
item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
}
return item;
}
}
My main view:
Editor.xaml
<Window.Resources>
<ResourceDictionary Source="Resources/GlobalDictionary.xaml" />
</Window.Resources>
<DockPanel>
<DockPanel DockPanel.Dock="Top" KeyboardNavigation.TabNavigation="None">
<Menu Height="20" KeyboardNavigation.TabNavigation="Cycle">
<MenuItem Header="{StaticResource MenuFile}">
<MenuItem Header="{StaticResource MenuOpen}" />
<MenuItem Header="{StaticResource MenuSave}" />
<MenuItem Header="{StaticResource MenuExit}" Command="{Binding Path=CloseCommand}" />
</MenuItem>
<MenuItem Header="{StaticResource MenuEditors}">
<MenuItem Header="{StaticResource MenuMonster}" Command="{Binding Path=OpenMonsterEditor}"/>
</MenuItem>
<MenuItem Header="{StaticResource MenuHelp}">
<MenuItem Header="{StaticResource MenuAbout}" />
<MenuItem Header="{StaticResource MenuContact}" />
</MenuItem>
</Menu>
</DockPanel>
<controls:TabControlEx ItemsSource="{Binding AvailableEditors}"
SelectedItem="{Binding CurrentEditor}"
Style="{StaticResource BlankTabControlTemplate}">
</controls:TabControlEx>
</DockPanel>
GlobalDictionary.xaml
<DataTemplate DataType="{x:Type vm:GBARomViewModel}">
<vw:GBARomView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MonsterViewModel}">
<vw:MonsterView />
</DataTemplate>
<Style x:Key="BlankTabControlTemplate" TargetType="{x:Type control:TabControlEx}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type control:TabControlEx}">
<DockPanel>
<!-- This is needed to draw TabControls with Bound items -->
<StackPanel IsItemsHost="True" Height="0" Width="0" />
<Grid x:Name="PART_ItemsHolder" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I'm not sure if the following is right, but each view extent TabItem as follow. The first view on startup is shown correctly.
GBARomView.xaml
<TabItem x:Class="FF6AE.View.GBARomView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FF6AE.View"
mc:Ignorable="d"
d:DesignHeight="380" d:DesignWidth="646"
Background="BlueViolet">
<Grid >
</Grid>
</TabItem>
GBARomView.xaml.cs
public partial class GBARomView : TabItem
{
public GBARomView()
{
InitializeComponent();
}
}
Finally my main viewModel:
EDitorViewModel.cs
public class EditorViewModel: ViewModelBase
{
ViewModelBase _currentEditor;
ObservableCollection<ViewModelBase> _availableEditors;
RelayCommand _closeCommand;
RelayCommand _OpenMonsEditorCommand;
public EditorViewModel()
{
base.DisplayName = (string)AppInst.GetResource("EditorName");
_availableEditors = new ObservableCollection<ViewModelBase>();
_availableEditors.Add(new GBARomViewModel());
_availableEditors.Add(new MonsterViewModel());
_availableEditors.CollectionChanged += this.OnAvailableEditorsChanged;
_currentEditor = _availableEditors[0];
_currentEditor.PropertyChanged += this.OnSubEditorChanged;
}
public ViewModelBase CurrentEditor
{
get
{
if (_currentEditor == null)
{
_currentEditor = new GBARomViewModel();
_currentEditor.PropertyChanged += this.OnSubEditorChanged;
}
return _currentEditor;
}
set
{
_currentEditor = value;
// this is the thing I was missing
OnPropertyChanged("CurrentEditor");
}
}
void OnSubEditorChanged(object sender, PropertyChangedEventArgs e)
{
// For future use
}
public ObservableCollection<ViewModelBase> AvailableEditors
{
get
{
return _availableEditors;
}
}
void OnAvailableEditorsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// For future use
}
public ICommand OpenMonsterEditor
{
get
{
if (_OpenMonsEditorCommand == null)
_OpenMonsEditorCommand = new RelayCommand(param => this.OpenRequestOpen());
return _OpenMonsEditorCommand;
}
}
void OpenRequestOpen()
{
_currentEditor = _availableEditors[1];
}
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
_closeCommand = new RelayCommand(param => this.OnRequestClose());
return _closeCommand;
}
}
public event EventHandler RequestClose;
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
So basically I am lost why clicking on the monster editor in the menu does not switch the view though currentEditor value is changed (fixed) and why on mouse over on the tabItem I lose the background testing color of the view (it turns to white). <- still not fixed!
Any help would be appreciated. Thanks in advance.
Edit: I was missing the OnPropertyChanged("CurrentEditor"); in the setter of CurrentEditor.
Well basically my two questions were misunderstandings. First, the CurrentEditor attribute need OnPropertyChanged called in EditorViewModel.cs such as follow. This calls the ViewModelBase class OnPropertyChanged method:
public ViewModelBase CurrentEditor
{
get
{
if (_currentEditor == null)
{
_currentEditor = new GBARomViewModel();
_currentEditor.PropertyChanged += this.OnSubEditorChanged;
}
return _currentEditor;
}
set
{
_currentEditor = value;
OnPropertyChanged("CurrentEditor");
}
}
My second problem took longer to solve. I did not needed to have my views extent TabItem but instead something like DockPanel. This ensure the whole window will not have the TabItem IsMouseOver behavior of reverting the color back to default. Setting an background image such as follow work and I can navigate the view. I wasn't able to solve this with a control template of TabItem.
GbaRomView.xaml.cs
public partial class GBARomView : DockPanel
{
public GBARomView()
{
InitializeComponent();
}
}
GbaRomView.xaml
<DockPanel x:Class="FF6AE.View.GBARomView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DesignHeight="380" d:DesignWidth="646">
<DockPanel.Background>
<ImageBrush ImageSource="/Resources/Images/DefaultBackground.png"></ImageBrush>
</DockPanel.Background>
</DockPanel>

CRM 2011 Plugin - Check if OptionSet is not empty

I am writing a plugin where I check if a particular value of a field, an Option Set is equal to a specific value and if so, then I do certain actions.
Now, within the plugin C# code, how can I check that the Option Set field is not null - i.e., is set to the Default Value?
What I did (clearly, that is wrong) because, it never went past the Null check statement. And, if I did not have the check, then I get this error message
Error:
Unexpected exception from plug-in (Execute): CRM.AppStateHandler.Plugins.PostApplicationCreate: System.NullReferenceException: Object reference not set to an instance of an object.
Code:
application applicationEntity = entity.ToEntity<new_application>();
if (new_applicationEntity.new_Applicationstatus != null)
{
var applicationStatus = applicationEntity.new_Applicationstatus.Value;
if (applicationStatus == CRMConstants.EntityApplication.Attributes.ApplicationStatusOptions.Applying)
{
//my logic
}
}
File constants.cs has the following
class CRMConstants
{
public struct EntityApplication
{
public struct Attributes
{
public struct ApplicationStatusOptions
{
// More before this
public const int Applying = 100000006;
// More to come
}
}
}
I think SergeyS has your fix, but I'll add some other (hopefully) helpful comments.
Don't custom create structs for your Option Set Values. Use the CrmSrvcUtil to create enums for you automatically.
I get annoyed with having to check for OptionSetValues being null or not, so I use these extension methods to make my life easier:
/// <summary>
/// Returns the value of the OptionSetValue, or int.MinValue if it is null
/// </summary>
/// <param name="osv"></param>
/// <param name="value"></param>
/// <returns></returns>
public static int GetValueOrDefault(this OptionSetValue osv)
{
return GetValueOrDefault(osv, int.MinValue);
}
/// <summary>
/// Returns the value of the OptionSetValue, or int.MinValue if it is null
/// </summary>
/// <param name="osv"></param>
/// <param name="value"></param>
/// <returns></returns>
public static int GetValueOrDefault(this OptionSetValue osv, int defaultValue)
{
if (osv == null)
{
return defaultValue;
}
else
{
return osv.Value;
}
}
/// <summary>
/// Allows for Null safe Equals Comparison for more concise code. ie.
/// if(contact.GenderCode.NullSafeEquals(1))
/// vs.
/// if(contact.GenderCode != null && contact.gendercode.Value == 1)
/// </summary>
/// <param name="osv"></param>
/// <param name="value"></param>
/// <returns></returns>
public static bool NullSafeEquals(this OptionSetValue osv, int value)
{
if (osv == null)
{
return false;
}
else
{
return osv.Value == value;
}
}
/// <summary>
/// Allows for Null safe Equals Comparison for more concise code. ie.
/// if(contact.GenderCode.NullSafeEquals(new OptionSet(1)))
/// vs.
/// if(contact.GenderCode != null && contact.gendercode.Value == new OptionSet(1))
/// </summary>
/// <param name="osv"></param>
/// <param name="value"></param>
/// <returns></returns>
public static bool NullSafeEquals(this OptionSetValue osv, OptionSetValue value)
{
if (osv == null)
{
return osv == value;
}
else
{
return osv.Equals(value);
}
}
There are two methods each with an overload:
GetValueOrDefault - This is equivalent to the Nullable.GetValueOrDefault(). The one difference is rather than defaulting to 0, I default to int.MinValue to make sure I don't accidentally match on a 0 optionset value. The Overload allows you to specify the default value if you'd like.
NullSafeEquals - This is the one you'd be using in your code to not have to check for null
application applicationEntity = entity.ToEntity<new_application>();
if (applicationEntity.new_Applicationstatus.NullSafeEquals(CRMConstants.EntityApplication.Attributes.ApplicationStatusOptions.Applying))
{
//my logic
}
You are checking:
if (new_applicationEntity.new_Applicationstatus != null)
but you need to check:
if (applicationEntity.new_Applicationstatus != null)

Entity Framework Detached Object Merging

I have a scenario where I am using Entity Framework in a WCF service, and changes happen on a non-tracked instance of a type that is mapped back to the database via code-first (non-trivial updates and deletes throughout the instance's object tree). When I try to attach the non-tracked instance into the context, EF is only recognizing changes to the simple value types on the root object.
Does anyone know of an elegant solution for this scenario? I am looking for a way to do this by using a generic repository, and avoiding having to run through the instance's entire object tree managing the "attach/detach" state of every object. I have considered possibly using ValueInjecter or AutoMapper to run the changes on a fully hydrated and tracked instance of the "old" state in order for the context to pickup the changes. Also, how would Nhibernate handle this situation?
Thanks in advance for your input!
UPDATE (7/31/2012): I have updated the code to handle genericly-typed keys, and some typing issues with EF Proxies. Also added some helper extensions when dealing with IEntity types. This implementation isn't perfect, but it is very functional.
UPDATE (3/13/2012): I have added a feature request for cleaner merging in EF. The request is located here: http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions/suggestions/2679160-better-merging-change-tracking
UPDATE (3/12/2012): I have posted my solution below. It uses FubuCore, ValueInjecter, and requires entities to be marked with one of two interfaces, either IEntity, or IRecursiveEntity for recursive classes. The solution will handle recursive, self-linked entities.
Also, I am referencing a generic repository (Repository) that allows me to get a reference to the IDbSet that EF exposes. This could be substituded with any other generic or specific repository. Lastly, the IEntity interface uses an int? id, however you could define that however you want (Guid/Guid?). The solution itself isn't quite as elegant as I would like, however it allows for much more elegant data access code when behind a physical WCF service boundary.
public class DomainMergeInjection : ConventionInjection
{
private readonly Repository _repository;
private readonly Dictionary<string, object> _potentialParentObjectDump;
private readonly Cache<Type, Type> _entityTypesAndKeysCache;
public DomainMergeInjection(Repository repository)
{
_repository = repository;
_potentialParentObjectDump = new Dictionary<string, object>();
_entityTypesAndKeysCache = new Cache<Type, Type>();
}
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if(c.SourceProp.Value == null)
return null;
//for value types and string just return the value as is
if(c.SourceProp.Type.IsSimple())
return c.SourceProp.Value;
//TODO: Expand on this to handle IList/IEnumerable (i.e. the non-generic collections and arrays).
//handle arrays
if(c.SourceProp.Type.IsArray)
{
var sourceArray = c.SourceProp.Value as Array;
// ReSharper disable PossibleNullReferenceException
var clonedArray = sourceArray.Clone() as Array;
// ReSharper restore PossibleNullReferenceException
for(int index = 0; index < sourceArray.Length; index++)
{
var sourceValueAtIndex = sourceArray.GetValue(index);
//Skip null and simple values that would have already been moved in the clone.
if(sourceValueAtIndex == null || sourceValueAtIndex.GetType().IsSimple())
continue;
// ReSharper disable PossibleNullReferenceException
clonedArray.SetValue(RetrieveComplexSourceValue(sourceValueAtIndex), index);
// ReSharper restore PossibleNullReferenceException
}
return clonedArray;
}
//handle IEnumerable<> also ICollection<> IList<> List<>
if(c.SourceProp.Type.IsGenericEnumerable())
{
var t = c.SourceProp.Type.GetGenericArguments()[0];
if(t.IsSimple())
return c.SourceProp.Value;
var tlist = typeof(List<>).MakeGenericType(t);
dynamic list = Activator.CreateInstance(tlist);
var addMethod = tlist.GetMethod("Add");
foreach(var sourceItem in (IEnumerable)c.SourceProp.Value)
{
addMethod.Invoke(list, new[] { RetrieveComplexSourceValue(sourceItem) });
}
return list;
}
//Get a source value that is in the right state and is tracked if needed.
var itemStateToInject = RetrieveComplexSourceValue(c.SourceProp.Value);
return itemStateToInject;
}
private object RetrieveComplexSourceValue(object source)
{
//If the source is a non-tracked type, or the source is a new value, then return its value.
if(!source.ImplementsIEntity(_entityTypesAndKeysCache) || source.IsEntityIdNull(_entityTypesAndKeysCache))
return source;
object sourceItemFromContext;
//Handle recursive entities, this could probably be cleaned up.
if(source.ImplementsIRecursiveEntity())
{
var itemKey = source.GetEntityIdString(_entityTypesAndKeysCache) + " " + ObjectContext.GetObjectType(source.GetType());
//If we have a context item for this key already, just return it. This solves a recursion problem with self-linking items.
if(_potentialParentObjectDump.ContainsKey(itemKey))
return _potentialParentObjectDump[itemKey];
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Add the class into the object dump in order to avoid any infinite recursion issues with self-linked objects
_potentialParentObjectDump.Add(itemKey, sourceItemFromContext);
}
else
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Recursively use this injection class instance to inject the source state on to the context source state.
var itemStateToInject = sourceItemFromContext.InjectFrom(this, source);
return itemStateToInject;
}
private object GetSourceItemFromContext(object source)
{
if(source == null)
return null;
//Using dynamic here to "AutoCast" to an IEntity<>. We should have one, but it's important to note just in case.
dynamic sourceEntityValue = source;
var sourceEntityType = ObjectContext.GetObjectType(source.GetType());
var sourceKeyType = sourceEntityType.GetEntityKeyType();
var method = typeof(DomainMergeInjection).GetMethod("GetFromContext", BindingFlags.Instance | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(sourceEntityType, sourceKeyType);
var sourceItemFromContext = generic.Invoke(this, new object[] { new object[] { sourceEntityValue.Id } });
return sourceItemFromContext;
}
// ReSharper disable UnusedMember.Local
private TItem GetFromContext<TItem, TKey>(object[] keys) where TItem : class, IEntity<TKey>
// ReSharper restore UnusedMember.Local
{
var foundItem = _repository.GetDbSet<TItem>().Find(keys);
return foundItem;
}
}
public static class EntityTypeExtensions
{
/// <summary>
/// Determines if an object instance implements IEntity.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache">A cache to hold types that do implement IEntity. If the cache does not have the Type and the Type does implement IEntity, it will add the type to the cache along with the </param>
/// <returns></returns>
public static bool ImplementsIEntity(this object entity, Cache<Type, Type> entityCache = null)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityCache != null && entityCache.Has(entityType))
return true;
var implementationOfIEntity = entityType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IEntity<>));
if(implementationOfIEntity == null)
return false;
if(entityCache != null)
{
var keyType = implementationOfIEntity.GetGenericArguments()[0];
entityCache.Fill(entityType, keyType);
}
return true;
}
/// <summary>
/// Determines if an object instances implements IRecurisveEntity
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static bool ImplementsIRecursiveEntity(this object entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
var implementsIRecursiveEntity = entityType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRecursiveEntity<>));
return implementsIRecursiveEntity;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static bool IsEntityIdNull(this object entity, Cache<Type, Type> entityCache = null)
{
bool isEntityIdNull = ExecuteEntityIdMethod<bool>("IsEntityIdNull", entity, entityCache);
return isEntityIdNull;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static string GetEntityIdString(this object entity, Cache<Type, Type> entityCache = null)
{
string entityIdString = ExecuteEntityIdMethod<string>("GetEntityIdString", entity, entityCache);
return entityIdString;
}
private static T ExecuteEntityIdMethod<T>(string methodName, object entityInstance, Cache<Type, Type> entityCache = null)
{
if(!entityInstance.ImplementsIEntity(entityCache))
throw new ArgumentException(string.Format("Parameter entity of type {0} does not implement IEntity<>, and so ist not executable for {1}!", entityInstance.GetType(), methodName));
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entityInstance.GetType());
var keyType = entityCache != null ? entityCache[entityType] : entityType.GetEntityKeyType();
var method = typeof(EntityTypeExtensions).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(keyType);
T returnValue = (T)generic.Invoke(null, new[] { entityInstance });
return returnValue;
}
private static string GetEntityIdString<TKey>(IEntity<TKey> entity)
{
var entityIdString = entity.Id.ToString();
return entityIdString;
}
private static bool IsEntityIdNull<TKey>(IEntity<TKey> entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityType.IsPrimitive)
return false;
//NOTE: We know that this entity's type is NOT primitive, therefore we can cleanly test for null, and return properly.
// ReSharper disable CompareNonConstrainedGenericWithNull
var entityIdIsNull = entity.Id == null;
// ReSharper restore CompareNonConstrainedGenericWithNull
return entityIdIsNull;
}
public static Type GetEntityKeyType(this Type typeImplementingIEntity)
{
var implementationOfIEntity = typeImplementingIEntity.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntity<>));
if(implementationOfIEntity == null)
throw new ArgumentException(string.Format("Type {0} does not implement IEntity<>", typeImplementingIEntity));
var keyType = implementationOfIEntity.GetGenericArguments()[0];
return keyType;
}
}
public interface IEntity<TKey>
{
TKey Id { get; set; }
}
public interface IRecursiveEntity<TKey> : IEntity<TKey>
{
IRecursiveEntity<TKey> Parent { get; }
IEnumerable<IRecursiveEntity<TKey>> Children { get; }
}
you could use the detached object only as a DTO,
and after refill the object from context with values from the DTO
with ValueInjecter this would be:
//manually
conObj.InjectFrom(dto);
conObj.RefTypeProp.InjectFrom(dto.RefTypeProp);
...
//or by writing a custom injection:
conObj.InjectFrom<ApplyChangesInjection>(dto);
here's the Injection that will do that automatically, (I did it by modifying a bit the DeepClone Injection from VI's home page)
the trick here is that the Injection uses itself in the SetValue method
public class ApplyChangesInjection : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if (c.SourceProp.Value == null) return null;
//for value types and string just return the value as is
if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
return c.SourceProp.Value;
//handle arrays - not impl
//handle IEnumerable<> also ICollection<> IList<> List<> - not impl
//for simple object types apply the inject using the corresponding source
return c.TargetProp.Value
.InjectFrom<ApplyChangesInjection>(c.SourceProp.Value);
}
}
//Note: I'm not handling collections in this injection, I just wanted you to understand the principle,
you can look at the original http://valueinjecter.codeplex.com/wikipage?title=Deep%20Cloning&referringTitle=Home