Accessor ///Comments and T4 templates - t4

I'm creating accessors for a POCO using T4 templates and when I attempt to generate the accessor comments they don't work when the property is hovered over in VS.
I build the accessors when looping through the columns like so
// Accessors
if(i>1){stbAccessors.Append("\t\t");}
if(col.IsForeignKey)
{
stbAccessors.Append("/// <summary>\r\n\t\t");
stbAccessors.Append("/// FOREIGN KEY\r\n\t\t");
stbAccessors.Append("/// <summary>\r\n\t\t");
}
stbAccessors.Append("[DataMember()]\r\n");
stbAccessors.Append("\t\tpublic ").Append(propertyType).Append(" ").Append(col_name).Append("\r\n");
stbAccessors.Append("\t\t{\r\n");
stbAccessors.Append("\t\t\tget { return").Append(" _").Append(col.Name).Append("; }\r\n");
stbAccessors.Append("\t\t\tset\r\n");
stbAccessors.Append("\t\t\t{\r\n");
stbAccessors.Append("\t\t\t\tif (").Append("_").Append(col.Name).Append(" == value){ return; }\r\n");
stbAccessors.Append("\t\t\t\t_").Append(col.Name).Append(" = value;\r\n");
stbAccessors.Append("\t\t\t\tChanged = true;\r\n");
stbAccessors.Append("\t\t\t}\r\n");
stbAccessors.Append("\t\t}\r\n\r\n");
I then generate the accessor code like so.
#region " ACCESSORS "
/// <summary>
/// Returns TRUE if data on this object record has changed
/// </summary>
[DataMember()]
public bool Changed { get; set; }
<#=stbAccessors.ToString() #>
#endregion
The Changed accessor displays the text "Returns TRUE if data on this object record has changed" properly when hovered over, but any that are marked as "FOREIGN KEY" do not.
Any ideas?
Thanks for any help.
Here is the full code of my DTO generator
<##import namespace="System" #><##>
<##import namespace="System.IO" #><##>
<##import namespace="System.Text" #><##>
<##assembly name="Microsoft.SqlServer.Smo"#><##>
<##assembly name="Microsoft.SqlServer.ConnectionInfo"#><##>
<##assembly name="Microsoft.SqlServer.Management.Sdk.Sfc"#><##>
<##import namespace="Microsoft.SqlServer.Management.Smo"#><##>
<##assembly name="System.Data"#><##>
<#+public void GenerateDTO(Table target_table, string classNamespace, string destinationFolder, string executed_by)
{
// StringBuilders for Fields, Properties and Parameters.
StringBuilder stbMembers = new StringBuilder();
StringBuilder stbUserSaveEditMembers = new StringBuilder();
StringBuilder stbAccessors = new StringBuilder();
StringBuilder stbUserSaveEditAccessors = new StringBuilder();
StringBuilder stbConstructorParameters = new StringBuilder();
StringBuilder stbClassProperties = new StringBuilder();
StringBuilder stbUserSaveEditClassProperties = new StringBuilder();
// Object Name
string ObjectName = SnakeCaseToCamelCase(target_table.Name);
// Keep count so we don't whitespace the last property/column
int columnCount = target_table.Columns.Count;
int i = 0;
string col_identity = string.Empty;
// Iterate all columns
foreach (Column col in target_table.Columns)
{
i++;
string propertyType = GetNetDataType(col.DataType.Name);
// If we can't map it, skip it
if (string.IsNullOrWhiteSpace(propertyType))
{
// Skip
continue;
}
// Make propertytype nullable for nullable columns
if (col.Nullable && propertyType != "string" && propertyType != "byte[]")
{
propertyType += "?";
}
// Build Fields & Class Properties
string col_name = SnakeCaseToCamelCase(col.Name);
// Capture Identity column name
if(col.Identity)
{
col_identity = col_name;
}
// TABLE
// Members
stbMembers.Append("private ").Append(propertyType).Append(" _").Append(col.Name).Append(";\r\n\t\t");
// Accessors
if(i>1){stbAccessors.Append("\t\t");}
if(col.IsForeignKey)
{
stbAccessors.Append("/// <summary>\r\n\t\t");
stbAccessors.Append("/// FOREIGN KEY\r\n\t\t");
stbAccessors.Append("/// <summary>\r\n\t\t");
}
stbAccessors.Append("[DataMember()]\r\n");
stbAccessors.Append("\t\tpublic ").Append(propertyType).Append(" ").Append(col_name).Append("\r\n");
stbAccessors.Append("\t\t{\r\n");
stbAccessors.Append("\t\t\tget { return").Append(" _").Append(col.Name).Append("; }\r\n");
stbAccessors.Append("\t\t\tset\r\n");
stbAccessors.Append("\t\t\t{\r\n");
stbAccessors.Append("\t\t\t\tif (").Append("_").Append(col.Name).Append(" == value){ return; }\r\n");
stbAccessors.Append("\t\t\t\t_").Append(col.Name).Append(" = value;\r\n");
stbAccessors.Append("\t\t\t\tChanged = true;\r\n");
stbAccessors.Append("\t\t\t}\r\n");
stbAccessors.Append("\t\t}\r\n\r\n");
// Class Properties
stbClassProperties.Append("_").Append(col.Name).Append(" =").Append(" p").Append(col_name).Append(";");
if(i < columnCount)
{
stbClassProperties.Append("\r\n\t\t\t");
}
// Class Parameters
stbConstructorParameters.Append(propertyType).Append(" p").Append(col_name);
if(i < columnCount)
{
stbConstructorParameters.Append(",\r\n\t\t\t\t\t\t\t\t");
}
}#>
using System;
using System.Runtime.Serialization;
namespace <#=classNamespace#>
{
#region " History "
// <remarks>
//-------------------------------------------------------------------------------------------------------------------------------
// Purpose : Data Transfer Object for <#=ObjectName #>
// T4 Template : DTOGenerator.tt
// Date Created : <#=DateTime.Now.ToString(#"MM\/dd\/yyyy")#>
// Created By : <#=executed_by#>
// Notice : Copyright © <#=DateTime.Now.ToString(#"yyyy")#> GooeyPC, All rights reserved.
//-------------------------------------------------------------------------------------------------------------------------------
// History:
//
//-------------------------------------------------------------------------------------------------------------------------------
// </remarks>
#endregion
/// <summary>
/// <#=ObjectName #> Data Transfer Object
/// </summary>
[DataContract()]
public class <#=ObjectName #>DTO
{
#region " MEMBERS "
// TABLE FIELDS
<#=stbMembers.ToString() #>
#endregion
#region " ACCESSORS "
/// <summary>
/// Returns TRUE if data on this object record has changed
/// </summary>
[DataMember()]
public bool Changed { get; set; }
<#=stbAccessors.ToString() #>
#endregion
#region " PROPERTIES - READ ONLY "
/// <summary>
/// Returns TRUE if primary key id != 0 (i.e. valid primary key id exists.)
/// </summary>
public virtual bool RecordExists
{
get { return (<#=col_identity #> != 0); }
}
#endregion
#region " CONSTRUCTORS "
/// <summary>
/// Description of <#=ObjectName #>
/// </summary>
public <#=ObjectName #>DTO()
{
// Default constructor is needed
}
/// <summary>
/// Description of <#=ObjectName #>
/// </summary>
public <#=ObjectName #>DTO(<#=stbConstructorParameters.ToString() #>)
{
// Set Class Properties
<#=stbClassProperties.ToString() #>
}
#endregion
}
}
<#+
// Write new class to its own file
SaveOutput(destinationFolder, ObjectName + "DTO.cs");}
#>
This is the code generated for an accessor that is supposed to have the "FOREIGN KEY" comment.
/// <summary>
/// FOREIGN KEY
/// <summary>
[DataMember()]
public Int32? YourChildTableNameId
{
get { return _your_child_table_name_id; }
set
{
if (_your_child_table_name_id == value){ return; }
_your_child_table_name_id = value;
Changed = true;
}
}

Related

Kentico 10 Filtered Repeater with multiple filters not working properly

I have a data source, filter, and a filtered repeater that I have created custom filters for.
For some reason, both my filters work without the other on the screen. One works fine with the other filter on the screen.
The last filter refuses to work when I have the other filter on the screen but works fine without anything else.
I am pretty sure it has something to do with my code behind files which I will put below.
FILTER#1
using CMS.DocumentEngine;
using CMS.Helpers;
using System;
using System.Web;
using System.Web.UI.WebControls;
using CMS.DocumentEngine.Web.UI;
public partial class CMSGlobalFiles_SectorFilterControl : CMSAbstractDataFilterControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
/// <summary>
/// Sets up the inner child controls.
/// </summary>
private void SetupControl()
{
// Hides the filter if StopProcessing is enabled
if (this.StopProcessing)
{
this.Visible = false;
}
// Initializes only if the current request is NOT a postback
else if (!RequestHelper.IsPostBack())
{
// Loads product departments as filtering options
InitializeClientSectors();
}
}
/// <summary>
/// Loads all existing product departments as filtering options into the department drop-down list.
/// </summary>
private void InitializeClientSectors()
{
// Adds the default '(all)' option
this.drpSector.Items.Insert(0, new ListItem("(all)", "##ALL##"));
var clientSectors = DocumentHelper.GetDocuments("BBUS.Sector")
.Path("/Sector/", PathTypeEnum.Children)
.OnSite("Balfour-dev.allata.com");
if (!DataHelper.DataSourceIsEmpty(clientSectors))
{
int count = 1;
foreach (var clientSector in clientSectors)
{
var ClientSectorID = clientSector.GetValue("SectorID").ToString();
this.drpSector.Items.Insert(count++, new ListItem(clientSector.DocumentName, ClientSectorID));
}
}
}
/// <summary>
/// Generates a WHERE condition and ORDER BY clause based on the current filtering selection.
/// </summary>
private void SetFilter()
{
string where = null;
// Generates a WHERE condition based on the selected product department
if (this.drpSector.SelectedIndex > 0 && this.drpSector.SelectedValue != null)
{
//where = string.Format("clientSector = {0}", this.drpClientSector.SelectedValue);
where = string.Format(
"sector LIKE '%|{0}|%' " +
"OR sector LIKE '{0}|%' " +
"OR sector LIKE '%|{0}' " +
"OR sector = '{0}'", this.drpSector.SelectedValue);
}
if (where != null)
{
// Sets the Where condition
this.WhereCondition = where;
}
// Raises the filter changed event
this.RaiseOnFilterChanged();
}
/// <summary>
/// Init event handler.
/// </summary>
protected override void OnInit(EventArgs e)
{
// Creates the child controls
SetupControl();
base.OnInit(e);
}
/// <summary>
/// PreRender event handler
/// </summary>
protected override void OnPreRender(EventArgs e)
{
var ClientSectorID = HttpContext.Current.Request.QueryString.Get("SectorID");
// Checks if the current request is a postback
if (RequestHelper.IsPostBack())
{
// Applies the filter to the displayed data
SetFilter();
}
else if (!string.IsNullOrEmpty(ClientSectorID))
{
this.drpSector.SelectedIndex = this.drpSector.Items.IndexOf(this.drpSector.Items.FindByValue(ClientSectorID));
SetFilter();
}
base.OnPreRender(e);
}
protected void btnFilter_Click(object sender, EventArgs e)
{
// Remove Query Strings
string url = Request.RawUrl.Split(new[] { '?' })[0];
// Add ClientSectorID Query String
string updatedQueryString = "?" + "SectorID=" + this.drpSector.SelectedValue;
Response.Redirect(url + updatedQueryString);
}
}
FILTER#2
using CMS.DocumentEngine;
using CMS.Helpers;
using System;
using System.Web;
using System.Web.UI.WebControls;
using CMS.DocumentEngine.Web.UI;
public partial class CMSGlobalFiles_SectorFilterControl : CMSAbstractDataFilterControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
/// <summary>
/// Sets up the inner child controls.
/// </summary>
private void SetupControl()
{
// Hides the filter if StopProcessing is enabled
if (this.StopProcessing)
{
this.Visible = false;
}
// Initializes only if the current request is NOT a postback
else if (!RequestHelper.IsPostBack())
{
// Loads product departments as filtering options
InitializeClientSectors();
}
}
/// <summary>
/// Loads all existing product departments as filtering options into the department drop-down list.
/// </summary>
private void InitializeClientSectors()
{
// Adds the default '(all)' option
this.drpSector.Items.Insert(0, new ListItem("(all)", "##ALL##"));
var clientSectors = DocumentHelper.GetDocuments("BBUS.Sector")
.Path("/Sector/", PathTypeEnum.Children)
.OnSite("Balfour-dev.allata.com");
if (!DataHelper.DataSourceIsEmpty(clientSectors))
{
int count = 1;
foreach (var clientSector in clientSectors)
{
var ClientSectorID = clientSector.GetValue("SectorID").ToString();
this.drpSector.Items.Insert(count++, new ListItem(clientSector.DocumentName, ClientSectorID));
}
}
}
/// <summary>
/// Generates a WHERE condition and ORDER BY clause based on the current filtering selection.
/// </summary>
private void SetFilter()
{
string where = null;
// Generates a WHERE condition based on the selected product department
if (this.drpSector.SelectedIndex > 0 && this.drpSector.SelectedValue != null)
{
//where = string.Format("clientSector = {0}", this.drpClientSector.SelectedValue);
where = string.Format(
"sector LIKE '%|{0}|%' " +
"OR sector LIKE '{0}|%' " +
"OR sector LIKE '%|{0}' " +
"OR sector = '{0}'", this.drpSector.SelectedValue);
}
if (where != null)
{
// Sets the Where condition
this.WhereCondition = where;
}
// Raises the filter changed event
this.RaiseOnFilterChanged();
}
/// <summary>
/// Init event handler.
/// </summary>
protected override void OnInit(EventArgs e)
{
// Creates the child controls
SetupControl();
base.OnInit(e);
}
/// <summary>
/// PreRender event handler
/// </summary>
protected override void OnPreRender(EventArgs e)
{
var ClientSectorID = HttpContext.Current.Request.QueryString.Get("SectorID");
// Checks if the current request is a postback
if (RequestHelper.IsPostBack())
{
// Applies the filter to the displayed data
SetFilter();
}
else if (!string.IsNullOrEmpty(ClientSectorID))
{
this.drpSector.SelectedIndex = this.drpSector.Items.IndexOf(this.drpSector.Items.FindByValue(ClientSectorID));
SetFilter();
}
base.OnPreRender(e);
}
protected void btnFilter_Click(object sender, EventArgs e)
{
// Remove Query Strings
string url = Request.RawUrl.Split(new[] { '?' })[0];
// Add ClientSectorID Query String
string updatedQueryString = "?" + "SectorID=" + this.drpSector.SelectedValue;
Response.Redirect(url + updatedQueryString);
}
}
Sadly this is a little bit of a limitation, you can only have 1 filter per Repeater/Data Source (except Smart Search it seems, which can handle multiple).
You will most likely need to combine both of your filters into 1 filter, and combine the logics into one where condition.
https://docs.kentico.com/k10/developing-websites/loading-and-displaying-data-on-websites/filtering-and-paging-data
Would be great to allow multiple filters though!

Find child table with foreign key in t4 template

I am pretty new to using T4 templates to generate POCOs and I have a working series of templates now that generate my DTO, DAL and Manager objects as well as the associated CRUD SQL files, history trigger and table.
It works great for individual tables, but I have been unable to figure out how to include the child tables in the DTO and DAL. Essentially, I want to create Read Only properties in my DTO and the Get Data methods for them in the DAL object for the related child DTO of the foreign key.
Here is my DTOGenerator.tt which is executed from another .tt taking in the Table, Namespace, Ddestination folder and a string to represent the user executing the template.
Thanks in advance for the help.
<## import namespace="System" #><##>
<## import namespace="System.IO" #><##>
<## import namespace="System.Text" #><##>
<## assembly name="System.Data" #>
<##assembly name="Microsoft.SqlServer.Smo"#><##>
<##assembly name="Microsoft.SqlServer.ConnectionInfo"#><##>
<##assembly name="Microsoft.SqlServer.Management.Sdk.Sfc"#><##>
<##import namespace="Microsoft.SqlServer.Management.Smo"#><##>
<#+public void GenerateDTO(Table target_table, string classNamespace, string destinationFolder, string executed_by)
{
// StringBuilders for Fields, Properties and Parameters.
StringBuilder stbMembers = new StringBuilder();
StringBuilder stbUserSaveEditMembers = new StringBuilder();
StringBuilder stbAccessors = new StringBuilder();
StringBuilder stbUserSaveEditAccessors = new StringBuilder();
StringBuilder stbConstructorParameters = new StringBuilder();
StringBuilder stbClassProperties = new StringBuilder();
StringBuilder stbUserSaveEditClassProperties = new StringBuilder();
// Keep count so we don't whitespace the last property/column
int columnCount = target_table.Columns.Count;
int i = 0;
string col_identity = string.Empty;
// Iterate all columns
foreach (Column col in target_table.Columns)
{
i++;
string propertyType = GetNetDataType(col.DataType.Name);
// If we can't map it, skip it
if (string.IsNullOrWhiteSpace(propertyType))
{
// Skip
continue;
}
// Make propertytype nullable for nullable columns
if (col.Nullable && propertyType != "string" && propertyType != "byte[]")
{
propertyType += "?";
}
// Build Fields & Class Properties
string col_name = SnakeCaseToCamelCase(col.Name);
// Capture Identity column name
if(col.Identity)
{
col_identity = col_name;
}
else if (col.IsForeignKey)
{
//Create Read Only property to return child DTO via the child table DAL
}
// TABLE
// Members
stbMembers.Append("private ").Append(propertyType).Append(" _").Append(col.Name).Append(";\r\n\t\t");
// Accessors
if(i>1){stbAccessors.Append("\t\t");}
stbAccessors.Append("[DataMember()]\r\n");
stbAccessors.Append("\t\tpublic ").Append(propertyType).Append(" ").Append(col_name).Append("\r\n");
stbAccessors.Append("\t\t{\r\n");
stbAccessors.Append("\t\t\tget { return").Append(" _").Append(col.Name).Append("; }\r\n");
stbAccessors.Append("\t\t\tset\r\n");
stbAccessors.Append("\t\t\t{\r\n");
stbAccessors.Append("\t\t\t\tif (").Append("_").Append(col.Name).Append(" == value){ return; }\r\n");
stbAccessors.Append("\t\t\t\t_").Append(col.Name).Append(" = value;\r\n");
stbAccessors.Append("\t\t\t\tChanged = true;\r\n");
stbAccessors.Append("\t\t\t}\r\n");
stbAccessors.Append("\t\t}\r\n\r\n");
// Class Properties
stbClassProperties.Append("_").Append(col.Name).Append(" =").Append(" p").Append(col_name).Append(";");
if(i < columnCount)
{
stbClassProperties.Append("\r\n\t\t\t");
}
// Class Parameters
stbConstructorParameters.Append(propertyType).Append(" p").Append(col_name);
if(i < columnCount)
{
stbConstructorParameters.Append(",\r\n\t\t\t\t\t\t\t\t");
}
}
#>
using System;
using System.Runtime.Serialization;
namespace <#=classNamespace #>
{
#region " History "
// <remarks>
//-------------------------------------------------------------------------------------------------------------------------------
// Purpose : Data Transfer Object for <#=target_table.Name #>
// T4 Template : DTOGenerator.tt
// Date Created : <#=DateTime.Now.ToString(#"MM\/dd\/yyyy")#>
// Created By : <#=executed_by#>
// Notice : Copyright © <#=DateTime.Now.ToString(#"yyyy")#> GooeyPC, All rights reserved.
//-------------------------------------------------------------------------------------------------------------------------------
// History:
//
//-------------------------------------------------------------------------------------------------------------------------------
// </remarks>
#endregion
[DataContract()]
public class <#=target_table.Name #>DTO
{
#region " _MEMBERS "
// TABLE FIELDS
<#=stbMembers.ToString() #>
#endregion
#region " ACCESSORS "
/// <summary>
/// Returns TRUE if data on this object record has changed
/// </summary>
[DataMember()]
public bool Changed { get; set; }
<#=stbAccessors.ToString() #>
#endregion
#region " PROPERTIES - READ ONLY "
/// <summary>
/// Returns TRUE if primary key id != 0 (i.e. valid primary key id exists.)
/// </summary>
public virtual bool RecordExists
{
get { return (<#=col_identity #> != 0); }
}
#endregion
#region " CONSTRUCTORS "
/// <summary>
/// Description of <#=target_table.Name #>
/// </summary>
public <#=target_table.Name #>DTO()
{
// Default constructor is needed
}
/// <summary>
/// Description of <#=target_table.Name #>
/// </summary>
public <#=target_table.Name #>DTO(<#=stbConstructorParameters.ToString() #>)
{
// Set Class Properties
<#=stbClassProperties.ToString() #>
}
#endregion
}
}
<#+
// Write new class to its own file
SaveOutput(destinationFolder, target_table.Name + "DTO.cs");}
#>

Optimize algorithm: Update a collection by other collection with order C#

Problem: I have a collection need to be update by other collection. The length of them are not equal and each item are not same structure. But both of them are have identity field to detect same.
I write a generic algorithm to use everywhere but the complexity are O(m*n*3). The main problem is: Are there any better optimization algorithm? The input must should be generic as IList<T> and delegate to reusable.
Current approach:
Source: Data collection is used to display on GUI.
Target: Data collection which is got from remote server.
Remove old items from Source which doesn't existed from Target via delegate comparer.
Add new items from Target to Source.
Reorder Source by Target.
Scenario Usage: If you have an application display about 1~2k row on list view. And you have interval timer update this list item (about 1 seconds). The main point here application should be smooth, doesn't flick , keep state of object: Selected, change data... etc.
Here is source code:
using System;
using System.Collections.Generic;
using System.Linq;
namespace UpdateCollection
{
/// <summary>
/// Person DTO (from other 3rd assembly) to get data from their remote server.
/// </summary>
public class PersonDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
/// <summary>
/// Person model is used to display on GUI (our application).
/// </summary>
public class PersonModel
{
public string Identity { get; set; }
public string DisplayName { get; set; }
public int? Age { get; set; }
public bool Selected { get; set; }
public override string ToString()
{
return string.Format("{0} {1} {2} {3}", Identity, DisplayName, Age, Selected);
}
}
static class Program
{
/// <summary>
/// Encapsulates a method that has two parameters and does not return a value.
/// </summary>
/// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.This type parameter is contravariant. That is, you can use either the type you specified or any type that is less derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics.</typeparam>
/// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
public delegate void RefAction<T1, in T2>(ref T1 arg1, T2 arg2);
/// TODO: The complexity of algorithm is: O(m*n*3) Need to be optimization. For example: m*log2(n)+ m + n
/// <summary>
/// Update source by target.
/// </summary>
/// <typeparam name="TSourceType"></typeparam>
/// <typeparam name="TTargetType"></typeparam>
/// <param name="source">Source collection.</param>
/// <param name="target">Target collection.</param>
/// <param name="compare">Comparing method between source and target.</param>
/// <param name="convert">Convert method</param>
/// <param name="update">Update method</param>
/// <param name="remove">Remove method</param>
public static void UpdateBy<TSourceType, TTargetType>(
this IList<TSourceType> source,
IList<TTargetType> target,
Func<TSourceType, TTargetType, bool> compare,
Func<TTargetType, TSourceType> convert,
RefAction<TSourceType, TTargetType> update,
Func<TSourceType, bool> remove = null)
{
if (source == null || target == null)
return;
if (convert == null)
throw new AggregateException("convert");
if (compare == null)
throw new ArgumentNullException("compare");
// Remove item
for (var index = 0; index < source.Count; ++index)
{
if (target.Any(c => compare(source[index], c))) continue;
var temp = source[index];
if (remove == null)
source.RemoveAt(index--);
else if (remove(temp))
source.RemoveAt(index--);
}
// Add new item
foreach (var t in target.Where(t => !source.Any(c => compare(c, t))))
{
source.Add(convert(t));
}
// Sort by target
for (var index = 0; index < target.Count; ++index)
{
for (var pos = 0; pos < source.Count; ++pos)
{
if (!compare(source[pos], target[index])) continue;
var temp = source[pos];
if (update != null)
update(ref temp, target[index]);
source[pos] = temp;
if (pos == index) continue;
temp = source[pos];
source[pos] = source[index];
source[index] = temp;
}
}
}
public static IList<PersonModel> GetFromUserInterface()
{
return new List<PersonModel>
{
new PersonModel {Identity = "1", DisplayName = "a",},
new PersonModel {Identity = "2", DisplayName = "b", Selected = true},
new PersonModel {Identity = "3", DisplayName = "c", Selected = true},
new PersonModel {Identity = "4", DisplayName = "D"}
};
}
public static IList<PersonDto> GetFromRemoteServer()
{
return new List<PersonDto>
{
new PersonDto {Id = 6, Name = "F", Birthday = DateTime.Parse("1984-01-02")},
new PersonDto {Id = 4, Name = "D", Birthday = DateTime.Parse("1986-01-12")},
new PersonDto {Id = 3, Name = "C", Birthday = DateTime.Parse("1982-03-05")},
new PersonDto {Id = 5, Name = "E", Birthday = DateTime.Parse("1984-05-22")},
new PersonDto {Id = 1, Name = "A", Birthday = DateTime.Parse("1986-02-14")}
};
}
public static bool Compare(PersonModel source, PersonDto target)
{
return source.Identity == target.Id.ToString();
}
public static PersonModel Convert(PersonDto target)
{
return new PersonModel
{
Identity = target.Id.ToString(),
Age = target.Birthday.Year,
DisplayName = target.Name,
};
}
public static void Update(ref PersonModel source, PersonDto target)
{
source.Age = target.Birthday.Year;
source.DisplayName = target.Name;
}
static void Main(string[] args)
{
var source = GetFromUserInterface();
var target = GetFromRemoteServer();
Console.WriteLine("==> Before Update:\r\n");
foreach (var item in source)
Console.Write("{0}\r\n\r\n", item);
// TODO: How to optimize UpdateBy algorithm to better?
source.UpdateBy(target, Compare, Convert, Update);
Console.WriteLine("==> After Update:\r\n");
foreach (var item in source)
Console.Write("{0}\r\n\r\n", item);
Console.ReadLine();
}
}
}
You are storing your data in a list; switching to a hash table structure would give you (roughly) constant time access to the members by key. If the ordering of the data is important, many languages have some kind of ordered hash construct you can use.
Finally, I found the best algorithm for this issue. Base on QuickSort algorithm. The procedure are:
Make lookup index base on Target collection.
Sort Source base on Target index with QuickSort algorithm. Keep track index of item from Source which doesn't exist in Target
Remove item from Source.
Add/update item from Target to Source
The complexity or algorithm is: O(m * 2 + n + n * log2(n))
Here is source code:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace UpdateCollection
{
static class CollectionUpdater
{
/// <summary>
/// Encapsulates a method that has two parameters and does not return a value.
/// </summary>
/// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.This type parameter is contravariant. That is, you can use either the type you specified or any type that is less derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics.</typeparam>
/// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
public delegate void RefAction<T1, in T2>(ref T1 arg1, T2 arg2);
/// <summary>
/// Update source collection by target collection.
/// </summary>
/// <remarks>The complexity of algorithm is: O(m * 2 + n + n * log2(n))</remarks>
/// <typeparam name="TSourceType">Source data type.</typeparam>
/// <typeparam name="TTargetType">Target data type.</typeparam>
/// <typeparam name="TIdentity"></typeparam>
/// <param name="source">The source collection.</param>
/// <param name="target">The target collection.</param>
/// <param name="targetIdentity">Convert target to identity.</param>
/// <param name="sourceIdentity">Convert source to identity.</param>
/// <param name="targetToSourceConverter">Convert target to source.</param>
/// <param name="sourceUpdater">Update source from target.</param>
/// <param name="sourceRemover">Remove source item.</param>
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
[SuppressMessage("ReSharper", "AccessToModifiedClosure")]
public static void UpdateBy<TSourceType, TTargetType, TIdentity>(
this IList<TSourceType> source,
IEnumerable<TTargetType> target,
Func<TTargetType, TIdentity> targetIdentity,
Func<TSourceType, TIdentity> sourceIdentity,
Func<TTargetType, TSourceType> targetToSourceConverter,
RefAction<TSourceType, TTargetType> sourceUpdater,
Func<TSourceType, bool> sourceRemover = null) where TIdentity : IComparable<TIdentity>
{
// Step 1: Make index of target. O(m)
// Index, target, order.
var targetOrderLookup = new Dictionary<TIdentity, Tuple<TTargetType, int>>();
do
{
var counterIndex = 0;
foreach (var item in target)
{
targetOrderLookup.Add(targetIdentity(item), Tuple.Create(item, counterIndex));
++counterIndex;
}
} while (false);
var skipRemoveIdentity = new HashSet<TIdentity>();
if (source.Count != 0)
{
// Step 2: Re-implement quick-sort.
Action<IList<TSourceType>, int, int, Comparison<Tuple<TSourceType, int>>> quickSort = null;
var removeIdentityLockup = new Dictionary<TIdentity, int>();
quickSort = (elements, left, right, comparer) =>
{
var i = left;
var j = right;
var pivotIndex = (left + right) / 2;
var pivot = elements[pivotIndex];
while (i <= j)
{
while (comparer(Tuple.Create(elements[i], i), Tuple.Create(pivot, pivotIndex)) < 0)
i++;
while (comparer(Tuple.Create(elements[j], j), Tuple.Create(pivot, pivotIndex)) > 0)
j--;
if (i <= j)
{
var leftId = sourceIdentity(elements[i]);
var rightId = sourceIdentity(elements[j]);
var leftValue = targetOrderLookup.ContainsKey(leftId);
var rightValue = targetOrderLookup.ContainsKey(rightId);
if (!leftValue)
removeIdentityLockup[leftId] = j;
if (!rightValue)
removeIdentityLockup[rightId] = i;
// Swap
var tmp = elements[i];
elements[i] = elements[j];
elements[j] = tmp;
i++;
j--;
}
}
// Recursive calls
if (left < j)
quickSort(elements, left, j, comparer);
if (i < right)
quickSort(elements, i, right, comparer);
};
// Step 2: Sort source. O(log2(n))
quickSort(source, 0, source.Count - 1, (c, d) =>
{
var leftId = sourceIdentity(c.Item1);
var rightId = sourceIdentity(d.Item1);
Tuple<TTargetType, int> leftValue;
if (!targetOrderLookup.TryGetValue(leftId, out leftValue))
removeIdentityLockup[leftId] = c.Item2;
Tuple<TTargetType, int> rightValue;
if (!targetOrderLookup.TryGetValue(rightId, out rightValue))
removeIdentityLockup[rightId] = d.Item2;
if (leftValue == null && rightValue == null)
return 0;
if (leftValue == null)
return -1;
if (rightValue == null)
return 1;
return leftValue.Item2.CompareTo(rightValue.Item2);
});
// Remove item
foreach (KeyValuePair<TIdentity, int> item in removeIdentityLockup.OrderByDescending(v => v.Value))
{
if (sourceRemover == null)
{
if (source.IsReadOnly)
skipRemoveIdentity.Add(item.Key);
else
{
source.RemoveAt(item.Value);
}
}
else
{
if (sourceRemover(source[item.Value]))
{
if (source.IsReadOnly)
skipRemoveIdentity.Add(item.Key);
else
source.RemoveAt(item.Value);
}
else // Keep can remove, avoid update
skipRemoveIdentity.Add(item.Key);
}
}
}
// Add new item
var sourceIndex = 0;
foreach (var item in target)
{
var targetItem = item;
if (sourceIndex < source.Count)
{
var sourceItem = source[sourceIndex];
var sourceId = sourceIdentity(sourceItem);
var targetId = targetIdentity(targetItem);
while (skipRemoveIdentity.Contains(sourceId) && sourceIndex < source.Count)
{
++sourceIndex;
sourceItem = source[sourceIndex];
sourceId = sourceIdentity(sourceItem);
}
if (sourceIndex < source.Count)
{
if (sourceId.CompareTo(targetId) == 0) // Update source
{
sourceUpdater(ref sourceItem, targetItem);
source[sourceIndex] = sourceItem;
++sourceIndex;
}
else // Insert new
{
if (source.IsReadOnly) continue;
source.Insert(sourceIndex, targetToSourceConverter(targetItem));
++sourceIndex;
}
}
else
{
if (source.IsReadOnly) continue;
source.Add(targetToSourceConverter(targetItem));
}
}
else
{
if (source.IsReadOnly) continue;
source.Add(targetToSourceConverter(targetItem));
++sourceIndex;
}
}
}
/// <summary>
/// Person DTO (from other 3rd assembly) to get data from their remote server.
/// </summary>
public class PersonDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
/// <summary>
/// Person model is used to display on GUI (our application).
/// </summary>
public class PersonModel
{
public string Identity { get; set; }
public string DisplayName { get; set; }
public int? Age { get; set; }
public bool Selected { get; set; }
public override string ToString()
{
return string.Format("\"{0}\" {1} {2} {3}", Identity, DisplayName, Age, Selected ? "selected" : string.Empty);
}
}
/// <summary>
/// Get from user interface, it work for both fix & non-fixed collection.
/// </summary>
public static IList<PersonModel> GetFromUserInterface()
{
//return new List<PersonModel> // For non-fixed collection. Add/remove/update support
return new[] // For fix collection. Just update support
{
new PersonModel {Identity = "4", DisplayName = "D"},
new PersonModel {Identity = "13", DisplayName = "", Selected = true}, // Must remove.
new PersonModel {Identity = "1", DisplayName = "a",},
new PersonModel {Identity = "10", DisplayName = "", Selected = true}, // Must remove.
new PersonModel {Identity = "3", DisplayName = "c", Selected = true},
new PersonModel {Identity = "9", DisplayName = "", Selected = true}, // Must remove.
new PersonModel {Identity = "2", DisplayName = "", Selected = true} // Must remove.
};
}
/// <summary>
/// Get from remote service.
/// </summary>
public static IEnumerable<PersonDto> GetFromRemoteServer()
{
return new List<PersonDto>
{
new PersonDto {Id = 6, Name = "F", Birthday = DateTime.Parse("1984-01-02")}, // Must add
new PersonDto {Id = 4, Name = "D", Birthday = DateTime.Parse("1986-01-12")},
new PersonDto {Id = 3, Name = "C", Birthday = DateTime.Parse("1982-03-05")},
new PersonDto {Id = 5, Name = "E", Birthday = DateTime.Parse("1984-05-22")}, // Must Add
new PersonDto {Id = 1, Name = "A", Birthday = DateTime.Parse("1986-02-14")}
};
}
/// <summary>
/// Convert target to source.
/// </summary>
public static PersonModel Convert(PersonDto target)
{
return new PersonModel
{
Identity = target.Id.ToString(),
Age = DateTime.Now.Year - target.Birthday.Year,
DisplayName = target.Name,
};
}
/// <summary>
/// Update target from source.
/// </summary>
public static void Update(ref PersonModel source, PersonDto target)
{
source.Age = DateTime.Now.Year - target.Birthday.Year;
source.DisplayName = target.Name;
}
/// <summary>
/// Get identity.
/// </summary>
public static string Identity(PersonModel arg)
{
return arg.Identity;
}
/// <summary>
/// Get identity.
/// </summary>
public static string Identity(PersonDto arg)
{
return arg.Id.ToString();
}
static void Main()
{
var source = GetFromUserInterface();
var target = GetFromRemoteServer();
Console.WriteLine("==> Before Update:\r\n");
foreach (var item in source)
Console.Write("{0}\r\n\r\n", item);
// TODO: Update source collection by target.
source.UpdateBy(target, Identity, Identity, Convert, Update);
Console.WriteLine("==> After Update:\r\n");
foreach (var item in source)
Console.Write("{0}\r\n\r\n", item);
Console.ReadLine();
}
}
}

How can I make CRMSvcUtil.exe generate unduplicated, error-free early-bound option sets?

I use Erik Pool's implementation of ICodeWriterFilterService and Manny Grewal's GenerateOption function as a model to filter out unwanted entities in the file that CRMSvcUtil generates. While Erik recommends returning true for the GenerateOptionSet method to generate enums for option sets, doing so duplicates any of the global option sets that are used by any particular entity (as mentioned in one of the comments on that post).
To address this, I check to see if the option set has been already generated, and if so, I return the default option (presumably false for most cases) as in the below.
//list of generated option sets, instantiated in the constructor
private List<string> GeneratedOptionSets;
public bool GenerateOptionSet
(OptionSetMetadataBase optionSetMetadata, IServiceProvider services)
{
if (!GeneratedOptionSets.Contains(optionSetMetadata.Name))
{
GeneratedOptionSets.Add(optionSetMetadata.Name);
return true;
}
return _defaultService.GenerateOptionSet(optionSetMetadata, services);
}
But when incorporating the generated file in my CRM projects, the compilation error
Cannot convert type 'Microsoft.Xrm.Sdk.OptionSetValue' to 'int'
is always thrown by every line of code that looks like
this.SetAttributeValue
("address1_shippingmethodcode", new Microsoft.Xrm.Sdk.OptionSetValue(((int)(value))));
.
As a workaround, I use a separate project where I filter the entities I need, run CRMSvcUtil with the arguments Erik suggests, replace the troublesome part of the code (int)(value) (where value is an OptionSetValue) with value.Value after the file is generated, and then resave the file, and all issues go away.
My question is this: do I need to do something differently that will fix this compilation error with the default CRMSvcUtil generated file without doing something so hackish as altering that generated file?
You can use the ICustomizeCodeDomService interface to rewrite the SetAttributeValue method for the optionSets. Snippet below:
namespace The.NameSpace
{
using System;
using System.CodeDom;
using System.Diagnostics;
using System.Linq;
using Microsoft.Crm.Services.Utility;
using Microsoft.Xrm.Sdk.Metadata;
/// <summary>
/// The customize code dom service.
/// </summary>
public sealed class CustomizeCodeDomService : ICustomizeCodeDomService
{
#region Constants and Fields
/// <summary>
/// The metadata.
/// </summary>
private IOrganizationMetadata metadata;
#endregion
#region Properties
#endregion
#region Public Methods
/// <summary>
/// The customize code dom.
/// </summary>
/// <param name="codeCompileUnit">
/// The code compile unit.
/// </param>
/// <param name="services">
/// The services.
/// </param>
public void CustomizeCodeDom(CodeCompileUnit codeCompileUnit, IServiceProvider services)
{
// Locate the namespace to use
CodeNamespace codeNamespace = codeCompileUnit.Namespaces[0];
var metadataProviderService = (IMetadataProviderService)services.GetService(typeof(IMetadataProviderService));
var filterService = (ICodeWriterFilterService)services.GetService(typeof(ICodeWriterFilterService));
this.metadata = metadataProviderService.LoadMetadata();
foreach (EntityMetadata entityMetadata in this.metadata.Entities)
{
if (filterService.GenerateEntity(entityMetadata, services))
{
CodeTypeDeclaration entityClass =
codeNamespace.Types.Cast<CodeTypeDeclaration>().First(codeType => codeType.Name.ToUpper() == entityMetadata.SchemaName.ToUpper());
UpdateEnumSetter(entityClass, entityMetadata);
}
}
}
#endregion
#region Private Methods
private static void UpdateEnumSetter(
CodeTypeDeclaration entityClass, EntityMetadata entity)
{
foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf)))
{
//Match the respective field Name.
AttributeMetadata metadata1 = attributeMetadata;
foreach (
CodeTypeMember codeMembers in
entityClass.Members.Cast<CodeTypeMember>().Where(
codeMembers => codeMembers.Name == metadata1.SchemaName))
{
var codeProperty = (CodeMemberProperty)codeMembers;
if (codeProperty.HasSet)
{
if (attributeMetadata.AttributeType != null && attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist)
{
((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] =
new CodeSnippetStatement
{
Value =
String.Format(
"this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));",
attributeMetadata.LogicalName)
};
Debug.WriteLine(String.Format("{0}.{1}", entity.LogicalName, attributeMetadata.LogicalName));
}
}
}
}
}
#endregion
}
}
Some changes to the UpdateEnumSetter method:
private static void UpdateEnumSetter(CodeTypeDeclaration entityClass, EntityMetadata entity)
{
foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf)))
{
AttributeMetadata currentMetadata = attributeMetadata;
foreach (CodeTypeMember codeMembers in entityClass.Members.Cast<CodeTypeMember>().Where(codeMembers => codeMembers.Name == currentMetadata.SchemaName))
{
CodeMemberProperty codeProperty = (CodeMemberProperty)codeMembers;
if (codeProperty.HasSet)
{
if (attributeMetadata.AttributeType != null && (attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist || attributeMetadata.AttributeType.Value == AttributeTypeCode.Status))
{
if (codeProperty.SetStatements[1].GetType() == typeof(CodeConditionStatement))
{
((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] = new CodeSnippetStatement
{
Value = String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName)
};
}
else
{
codeProperty.SetStatements[1] = new CodeSnippetStatement(String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName));
}
}
}
}
}
}
I'm betting that Guarav's answer is the real way to go, but in the absence of documentation surrounding CRMSvcUtil, I'm forced to use my workaround. (I use a separate project where I filter the entities I need, run CRMSvcUtil with the arguments Erik suggests, replace the troublesome part of the code(int)(value) (where value is an OptionSetValue) with value.Value after the file is generated, and then resave the file.)
Not a perfect solution, but it's been working on the few samples I've worked with so far.
It turns out that this fault is to do with the code attempting to make optionsets that look like the code below when the types are available for use. Note the only difference is the correct type being chose for the return type and the cast.
It should be possible to update the codegen stuff to fix this bug, but it might be better to get microsoft to fix the damn thing properly, I would make a solution but I don't really have time to implement it right now because we have a mostly working solution even if we have to deal with the optionsetvalue class.
public enum entityname_optionsetname
{
Value = 200
}
[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("myprefix_fieldname")]
public entityname_optionsetname myprefix_FieldName
{
get
{
Microsoft.Xrm.Sdk.OptionSetValue optionSet = this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("myprefix_fieldname");
if ((optionSet != null))
{
return ((entityname_optionsetname)(System.Enum.ToObject(typeof(Microsoft.Xrm.Sdk.OptionSetValue), optionSet.Value)));
}
else
{
return null;
}
}
set
{
this.OnPropertyChanging("myprefix_FieldName");
if ((value == null))
{
this.SetAttributeValue("myprefix_fieldname", null);
}
else
{
this.SetAttributeValue("myprefix_fieldname", new Microsoft.Xrm.Sdk.OptionSetValue(((int)(value))));
}
this.OnPropertyChanged("myprefix_FieldName");
}
}
I finally am able to generate early bound class with a filtered set of entities and error free option set. I found the bulk of my answer through this thread, so thanks guys. The problem though is it's difficult to compile all of the various suggestions into something that actually... compiles. So I thought I'd post my final solution for the benefit of others, here's what worked for me.
I used Erik Pool's, Manny Grewal's, and Peter Majeed's solution for outputting only distinct enums with proper values, then combined that with Gaurav Dalal's solution (updated by JFK007 to fix the cast error) to re-write the SetAttributeValue that caused the (int)(value) error. And as an added bonus, I used the same solution for filtering distinct option sets to also filter for distinct option set values (which was an issue in my org).
The result is a class library containing CodeWriterFilter and CustomizeCodeDomService, the cmd batch file to run CrmSvcUtil.exe, and the filter.xml to filter the entities.
In your class library add references to CrmSvcUtil.exe, Microsoft.Xrm.Sdk, and System.Runtime.Serialization then compile the dll and copy it to the same the folder as your CrmSvcUtil.exe. Use the command I've included to reference your new assembly and build the early bound class file.
CodeWriterFilter:
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Crm.Services.Utility;
using Microsoft.Xrm.Sdk.Metadata;
using System.Text.RegularExpressions;
using Microsoft.Xrm.Sdk;
namespace SvcUtilFilter
{
/// <summary>
/// CodeWriterFilter for CrmSvcUtil that reads list of entities from an xml file to
/// determine whether or not the entity class should be generated.
/// </summary>
public class CodeWriterFilter : ICodeWriterFilterService
{
//list of entity names to generate classes for.
private HashSet<string> _validEntities = new HashSet<string>();
//reference to the default service.
private ICodeWriterFilterService _defaultService = null;
//list of generated option sets, instantiated in the constructor
private List<string> GeneratedOptionSets;
//list of generated options, instantiated in the constructor
private List<string> GeneratedOptions;
/// <summary>
/// constructor
/// </summary>
/// <param name="defaultService">default implementation</param>
public CodeWriterFilter(ICodeWriterFilterService defaultService)
{
this._defaultService = defaultService;
this.GeneratedOptionSets = new List<string>();
this.GeneratedOptions = new List<string>();
LoadFilterData();
}
/// <summary>
/// loads the entity filter data from the filter.xml file
/// </summary>
private void LoadFilterData()
{
XElement xml = XElement.Load("filter.xml");
XElement entitiesElement = xml.Element("entities");
foreach (XElement entityElement in entitiesElement.Elements("entity")) {
_validEntities.Add(entityElement.Value.ToLowerInvariant());
}
}
/// <summary>
/// /Use filter entity list to determine if the entity class should be generated.
/// </summary>
public bool GenerateEntity(EntityMetadata entityMetadata, IServiceProvider services)
{
return (_validEntities.Contains(entityMetadata.LogicalName.ToLowerInvariant()));
}
//All other methods just use default implementation:
public bool GenerateAttribute(AttributeMetadata attributeMetadata, IServiceProvider services)
{
return _defaultService.GenerateAttribute(attributeMetadata, services);
}
public bool GenerateOption(OptionMetadata optionMetadata, IServiceProvider services)
{
//return _defaultService.GenerateOption(optionMetadata, services);
string label = optionMetadata.Label.UserLocalizedLabel.Label;
//remove spaces and special characters
label = Regex.Replace(label, #"[^a-zA-Z0-9]", string.Empty);
if (label.Length > 0 && !char.IsLetter(label, 0)) {
label = "Number_" + label;
}
else if (label.Length == 0) {
label = "empty";
}
if (!GeneratedOptions.Exists(l=>l.Equals(label))) {
GeneratedOptions.Add(label);
optionMetadata.Label = new Label(label, 1033);
return _defaultService.GenerateOption(optionMetadata, services);
}
else { return false; }
}
public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceProvider services)
{
//return _defaultService.GenerateOptionSet(optionSetMetadata, services);
if (!GeneratedOptionSets.Contains(optionSetMetadata.Name)) {
GeneratedOptionSets.Add(optionSetMetadata.Name);
return true;
}
return _defaultService.GenerateOptionSet(optionSetMetadata, services);
}
public bool GenerateRelationship(RelationshipMetadataBase relationshipMetadata, EntityMetadata otherEntityMetadata, IServiceProvider services)
{
return _defaultService.GenerateRelationship(relationshipMetadata, otherEntityMetadata, services);
}
public bool GenerateServiceContext(IServiceProvider services)
{
return _defaultService.GenerateServiceContext(services);
}
}
}
CustomizeCodeDomService:
using System;
using System.CodeDom;
using System.Diagnostics;
using System.Linq;
using Microsoft.Crm.Services.Utility;
using Microsoft.Xrm.Sdk.Metadata;
namespace SvcUtilFilter
{
/// <summary>
/// The customize code dom service.
/// </summary>
public sealed class CustomizeCodeDomService : ICustomizeCodeDomService
{
#region Constants and Fields
/// <summary>
/// The metadata.
/// </summary>
private IOrganizationMetadata metadata;
#endregion
#region Properties
#endregion
#region Public Methods
/// <summary>
/// The customize code dom.
/// </summary>
/// <param name="codeCompileUnit">
/// The code compile unit.
/// </param>
/// <param name="services">
/// The services.
/// </param>
public void CustomizeCodeDom(CodeCompileUnit codeCompileUnit, IServiceProvider services)
{
// Locate the namespace to use
CodeNamespace codeNamespace = codeCompileUnit.Namespaces[0];
var metadataProviderService = (IMetadataProviderService)services.GetService(typeof(IMetadataProviderService));
var filterService = (ICodeWriterFilterService)services.GetService(typeof(ICodeWriterFilterService));
this.metadata = metadataProviderService.LoadMetadata();
foreach (EntityMetadata entityMetadata in this.metadata.Entities) {
if (filterService.GenerateEntity(entityMetadata, services)) {
CodeTypeDeclaration entityClass =
codeNamespace.Types.Cast<CodeTypeDeclaration>().First(codeType => codeType.Name.ToUpper() == entityMetadata.SchemaName.ToUpper());
UpdateEnumSetter(entityClass, entityMetadata);
}
}
}
#endregion
#region Private Methods
private static void UpdateEnumSetter(CodeTypeDeclaration entityClass, EntityMetadata entity)
{
foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf))) {
AttributeMetadata currentMetadata = attributeMetadata;
foreach (CodeTypeMember codeMembers in entityClass.Members.Cast<CodeTypeMember>().Where(codeMembers => codeMembers.Name == currentMetadata.SchemaName)) {
CodeMemberProperty codeProperty = (CodeMemberProperty)codeMembers;
if (codeProperty.HasSet) {
if (attributeMetadata.AttributeType != null && (attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist || attributeMetadata.AttributeType.Value == AttributeTypeCode.Status)) {
if (codeProperty.SetStatements[1].GetType() == typeof(CodeConditionStatement)) {
((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] = new CodeSnippetStatement {
Value = String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName)
};
}
else {
codeProperty.SetStatements[1] = new CodeSnippetStatement(String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName));
}
}
}
}
}
}
#endregion
}
}
CrmSvcUtil_run.cmd Command Batch File:
#echo off
set url=https://[organization].api.crm.dynamics.com/XRMServices/2011/Organization.svc
echo.
echo Generating CrmSvcUtil Proxy class in output folder
echo.
CrmSvcUtil.exe /metadataproviderservice:"MetadataProvider.IfdMetadataProviderService,
MetadataProvider"
/url:https://[organization].api.crm.dynamics.com/XRMServices/2011/Organization.svc /out:Xrm.cs
/namespace:Xrm /serviceContextName:XrmServiceContext /serviceContextPrefix:Xrm
/u:[username] /p:[password]
/codewriterfilter:SvcUtilFilter.CodeWriterFilter,SvcUtilFilter
/codecustomization:SvcUtilFilter.CustomizeCodeDomService,SvcUtilFilter
echo.
pause
filter.xml
<filter>
<entities>
<entity>systemuser</entity>
<entity>team</entity>
<entity>role</entity>
<entity>businessunit</entity>
<entity>account</entity>
<entity>product</entity>
<entity>transactioncurrency</entity>
</entities>
</filter>
It looks like there was a bug in the crmsrvcutil that has since been fixed. My code for OptionSet properties now looks like this:
[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("prioritycode")]
public Microsoft.Xrm.Sdk.OptionSetValue PriorityCode
{
get
{
return this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("prioritycode");
}
set
{
this.OnPropertyChanging("PriorityCode");
this.SetAttributeValue("prioritycode", value);
this.OnPropertyChanged("PriorityCode");
}
}
And I get no error setting the OptionSetValue...

Issue with Updating Changes in LINQ

I'm having an issue with updating the database. The app shows the updated value, but the database does not. No errors returned. My table has a PK. Using DotConnect for Oracle, but the LINQ syntax is the same.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataContext DB =
new DataContext("User Id=...Password=...;Server=...;");
DB.Log = Console.Out;
Console.WriteLine();
IEnumerable<Foodorder> fo = from f in DB.Foodorders
where f.Orderid == 10210
select f;
foreach (Foodorder food in fo)
{
Console.WriteLine(food.Orderid + " " + food.Externalref
+ "\r\n " + food.Orderremarks);
}
Console.Read();
//Try a new connection (eliminate caching)
DB.Dispose();
DataContext DB2 = new DataContext("User Id=...Password=...;Server=...;");
Foodorder fo2 = DFunc.GetFoodOrder(10198);
fo2.Orderremarks = "This should save now.";
fo2.Orderqty = 9999;
DB.SubmitChanges();
//Retrieves a single order: DFunc.GetFoodOrder(pk)
Console.WriteLine(DFunc.GetFoodOrder(10198).Orderremarks);
Console.Read();
}
}
}
The console reads the correct updated values, but the DB Does Not Update.
The data function, DFunc.GetFoodOrder(Oid) and data context functions are below. The are both in another assembly as the LINQ DAL:
#region Data Context
private static CommoDTContext cdtDataContext = new CommoDTContext(connectionSTringHere);
/// <summary>
/// This property gets the DevArt Oracle DotConnect data context,
/// providing LINQ to Oracle, and direct ORM capabilities.
/// </summary>
public static CommoDTContext DB
{
get
{
return cdtDataContext;
}
set
{
cdtDataContext = value;
}
}
#endregion Data Context
/// <summary>
/// Get food order by specifying the order ID.
/// </summary>
/// <param name="orderId"></param>
/// <returns></returns>
public static Foodorder GetFoodOrder(decimal orderId)
{ //left out validation/try-catch for brevity.
if (orderId == 0) return null;
var food =
from fo in DB.Foodorders
where fo.Orderid == orderId
select fo;
if (food.FirstOrDefault() == null)
return null;
else
return food.FirstOrDefault();
}
You don't show how DFunc.GetFoodOrder(10198) is implemented. However, it doesn't seem to have access to the data context. You also submit changes against DB after disposing. I think you meant to submit against DB2.
In order for LINQ to do updates, the record has to be "attached" to the data context. If you query via the DataContext, the record will be attached and LINQ will track changes. Try...
FoodOrder fo2 = DB2.Foodorders.Single(x => x.Orderid == 10198);
fo2.Orderremarks = ...
DB2.SubmitChanges();
Here's how I modified my program based on Rob's answer.
class Program
{
static void Main(string[] args)
{
string outFmt = "{0} {1} of {2}\r\n {3}";
CommoDT.Context.CommoDTContext DB =
new CommoDT.Context.CommoDTContext(PutOracleConnectionStringHere);
DB.Log = Console.Out;
Console.WriteLine();
Foodorder fo2 = DB.Foodorders.Single(x => x.Orderid == 10198);
fo2.Orderremarks = "These are the first comments.";
fo2.Orderqty = 1000;
DB.SubmitChanges();
Console.WriteLine(outFmt,
fo2.Orderid.ToString(), fo2.Orderqty.ToString(), fo2.Externalref, fo2.Orderremarks);
Console.Read();
Foodorder fo3 = DFunc.GetFoodOrder(ref DB, 10198);
fo3.Orderremarks += " And these are the second comments for the order.";
fo3.Orderqty = 2000;
DB.SubmitChanges();
Console.WriteLine(outFmt,
fo3.Orderid.ToString(), fo3.Orderqty.ToString(), fo3.Externalref, fo3.Orderremarks);
Console.Read();
DB.Dispose();
}
}
/// <summary>
/// Get food order by specifying the order ID.
/// </summary>
/// <param name="orderId"></param>
/// <returns>Food</returns>
public static Foodorder GetFoodOrder(ref CommoDT.Context.CommoDTContext DB, decimal orderId)
{
if (orderId == 0) return null;
var food =
from fo in DB.Foodorders
where fo.Orderid == orderId
select fo;
if (food.FirstOrDefault() == null)
return null;
else
return food.FirstOrDefault();
}

Resources