Related
I am using Cosmosdb with .net CORE 2.2 and the Cosmosdb SQL SDK. By default cosmosdb will assign every documents 'Id' property as a Guid. But the Id alone is not enough to directly read the document, you must also know its partition. So I created a class named CosmosGuid that contains a Id property(Guid) and a PartitionKey property(string). The ToString() is overridden to call .ToString("N") on the Guid to remove the dashes and it appends the PartitionKey to the end. The problem im having is when I use the CosmosGuid in Linq, the generated SQL will contain a json version of CosmosGuid, I really need it to just be a string. I can call .ToString() and that will produce the desired result, but im afraid another developer will use my class in a Linq expression and it fail for no known reason. When I save the CosmosGuid I created a custom newtonsoft converter to call ToString() when it saves and to call .Parse(string) when it reads. When you compare two Guids in Linq the generated SQL comes out to a string, but when I compare two CosmosGuid it creates a json string of my class. How can I make my class act like a Guid?
I have already attempted to implement all the same Interfaces as the Guid. The closes I have came was implementing 'IEnumerable' and in the GetComparer() I returned:
new string[] { this.ToString() }.GetEnumerator();
The code produced was perfect, but it kept putting my string surrounded with brackets[].
here is an example:
SELECT VALUE root FROM root WHERE (root['id'] = ['9a9dbbd5f78143c48b16f780c7ceaa4011'])
This is the CosmosGuid class, I figure id post the full class since its not very large and it may be useful to some.
public class CosmosGuid
{
// This is the unique Id of the entity
public Guid Guid { get; set; }
// This is the partition key where the entity lives
public string PartitionKey { get; set; }
// This is the unique Id of the Document that contains the entity
public Guid? ParentGuid { get; set; }
// This is the PartitionKey of the Document that contains the entity
public string ParentPartitionKey { get; set; }
/// <summary>
/// Parses a CosmosGuid string into a new CosmosGuid
/// </summary>
/// <param name="cosmosGuid"></param>
public CosmosGuid(string cosmosGuid)
{
ParentGuid = null;
ParentPartitionKey = null;
try
{
var parsed = cosmosGuid.Split('-');
// We can accuratly parse the guid from the string by always grabing the first 32 characters.
// The characters after the first 32 are the PartitionKey.
// https://stackoverflow.com/a/4458925
// Guid.NewGuid().ToString("N") => 32 characters (digits only, no dashes)
Guid = Guid.Parse(parsed[0].Substring(0, 32));
PartitionKey = parsed[0].Substring(32, parsed[0].Length - 32);
if (parsed.Length == 2)
{
ParentGuid = Guid.Parse(parsed[1].Substring(0, 32));
ParentPartitionKey = parsed[1].Substring(32, parsed[1].Length - 32);
}
}
catch (Exception ex)
{
throw new Exception("The Id of the document is not a properly formatted CosmosGuid.", ex);
}
}
/// <summary>
/// Generates a new Guid and appends the PartitionKey. This is used for Documents.
/// </summary>
/// <param name="partitionKey"></param>
/// <returns></returns>
public static CosmosGuid NewCosmosGuid(string partitionKey)
{
return new CosmosGuid($"{ShortenGuid(Guid.NewGuid())}{partitionKey}");
}
/// <summary>
/// Generates a new Guid and appends the PartitionKey as well as the Parent Guid and Parent PartitionKey. This is used for Subdocuments.
/// </summary>
/// <param name="parent"></param>
/// <param name="partitionKey"></param>
/// <returns></returns>
public static CosmosGuid NewCosmosGuid(CosmosGuid parent, string partitionKey)
{
return new CosmosGuid($"{ShortenGuid(Guid.NewGuid())}{partitionKey}-{ShortenGuid(parent.Guid)}{parent.PartitionKey}");
}
/// <summary>
/// Returns only the Parent CosmosGuid. If there is no parent the value returned will be null.
/// </summary>
public CosmosGuid Parent
{
get
{
if (ParentGuid != null && ParentPartitionKey != null)
return new CosmosGuid($"{ShortenGuid((Guid)ParentGuid)}{ParentPartitionKey}");
else
return null;
}
}
/// <summary>
/// Parses a CosmosGuid string into a new CosmosGuid.
/// </summary>
/// <param name="cosmosGuid"></param>
/// <returns></returns>
public static CosmosGuid Parse(string cosmosGuid)
{
return new CosmosGuid(cosmosGuid);
}
/// <summary>
/// Generates a CosmosGuid formatted string.
/// </summary>
/// <returns></returns>
public override string ToString()
{
if (ParentGuid == null)
return $"{ShortenGuid(Guid)}{PartitionKey}";
else
return $"{ShortenGuid(Guid)}{PartitionKey}-{ShortenGuid((Guid)ParentGuid)}{ParentPartitionKey}";
}
/// <summary>
/// Removes the dashes from a Guid
/// </summary>
/// <param name="guid"></param>
/// <returns></returns>
private static string ShortenGuid(Guid guid)
{
// Just remove dashes from the guid to shorten it some.
// More can be done here if you wish but make sure the guid uniqueness isnt compromised.
return guid.ToString("N");
}
public static bool operator ==(CosmosGuid obj1, CosmosGuid obj2)
{
return obj1?.ToString() == obj2?.ToString();
}
public static bool operator !=(CosmosGuid obj1, CosmosGuid obj2)
{
return obj1?.ToString() != obj2?.ToString();
}
}
If a developer where to use the CosmosGuid like so it would fail to work, because the SQL generated is a Json version of the class. (the Id is also a CosmosGuid):
var cosmosGuid = CosmosGuid.Parse("6bec688a0aca477c8175c09162b7a9b411");
var result = await Client.CreateDocumentQuery<MyClass>(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), options)
.Where(x => x.Id == cosmosGuid)
.AsDocumentQuery();
This is the sql generated
SELECT VALUE root FROM root WHERE (root['id'] = {'Guid':'6bec688a-0aca-477c-8175-c09162b7a9b4','PartitionKey':'11','ParentGuid':null,'ParentPartitionKey':null,'Parent':null})
Instead, the developer must call .ToString() everywhere in the code.
var cosmosGuid = CosmosGuid.Parse("6bec688a0aca477c8175c09162b7a9b411");
var result = await Client.CreateDocumentQuery<MyClass>(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), options)
.Where(x => x.Id.ToString() == cosmosGuid.ToString())
.AsDocumentQuery();
This is the Sql generated
SELECT VALUE root FROM root WHERE (root['id'] = '6bec688a0aca477c8175c09162b7a9b411')
If I remove the CosmosGuid and revert back to using just a Guid as the Id property the SQL generated by the Cosmosdb SDK works fine. How can I make my class act like a .net Guid when used in Linq?
For LINQ to objects:
You can overload the == operator on your CosmosGuid class, see operator keyword.
Also, you could implement IEquatable<Guid> and use the .Equals() instead:
public class CosmosGuid : IEquatable<Guid>
{
....
public bool Equals(Guid other) {
return this.Guid == other;
}
}
.Where(x => cosmosGuid.Equals(x.Id))
I have a UI from where we are adding following values in a table Fields
ProductName
ProductId
ProductCode
I have a existing class Product with some existing properties
public class Product
{
public string ProductID { get; set; }
//product in a product search listing
public string StoreName { get; set; }
public string SearchToken { get; set; }
}
I am looking for a method which will add properties in existing class Product at run time (dynamically) when user adds values in a table Fields
I don't know a way of defining properties during runtime, but another possibiity for you to achieve what you need is to use a dynamic object in C# known as ExpandoObject.
You first need to declare your dynamic object, it uses a kind of Dictionary internally so you can then add your properties to it.
using System.Dynamic;
dynamic newobj = new ExpandoObject();
//I can add properties during compile time such as this
newobj.Test = "Yes";
newobj.JValue = 123;
//Or during runtime such as this (populated from two text boxes)
AddProperty(newobj, tbName.Text, tbValue.Text);
public void AddProperty(ExpandoObject expando, string propertyName, object propertyValue)
{
var exDict = expando as IDictionary<string, object>;
if (exDict.ContainsKey(propertyName))
exDict[propertyName] = propertyValue;
else
exDict.Add(propertyName, propertyValue);
}
I have used it once in a solution here: Flattern child/parent data with unknown number of columns
But these sources can probably explain it better;
https://www.oreilly.com/learning/building-c-objects-dynamically
https://weblog.west-wind.com/posts/2012/feb/08/creating-a-dynamic-extensible-c-expando-object
https://learn.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/types/using-type-dynamic
But, I'm not sure that this would really offer you any real advantages over using a simple Dictionary<string, object>
As others have pointed out, it doesn't sound like your scenario calls for this behavior. However, what you're asking for is possible. Using System.Reflection.Emit, you can dynamically extend your base class at runtime and even save it at runtime as a compiled .dll. The trickier part would be to figure out how to deploy your new class to production from your production code and overwrite your previous class, if that's needed. I'll leave that part up to you to figure out. As per the rest of your question, I had a similar need when creating automated testing for a framework that requires implementers to extend base classes, so I built a mini framework to do this. You can use it like this:
public void TestTypeCreator()
{
//create and save new type
object _newProduct = DynamicTypeCreator
.Create("NewProduct", typeof(Product), #"C:\PROD")
.AddPassThroughCtors()
.AddProperty("ProductName", typeof(string))
.FinishBuildingAndSaveType("NewProduct.dll")
.GetConstructor(new Type[0] { })
.Invoke(new object[0] { });
//set ProductName value
_newProduct.GetType().GetProperty("ProductName").SetValue(_newProduct, "Cool Item");
//get ProductName value
string _prodName = _newProduct.GetType().GetProperty("ProductName").GetValue(_newProduct).ToString();
//get StoreName value
string _storeName = _newProduct.GetType().GetProperty("StoreName").GetValue(_newProduct).ToString();
}
Since I haven't published this code anywhere, I'll paste the whole thing below. This "framework" is very limited and you might have to add to it/modify it for your specific purpose. Here ya go:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.IO;
namespace ConsoleCommon
{
public interface IBaseObject
{
IEmptyObject AddPassThroughCtors();
}
public interface IEmptyObject
{
IAfterProperty AddProperty(string name, Type type);
}
public interface IAfterProperty : IEmptyObject, IFinishBuild
{
IAfterAttribute AddPropertyAttribute(Type attrType, Type[] ctorArgTypes, params object[] ctorArgs);
}
public interface IAfterAttribute : IEmptyObject, IFinishBuild
{
}
public interface IFinishBuild
{
Type FinishBuildingType();
Type FinishBuildingAndSaveType(string assemblyFileName);
}
public static class DynamicTypeCreator
{
public static IBaseObject Create(string className, Type parentType)
{
return new DynamicTypeCreatorBase().Create(className, parentType);
}
public static IBaseObject Create(string className, Type parentType, string dir)
{
return new DynamicTypeCreatorBase().Create(className, parentType, dir);
}
}
public class PropertyBuilding
{
public PropertyBuilding(PropertyBuilder propertyBuild, MethodBuilder getBuild, MethodBuilder setBuild)
{
propertyBuilder = propertyBuild;
getBuilder = getBuild;
setBuilder = setBuild;
}
public PropertyBuilder propertyBuilder { get; }
public MethodBuilder getBuilder { get; }
public MethodBuilder setBuilder { get; }
}
public class DynamicTypeCreatorBase : IBaseObject, IEmptyObject, IAfterProperty, IAfterAttribute
{
TypeBuilder _tBuilder;
List<PropertyBuilding> _propBuilds = new List<PropertyBuilding>();
AssemblyBuilder _aBuilder;
/// <summary>
/// Begins creating type using the specified name.
/// </summary>
/// <param name="className">Class name for new type</param>
/// <param name="parentType">Name of base class. Use object if none</param>
/// <returns></returns>
public IBaseObject Create(string className, Type parentType)
{
return Create(className, parentType, "");
}
/// <summary>
/// Begins creating type using the specified name and saved in the specified directory.
/// Use this overload to save the resulting .dll in a specified directory.
/// </summary>
/// <param name="className">Class name for new type</param>
/// <param name="parentType">Name of base class. Use object if none</param>
/// <param name="dir">Directory path to save .dll in</param>
/// <returns></returns>
public IBaseObject Create (string className, Type parentType, string dir)
{
_parentType = parentType;
//Define type
AssemblyName _name = new AssemblyName(className);
if (string.IsNullOrWhiteSpace(dir))
{
_aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(_name, AssemblyBuilderAccess.RunAndSave);
}
else _aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(_name, AssemblyBuilderAccess.RunAndSave, dir);
ModuleBuilder _mBuilder = _aBuilder.DefineDynamicModule(_name.Name, _name.Name + ".dll");
_tBuilder = _mBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class, parentType);
return this;
}
/// <summary>
/// Adds constructors to new type that match all constructors on base type.
/// Parameters are passed to base type.
/// </summary>
/// <returns></returns>
public IEmptyObject AddPassThroughCtors()
{
foreach(ConstructorInfo _ctor in _parentType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
ParameterInfo[] _params = _ctor.GetParameters();
Type[] _paramTypes = _params.Select(p => p.ParameterType).ToArray();
Type[][] _reqModifiers = _params.Select(p => p.GetRequiredCustomModifiers()).ToArray();
Type[][] _optModifiers = _params.Select(p => p.GetOptionalCustomModifiers()).ToArray();
ConstructorBuilder _ctorBuild = _tBuilder.DefineConstructor(MethodAttributes.Public, _ctor.CallingConvention, _paramTypes, _reqModifiers, _optModifiers);
for (int i = 0; i < _params.Length; i++)
{
ParameterInfo _param = _params[i];
ParameterBuilder _prmBuild = _ctorBuild.DefineParameter(i + 1, _param.Attributes, _param.Name);
if (((int)_param.Attributes & (int)ParameterAttributes.HasDefault) != 0) _prmBuild.SetConstant(_param.RawDefaultValue);
foreach(CustomAttributeBuilder _attr in GetCustomAttrBuilders(_param.CustomAttributes))
{
_prmBuild.SetCustomAttribute(_attr);
}
}
//ConstructorBuilder _cBuilder = _tBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Any, argTypes);
ILGenerator _ctorGen = _ctorBuild.GetILGenerator();
_ctorGen.Emit(OpCodes.Nop);
//arg0=new obj, arg1-arg3=passed params. Push onto stack for call to base class
_ctorGen.Emit(OpCodes.Ldarg_0);
for (int i = 1; i <= _params.Length; i++) _ctorGen.Emit(OpCodes.Ldarg, i);
_ctorGen.Emit(OpCodes.Call, _ctor);
_ctorGen.Emit(OpCodes.Ret);
}
return this;
}
/// <summary>
/// Adds a new property to type with specified name and type.
/// </summary>
/// <param name="name">Name of property</param>
/// <param name="type">Type of property</param>
/// <returns></returns>
public IAfterProperty AddProperty(string name, Type type)
{
//base property
PropertyBuilder _pBuilder = _tBuilder.DefineProperty(name, PropertyAttributes.None, type, new Type[0] { });
//backing field
FieldBuilder _fBuilder = _tBuilder.DefineField($"m_{name}", type, FieldAttributes.Private);
//get method
MethodAttributes _propAttrs = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
MethodBuilder _getBuilder = _tBuilder.DefineMethod($"get_{name}", _propAttrs, type, Type.EmptyTypes);
ILGenerator _getGen = _getBuilder.GetILGenerator();
_getGen.Emit(OpCodes.Ldarg_0);
_getGen.Emit(OpCodes.Ldfld, _fBuilder);
_getGen.Emit(OpCodes.Ret);
//set method
MethodBuilder _setBuilder = _tBuilder.DefineMethod($"set_{name}", _propAttrs, null, new Type[] { type });
ILGenerator _setGen = _setBuilder.GetILGenerator();
_setGen.Emit(OpCodes.Ldarg_0);
_setGen.Emit(OpCodes.Ldarg_1);
_setGen.Emit(OpCodes.Stfld, _fBuilder);
_setGen.Emit(OpCodes.Ret);
_propBuilds.Add(new PropertyBuilding(_pBuilder, _getBuilder, _setBuilder));
return this;
}
/// <summary>
/// Adds an attribute to a property just added.
/// </summary>
/// <param name="attrType">Type of attribute</param>
/// <param name="ctorArgTypes">Types of attribute's cstor parameters</param>
/// <param name="ctorArgs">Values to pass in to attribute's cstor. Must match in type and order of cstorArgTypes parameter</param>
/// <returns></returns>
public IAfterAttribute AddPropertyAttribute(Type attrType, Type[] ctorArgTypes, params object[] ctorArgs)
{
if (ctorArgTypes.Length != ctorArgs.Length) throw new Exception("Type count must match arg count for attribute specification");
ConstructorInfo _attrCtor = attrType.GetConstructor(ctorArgTypes);
for (int i = 0; i < ctorArgTypes.Length; i++)
{
CustomAttributeBuilder _attrBuild = new CustomAttributeBuilder(_attrCtor, ctorArgs);
_propBuilds.Last().propertyBuilder.SetCustomAttribute(_attrBuild);
}
return this;
}
/// <summary>
/// Completes building type, compiles it, and returns the resulting type
/// </summary>
/// <returns></returns>
public Type FinishBuildingType()
{
foreach(var _pBuilder in _propBuilds)
{
_pBuilder.propertyBuilder.SetGetMethod(_pBuilder.getBuilder);
_pBuilder.propertyBuilder.SetSetMethod(_pBuilder.setBuilder);
}
Type _paramsType = _tBuilder.CreateType();
return _paramsType;
}
/// <summary>
/// Completes building type, compiles it, saves it, and returns the resultying type.
/// Assembly is saved in the calling assembly's directory or in the dir specified in the Create method.
/// </summary>
/// <param name="assemblyFileName">Filename of the assembly</param>
/// <returns></returns>
public Type FinishBuildingAndSaveType(string assemblyFileName)
{
Type _newType = FinishBuildingType();
Save(assemblyFileName);
return _newType;
}
#region Helpers
private CustomAttributeBuilder[] GetCustomAttrBuilders(IEnumerable<CustomAttributeData> customAttributes)
{
return customAttributes.Select(attribute => {
object[] attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray();
PropertyInfo[] namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<PropertyInfo>().ToArray();
object[] namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray();
FieldInfo[] namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<FieldInfo>().ToArray();
object[] namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray();
return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues);
}).ToArray();
}
/// <summary>
/// Requires admin privileges.
/// To save in a specified dir, use the Create overload that requires a 'dir' parameter.
/// </summary>
/// <param name="assemblyFileName"></param>
private void Save(string assemblyFileName)
{
string _assemblyName = assemblyFileName;
if (!Path.HasExtension(assemblyFileName) || Path.GetExtension(assemblyFileName).ToLower() != ".dll")
_assemblyName += ".dll";
_aBuilder.Save(_assemblyName);
}
#endregion
}
}
Definition:
public class Product
{
public string ProductID { get; set; }
//product in a product search listing
public string StoreName { get; set; }
public string SearchToken { get; set; }
public Dictionary<string, object> Fields { get; set; }
}
Usage:
var Prod = new Product();
Prod.Fields.Add("MyCustomFieldString", "my value here");
Prod.Fields.Add("MCustomFieldInt", 123);
MessageBox.Show(Prod.Fields[MyCustomFieldString].ToString());
MessageBox.Show(Prod.Fields[MCustomFieldInt].ToString());
You need to clarify (maybe to yourself) what exactly do you need, or I'll say, do you want.
After you compile your code, metadata is generated based on your types\methods\fields. There are many tables (45 to be more specific) to describe your data.
So, If you want to add new properties to some type, you need to add new rows to metadata tables. To add new rows, you need to compile code.
So what is Refelection.Emit? Is a way to dynamically create new data in your app. With this you create a dynamic assembly, dynamic type, dynamic method, add some IL and there you go. But, all of this is in memory. and that not what you want.
So, or dynamically data using Emit \ ExpandoObject, or statically data by adding the properties to code and compile it.
You have also a way of between the run time way to compile way, it's call Mono.Cecil by using it, you can, by code, add the new properties to type, then compile it and load the new dll to your AppDomain or just shutdown the app and re lunch it. It can be automatically and just by code. There is no magic, it will add data to the metadata tables in your assembly, but it can short the way of doing that manually.
But there is a more option that I'm sure you know about it. I you are using Visual Studio, you probably heard about Edit and Continue (EnC). This feature is actually write new code to your running assembly, on the fly add the new data (and only the new) to the metadata.
So, I can't tell you what you should do because I don't know exactly what is your case, and there is cases that people want extreme solution when they not need them, but if you really must the option to add new data to your already running assembly, EnC can be a solution. To use EnC take a look on the feature in Roslyn code (you might want to check their test to understand how to work with). After that, take a look on #Josh Varty example here.
After all I wrote, I really (x3) don't think you need to make something crazy for what you need. I'm sure there is a better, simple way to do it.
I found using DynamicObject Class useful for adding properties dynamically during runtime.
Add a property to your Existing Class like below :
class MyClass
{
public int Id { get; set; }// Existing property
public List<dynamic> Information { get; set; }
// Added above property to handle new properties which will come dynamically
}
//-------- While Implementing ----
MyClass obj = new MyClass();
obj.Id = 1111; // Existing Property
obj.Information = new List<dynamic>();
obj.Information.Add(new ExpandoObject());
obj.Information[0].Name= "Gyan"; // New Property
obj.Information[0].Age = 22; // New Property
I have stored immutable types in a temporary CQRS read store (query/read side, in fact implemented by a simple List with abstraction access layer, I don't want to use a full blown document database at this point). These read stores contains items like the following:
public class SomeItem
{
private readonly string name;
private readonly string description;
public SomeItem(string name, string description)
{
this.name = name;
this.description = description;
}
public string Name
{
get { return this.name; }
}
public string Description
{
get { return this.description; }
}
}
Now I want to change the Name and in a 2nd Command the Description.
These changes should keep the current state, which means for the example above:
// initial state
var someItem = new SomeItem("name", "description");
// update name -> newName
someItem = new SomeItem("newName", someItem.Description);
// update description -> newDescription
someItem = new SomeItem(someItem.Name, "newDescription");
This does look error prone to me if you have several properties... you have to manage keeping the current state. I could add something like Clone() to every type but I think/hope there is something better out there that performs well and is easy to use, I don't want to write much repetive code (lazy programmer). Any suggestions how to improve the code above? The SomeItem class needs to stay immutable (transported through several different threads).
Sadly, there's no simple way in C#. F# has the with keyword, and you could have a look at lenses, but it's all somewhat tedious in C#. The best I can give you is something like this:
class SomeItem
{
private readonly string name;
private readonly string description;
public SomeItem(string name, string description)
{
this.name = name;
this.description = description;
}
public SomeItem With
(
Option<string> name = null,
Option<string> description = null
)
{
return new SomeItem
(
name.GetValueOrDefault(this.name),
description.GetValueOrDefault(this.description)
);
}
}
This allows you to do the updates like
var newItem = oldItem.With(name: "My name!");
I've used this approach with extension methods and T4s to great effect, but even when you write the code manually, it's reasonably reliable - if you add a new field, you must add it to the With as well, so it works quite well.
There's a few more approaches if you are willing to tolerate runtime code generation and reducing type safety, but that's kind of going against the grain IMO.
With C#9 we got the with operator for this purpose.
public record Car
{
public string Brand { get; init; }
public string Color { get; init; }
}
var car = new Car{ Brand = "BMW", Color = "Red" };
var anotherCar = car with { Brand = "Tesla"};
With-expressions When working with immutable data, a common pattern is
to create new values from existing ones to represent a new state. For
instance, if our person were to change their last name we would
represent it as a new object that’s a copy of the old one, except with
a different last name. This technique is often referred to as
non-destructive mutation. Instead of representing the person over
time, the record represents the person’s state at a given time. To
help with this style of programming, records allow for a new kind of
expression; the with-expression:
News in C#9
NOTE
With operator is only supported by records.
Records At the core of classic object-oriented programming is the idea that an object has strong identity and encapsulates mutable state
that evolves over time. C# has always worked great for that, But
sometimes you want pretty much the exact opposite, and here C#’s
defaults have tended to get in the way, making things very laborious.
Recods in C#9
What you are looking for is commonly called the with operator:
// returns a new immutable object with just the single property changed
someItem = { someItem with Name = "newName" };
Unfortunately, unlike F#, C# does not have such an operator (yet?).
Other C# developers are missing this feature as well, which is why someone wrote a Fody extension to do exactly that:
https://github.com/mikhailshilkov/With.Fody
Here's another approach, which implements an UpdateWith method manually but requires an Option<T> helper class. Luaan's answer describes this approach in more detail:
Implementing F#-inspired "with" updates for immutable classes in C#
Simple solution
I also thought about this question. Record's are not suitable for my purposes, since it is necessary to interact with EF Core.
I suggest a simple and low-cost way:
add a copy constructor to the class;
Make properties that change during cloning available for initialization;
clone an object with a change through the copy constructor with an initialization list:
var a = new SomeItem("name", "abracadabra");
var b = new SomeItem(a) {Description="descr"};
Simple code
var a = new SomeItem("name", "abracadabra");
var b = new SomeItem(a) {Description="descr"};
public class SomeItem
{
private string name;
private string description;
public SomeItem(string name, string description)
{
Name = name;
Description = description;
}
public SomeItem(SomeItem another): this(another.Name, another.Description)
{
}
public string Name
{
get => name;
init => name = value;
}
public string Description
{
get => description;
init => description = value;
}
}
Extended solution
If the final type is not known at compile time, then this approach is easy to extend. Let's say there is a class "ValueObject" whose derived types we need to clone.
Note: I apologize for the incorrect translation in some places. English version obtained using google.translate
Additional code
using System.Linq.Expressions;
using Light.GuardClauses;
using JetBrains.Annotations;
using static DotNext.Linq.Expressions.ExpressionBuilder;
using ValueObject = Company.Domain....;
/// <summary>
/// The plagiarizer creates a copy of the object with a change in its individual properties using an initializer
/// </summary>
/// <remarks> The foreign object must define a copy constructor, and mutable members must support initialization </remarks>
public struct Plagiarist {
/// <summary>
/// Object to be copied
/// </summary>
private readonly object _alienObject;
/// <summary>
/// Type <see cref="_alienObject" />
/// </summary>
private Type _type => _alienObject.GetType();
/// <summary>
/// Object parsing Expression
/// </summary>
private readonly ParsingInitializationExpression _parser = new();
public Plagiarist(object alienObject) {
_alienObject = alienObject.MustNotBeNullReference();
if (!CopyConstructorIs())
throw new ArgumentException($"Type {_type.FullName} must implement a copy constructor");
}
/// <summary>
/// Does the object we want to plagiarize have a copy constructor?
/// </summary>
/// <returns>True - there is a copy constructor, otherwise - false</returns>
[Pure]
private bool CopyConstructorIs() {
return _type.GetConstructor(new[] { _type }) is not null;
}
/// <summary>
/// Returns a copy of a foreign object with a change in its individual properties using an initializer
/// </summary>
/// <param name="initializer">
/// <see cref="Expression" /> create an object with initialization of those fields,
/// which need to be changed:
/// <code>() => new T() {Member1 = "Changed value1", Member2 = "Changed value2"}</code>
/// or <see cref="Expression" /> create an anonymous type with initialization of those fields
/// that need to be changed:
/// <code>() => new {Member1 = "Changed value1", Member2 = "Changed value2"}</code>
/// </param>
/// <returns></returns>
[Pure]
public object Plagiarize(Expression<Func<object>> initializer) {
var (newValues, constructParam) = _parser.ParseInitialization(initializer);
var constrCopies = _type.New(_alienObject.Const().Convert(_type));
Expression plagiarist = (newValues.Count, constructParam.Count) switch {
(> 0, _) => Expression.MemberInit(constrCopies, newValues.Values),
(0, > 0) => Expression.MemberInit(constrCopies, ConstructorInInitializationList(constructParam).Values),
_ => constrCopies
};
var plagiarize = Expression.Lambda<Func<object>>(plagiarist).Compile();
return plagiarize();
}
[Pure]
public Dictionary<string, MemberAssignment> ConstructorInInitializationList(
Dictionary<string, Expression> constructorParameters) {
Dictionary<string, MemberAssignment> initializer = new();
const BindingFlags flagReflections = BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public;
var allProperties = _type.GetProperties(flagReflections);
var allFields = _type.GetFields(flagReflections);
foreach (var memberName in constructorParameters.Keys) {
var property = allProperties.FirstOrDefault(s => s.Name ==memberName);
var field = allFields.FirstOrDefault(s => s.Name == memberName);
(MemberInfo member, Type memberType) = (property, field) switch {
({ }, _) => (property, property.PropertyType),
(null, { }) => ((MemberInfo)field, field.FieldType),
_ => throw new ArgumentException($"{_type.FullName} does not contain member {memberName}")
};
initializer[memberName] = Expression.Bind(member, constructorParameters[memberName].Convert(memberType));
}
return initializer;
}
/// <summary>
/// Template "Visitor" for traversing the expression tree in order to highlight
/// initialization expression and constructor
/// </summary>
private class ParsingInitializationExpression : ExpressionVisitor {
private Dictionary<string, MemberAssignment>? _initializer;
private Dictionary<string, Expression>? _initializerAnonym;
/// <summary>
/// Parses the expression tree and returns the initializer and constructor parameters
/// </summary>
/// <param name="initializer"><see cref="Expression" /> to parse</param>
/// <returns> tuple of initializer and constructor</returns>
public ParsedInitialization ParseInitialization(Expression initializer) {
_initializer = new Dictionary<string, MemberAssignment>();
_initializerAnonym = new Dictionary<string, Expression>();
Visit(initializer);
return new ParsedInitialization(_initializer, _initializerAnonym);
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) {
_initializer![node.Member.Name] = node;
return base.VisitMemberAssignment(node);
}
protected override Expression VisitNew(NewExpression node) {
foreach (var (member, value) in node.Members?.Zip(node.Arguments) ??
Array.Empty<(MemberInfo First, Expression Second)>())
_initializerAnonym![member.Name] = value;
return base.VisitNew(node);
}
/// <summary>
/// Type to return values from method <see cref="ParseInitialization" />
/// </summary>
/// <param name="Initializer"></param>
/// <param name="ConstructorParameters"></param>
public record struct ParsedInitialization(Dictionary<string, MemberAssignment> Initializer,
Dictionary<string, Expression> ConstructorParameters);
}
}
public static class ValueObjectPlagiarizer{
/// <summary>
/// Creates a copy of the object with a change in its individual properties using an initializer
/// </summary>
/// <param name="alien">Object to be plagiarized</param>
/// <param name="initializer">
/// <see cref="Expression" /> creating an object of type <typeparamref name="T" />
/// with initialization of those fields that need to be changed:
/// <code>ob.Plagiarize(() => new T() {Member1 = "Changed value1", Member2 = "Changed value2"})</code>
/// or <see cref="Expression" /> create an anonymous type with initialization of those fields that need to be changed:
/// <code>ob.Plagiarize(() => new {Member1 = "Changed value1", Member2 = "Changed value2"})</code>
/// </param>
/// <returns>plagiarism of the object</returns>
public static object Plagiarize<T>(this ValueObject alien, Expression<Func<T>> initializer)
where T : class {
var bodyReduced = initializer.Convert<object>();
var initializerReduced = Expression.Lambda<Func<object>>(bodyReduced, initializer.Parameters);
return new Plagiarist(alien).Plagiarize(initializerReduced);
}
}
Usages
If SomeItem is a descendant of ValueObject
ValueObject a = new SomeItem("name", "abracadabra");
// via type constructor
var b = (SomeItem)a.Plagiarize(()=>new SomeItem(null){Description="descr"});
// anonymous type
var c = (SomeItem)a.Plagiarize(()=>new{Description="descr"});
b.Description.Should().Be("descr"); //true
c.Description.Should().Be("descr"); //true
If what you want to do is (as you commented) update the name of an existing object, a readonly property might be bad design.
Otherwise if its really a new object you want to create, you might want your class to implement some interface with a 'Dispose' method.
I am not sure how to seek help and this is kind of unusual I agree, so please pardon me. I'll try to explain as below:
• I am consuming JSON using POST and getting a dynamic object. I need to convert all incoming properties in dynamic object to uppercase.
• I am using what I think is a bodged solution. I'm converting JSON to string Dictionary, then putting values into a new dictionary after converting the Key to Key.ToUpper() and then deserializing it back into a dynamic object.
Current working solution is as follows:
string _StrJSON = #"{""FIELDA"" : ""1234"",""fieldb"" : ""OtherValue""}";
var d = JsonConvert.DeserializeObject<Dictionary<string, string>>(_StrJSON);
d.ContainsKey("FIELDB").Dump(); // returns false, obviously.
Dictionary<string, string> dr = new Dictionary<string, string>();
d.ToList().ForEach(r => dr.Add(r.Key.ToUpper(), r.Value));
dr.Dump("dr"); // FIELDA 1234 - FIELDB OtherValue
var a = JsonConvert.SerializeObject(dr);
a.Dump(); // {"FIELDA":"1234","FIELDB":"OtherValue"}
This works.
[EDIT]
Some confusion about my "var" above and other stuff. I am very clear on what is dynamic and what isn't.
[/EDIT]
What I have tried so far which DOES NOT WORK is as below:
namespace Newtonsoft.Json
{
/// <summary>
/// Converts JSON keys to uppercase.
/// </summary>
public class UppercaseContractResolver : Serialization.DefaultContractResolver
{
#region Methods.
#region Public Methods.
/// <summary>
/// Resolves property name for this JSON object.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
protected override string ResolvePropertyName(string key)
{
return key.ToUpper();
}
#endregion
#endregion
#region Constructors.
/// <summary>
/// Converts JSON keys to uppercase.
/// </summary>
public UppercaseContractResolver()
{
}
#endregion
}
/// <summary>
/// Wrapper for Newtonsoft.Json.JsonConvert for JSON keys as uppercase.
/// </summary>
public static class JsonConvertUpper
{
#region Members.
#endregion
#region Methods.
#region Public Methods.
/// <summary>
/// Tries to serialize specified object with JSON keys in upper case.
/// <para>e.g. "key":value should become "KEY":value by using this method.</para>
/// </summary>
/// <param name="value">Object.</param>
/// <returns></returns>
public static string SerializeObjectUpper(object value)
{
return JsonConvert.SerializeObject(value, new JsonSerializerSettings
{
ContractResolver = new UppercaseContractResolver()
});
}
/// <summary>
/// Tries to Deserialize specified object with JSON keys in upper case.
/// <para>e.g. "key":value should become "KEY":value by using this method.</para>
/// </summary>
/// <param name="strValue">String.</param>
/// <returns></returns>
public static object DeserializeObjectUpper(string strValue)
{
// DeserializeObject does not allow uppercase properties. So use SerializeObjectUpper and then deserialize.
var value = JsonConvert.DeserializeObject(strValue);
string strJSON = SerializeObjectUpper(value);
return JsonConvert.DeserializeObject(strJSON, new JsonSerializerSettings()
{
ContractResolver = new UppercaseContractResolver()
});
}
#endregion
#endregion
}
}
The method call for above would be:
dynamic json = JsonConvertUpper.DeserializeObjectUpper(_StrJSON);
if (json.CTN== null)
{...}
[EDIT]
Please note I don't have classes as JSON keys have to be treated as UPPERCASE and classes and other code etc are all in ProperCase.
[/EDIT]
Any way of doing it inside JsonConvert at all? To avoid my manual patch? Any help is appreciated. Thanks.
The reason that your contract resolver does not work is that Json.NET's contract resolver defines how to map JSON from and to the properties of a POCO when serializing -- but when you deserialize to dynamic, Json.NET isn't actually mapping from and to a POCO, it's building up a JToken hierarchy directly from the JSON, the members of which implement IDynamicMetaObjectProvider. I.e.
dynamic o = JsonConvert.DeserializeObject(strJSON);
and
dynamic o = JToken.Parse(strJSON);
are the same.
Given that this is the case, the easiest way to do what you want would be to explicitly convert each property name to uppercase as the hierarchy is being built, like so:
public static class JsonExtensions
{
public static JToken ParseUppercase(string json)
{
using (var textReader = new StringReader(json))
using (var jsonReader = new JsonTextReader(textReader))
{
return jsonReader.ParseUppercase();
}
}
public static JToken ParseUppercase(this JsonReader reader)
{
return reader.Parse(n => n.ToUpperInvariant());
}
public static JToken Parse(this JsonReader reader, Func<string, string> nameMap)
{
JToken token;
using (var writer = new RenamingJTokenWriter(nameMap))
{
writer.WriteToken(reader);
token = writer.Token;
}
return token;
}
}
class RenamingJTokenWriter : JTokenWriter
{
readonly Func<string, string> nameMap;
public RenamingJTokenWriter(Func<string, string> nameMap)
: base()
{
if (nameMap == null)
throw new ArgumentNullException();
this.nameMap = nameMap;
}
public override void WritePropertyName(string name)
{
base.WritePropertyName(nameMap(name));
}
// No need to override WritePropertyName(string name, bool escape) since it calls WritePropertyName(string name)
}
And then use it like:
dynamic json = JsonExtensions.ParseUppercase(_StrJSON);
Sample fiddle.
I have added a new control to my .NET form and I want to save its value in a table.I have added a new column in my table.How do you use MyGeneration Doodads to create a data access object for this table?I have looked at http://www.mygenerationsoftware.com/portal/doodads/cusage/tabid/53/default.aspx
but I can't understand what it means by "template".What is the procedure to regenerate doodads for a table?
You wont get much of a response on this... dOOdads have not been supported for many years. Regardless, we use dOOdads too and I just roll my own repositories for my WPF projects (I know it's not ASP, but I don't think you can just "plug-and-play"). Here is an example of my base lookup class:
public abstract class BaseLookup<TEntity>
{
// Constructor
protected BaseLookup()
{
this.SubsetIdentifier = null;
}
// Properties
public virtual object SubsetIdentifier { get; set; }
// Public Methods
public abstract IEnumerable<TEntity> Read();
public virtual TEntity ReadSingle()
{
return default(TEntity);
}
// Protected Methods
/// <summary>
/// Retrieve translated entities from the database. The methods used to do this
/// are specified from the child class as parameters (i.e. Action or Func delegates).
/// </summary>
/// <param name="loadSubsetFunc">Specify how to load a set of database records.
/// Return boolean confirmation that records were found.</param>
/// <param name="orderByAction">Specify what should happen to sort the results.</param>
/// <param name="translateRowFunc">Specify how a database record should translate to
/// a model entity. Return the new entity.</param>
/// <param name="moveNextFunc">Specify how the database row pointer should move on.
/// Return FALSE on a call to the final row.</param>
/// <returns>A set of translated entities from the database.</returns>
/// <example><code>
///
/// return base.ReloadRecords(
/// _dOOdad.LoadAll,
/// () =>
/// {
/// _dOOdad.Sort = _dOOdad.GetAutoKeyColumn();
/// },
/// () =>
/// {
/// var entity = new LookupEntity();
/// return entity.PopulateLookupEntity(_dOOdad.CurrentRow.ItemArray);
/// },
/// _dOOdad.MoveNext);
///
/// </code></example>
protected virtual IEnumerable<TEntity> ReloadRecords(Func<bool> loadSubsetFunc,
Action orderByAction, Func<TEntity> translateRowFunc, Func<bool> moveNextFunc)
{
// If records are found, sort them and return set of entities
if (loadSubsetFunc())
{
orderByAction();
do
{
var entity = translateRowFunc();
yield return entity;
}
while (moveNextFunc());
}
else
{
Debug.WriteLine(
string.Format(
"# ZERO records found: Returning empty set of {0}.",
typeof(TEntity).Name));
}
}
}
And here is a concrete implementation of the base lookup:
public class CyclesLookup : BaseLookup<BaseLookupEntity>
{
// Fields & Constructor
private readonly CYCLES _dOOdad;
public CyclesLookup(IDbConnection connection, string library)
{
_dOOdad = OpenConnection(connection, library);
}
// Base Override Methods
public override IEnumerable<BaseLookupEntity> Read()
{
// Clear old result set and settings
_dOOdad.FlushData();
// Reload the records and return them sorted and translated
return base.ReloadRecords(
_dOOdad.LoadAll,
() =>
{
_dOOdad.Sort = _dOOdad.GetAutoKeyColumn();
},
() =>
{
var entity = new LookupEntity();
entity.Description = string.Format("Cycles for {0}", _dOOdad.YEAR);
return entity.PopulateLookupEntity(_dOOdad.CurrentRow.ItemArray, true);
},
_dOOdad.MoveNext);
}
// Private Helper Methods
private static CYCLES OpenConnection(IDbConnection connection, string library)
{
var dOOdad = new CYCLES(connection);
dOOdad.SchemaGlobal = library + ".";
return dOOdad;
}
}
That PopulateLookupEntity method is just an extension method:
public static T PopulateLookupEntity<T>(this T entity, object[] rowItems,
bool noDescription = false) where T : BaseLookupEntity
{
int id = 0;
int.TryParse(rowItems[0].ToString(), out id);
entity.RecordID = id;
var attributesFirstIndex = 1;
if (!noDescription)
{
entity.Description = rowItems[1].ToString();
attributesFirstIndex = 2;
}
entity.Attributes = new object[rowItems.Length - attributesFirstIndex];
for (int index = attributesFirstIndex; index < rowItems.Length; index++)
{
entity.Attributes[index - attributesFirstIndex] = rowItems[index];
}
return (T)entity;
}
For non-lookups, I have a more complex BaseRepository class that inherits from BaseLookup. With that one, I don't use PopulateLookupEntity, but a private helper method like this:
private IPlanningGridHeader TranslateCurrentDoodadRow()
{
return new PlanningGridHeader()
{
PlanNumber = Convert.ToInt32(_dOOdad.PLANNUMBER),
Active = _dOOdad.ACTIVE == 1M,
Capturer = _dOOdad.CAPTURER,
Region = _dOOdad.REGION,
CorporateID = Convert.ToInt32(_dOOdad.CORPORATEID),
StartDate = _dOOdad.STARTDATE.ConvertDb2Date(),
EndDate = _dOOdad.ENDDATE.ConvertDb2Date(),
AdvertStartDate = _dOOdad.ADVERTSTARTDATE.ConvertDb2Date(),
AdvertEndDate = _dOOdad.ADVERTENDDATE.ConvertDb2Date(),
BpcsDealNumber = Convert.ToInt32(_dOOdad.BPCSDEALNUMBER),
Description = _dOOdad.DESCRIPTION,
DeactivationReason = _dOOdad.DEACTIVATIONREASON,
LastSavedUsername = _dOOdad.LASTUSER,
LastSavedDateTime = _dOOdad.LASTDATE.ConvertDb2Date().AddDb2Time(_dOOdad.LASTTIME)
};
}
Hope this helps :-)
Although it's old but I recommend staying away from this library. It has a serious bug that cause connection leaking, I bet the creator didn't check ADO.NET practices when coding it. Moreover, he didn't know how to deal with DBNull, thus he invented "string properties" along the side of normal properties to deal with NULL problem, turned generated code and programming model into a big mess, created more NullReferenceException when developer access normal properties which has value as null.
I collected all those problems from a 10+ years old legacy system using MyGeneration Doodads. Felt very happy that I could get rid of it at last.
If you're on the way of finding a DAC library for enterprise app, just don't pick this library.