Suppose I have the following:
public record Settings
{
public int Setting1 { get; init; }
}
public record MoreSettings : Settings
{
public string Setting2 { get; init; }
}
...
var settings = new Settings { Setting1 = 1 };
// This is an error - CS0117
MoreSettings moreSettings = settings with { Setting2 = "A string setting" };
Is there a clean way to achieve this? Do I have the syntax wrong?
Obviously, in this contrived case I could just manually copy each base property.
var moreSettings = new MoreSettings { Setting1 = settings.Setting1, Setting2 = "A String!" };
But what if the base record type has lots of properties?
From your original question,
MoreSettings moreSettings = settings with { Setting2 = "A string setting" };
with keyword internally ensures call to the copy constructor of the type, before changing the specified properties. The Settings record, in the example, does not have a property called Settings2, which is why this is not allowed.
Regarding your second approach, if the base class has a lot properties, you could introduce an additional constructor for the MoreSettings record, which accepts an instance of Settings as workaround for not having to add each of the properties as in the second example given in OP.
Instead, you could,
public record MoreSettings : Settings
{
public MoreSettings(Settings parent) : base(parent) { }
public string Setting2 { get; init; }
}
Now you could call make the call as
MoreSettings moreSettings = new MoreSettings(settings) { Setting2 = "A string setting" };
You could also remove the MoreSettings on right hand side by making use of Target-Type New expressions in C# 9
MoreSettings moreSettings = new (settings) { Setting2 = "A string setting" };
Related
Suppose I have the following:
public record Settings
{
public int Setting1 { get; init; }
}
public record MoreSettings : Settings
{
public string Setting2 { get; init; }
}
...
var settings = new Settings { Setting1 = 1 };
// This is an error - CS0117
MoreSettings moreSettings = settings with { Setting2 = "A string setting" };
Is there a clean way to achieve this? Do I have the syntax wrong?
Obviously, in this contrived case I could just manually copy each base property.
var moreSettings = new MoreSettings { Setting1 = settings.Setting1, Setting2 = "A String!" };
But what if the base record type has lots of properties?
From your original question,
MoreSettings moreSettings = settings with { Setting2 = "A string setting" };
with keyword internally ensures call to the copy constructor of the type, before changing the specified properties. The Settings record, in the example, does not have a property called Settings2, which is why this is not allowed.
Regarding your second approach, if the base class has a lot properties, you could introduce an additional constructor for the MoreSettings record, which accepts an instance of Settings as workaround for not having to add each of the properties as in the second example given in OP.
Instead, you could,
public record MoreSettings : Settings
{
public MoreSettings(Settings parent) : base(parent) { }
public string Setting2 { get; init; }
}
Now you could call make the call as
MoreSettings moreSettings = new MoreSettings(settings) { Setting2 = "A string setting" };
You could also remove the MoreSettings on right hand side by making use of Target-Type New expressions in C# 9
MoreSettings moreSettings = new (settings) { Setting2 = "A string setting" };
I have this implementation of versioned property
public class VersionedProperty<T>: Dictionary<int, T>, IParentEntityTracker //where T:IComparable<T>
{
[BsonIgnore]
public IVersionableEntity ParentEntity { get; set; }
public VersionedProperty()
{
}
public VersionedProperty(IVersionableEntity parentEntity)
{
ParentEntity = parentEntity;
}
public T Value
{
set
{
var curVal = Value;
if (EqualityComparer<T>.Default.Equals(curVal, value))
return;
ParentEntity.AtLeastOneVersionedPropertyModified = true;
this[ParentEntity.Version + 1] = value;
}
get
{
var key = ParentEntity.Version + (ParentEntity.AtLeastOneVersionedPropertyModified ? 1 : 0);
var keys = Keys.Where(k => k<=key).ToList();
if (!keys.Any())
return default(T);
var max = keys.Max();
T res;
TryGetValue(max, out res);
return res;
}
}
}
Initially I created some property in the document as not versioned. Let say
public class Product
{
public Decimal Price{get;set;}
}
after some time I realized that I did mistake and I should use versioned property
public class Product
{
public VersionedProperty<Decimal> Price{get;set;}
}
What I would like is that old value in the existing document automatically deserialized into this versioned property to avoid writing update queries on the collection.
Is it possible somehow interfere with the process of deserializing?
May be this can help: I use postsharp to create empty instance of versioned property for autoproperties. My postsharp aspect also assigns reference of a parent entity to ParentEntity property of that newly created empty instance of the versioned property.
I was thinking to generate EntityTypeConfiguration dynamically from run time and i don't want any EF dependency in Models[That is why i avoid Data Annotation].
So I declare a custom attribute(or can be any configuration file later on)
[AttributeUsage(AttributeTargets.Property, AllowMultiple=true )]
public class PersistableMemberAttribute : Attribute
{
public bool Iskey;
public bool IsRequired;
public bool IsIgnored;
public bool IsMany;
public string HasForeignKey;
public bool PropertyIsRequired;
public bool PropertyIsOptional;
}
And here is one of my Models is look like:
public class Blog
{
[PersistableMember(Iskey=true)]
public Guid BlogId { get; set; }
[PersistableMember(PropertyIsRequired = true)]
public string Name { get; set; }
public string Url { get; set; }
[PersistableMember(IsIgnored=true)]
public int Rating { get; set; }
[PersistableMember(IsMany =true)]
public ICollection<Post> Posts { get; set; }
}
Now I am going to write a generic EntityTypeConfiguration , which will create the configuration dynamically on run time based on the attribute values :
public class GenericEntityConfiguration<T> : EntityTypeConfiguration<T> where T : class
{
public GenericEntityConfiguration()
{
var members = typeof(T).GetProperties();
if (null != members)
{
foreach (var property in members)
{
var attrb= property.GetCustomAttributes(typeof( PersistableMemberAttribute ),false).OfType<PersistableMemberAttribute>();
if (attrb != null && attrb.Count() > 0)
{
foreach (var memberAttributute in attrb)
{
if (memberAttributute.Iskey || memberAttributute.IsIgnored)
{
var entityMethod = this.GetType().GetMethod("Setkey");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
if (memberAttributute.IsRequired)
{
var entityMethod = this.GetType().GetMethod("SetRequired");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
if (memberAttributute.PropertyIsRequired || memberAttributute.PropertyIsOptional)
{
var entityMethod = this.GetType().GetMethod("SetPropertyConfiguration");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
}
}
}
}
}
public void SetPropertyConfiguration<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute)
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.PropertyIsRequired)
{
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsRequired();
}
if (attribute.PropertyIsOptional)
{
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsOptional();
}
}
public void Setkey<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute)
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.Iskey)
{
this.HasKey<TResult>((Expression<Func<T,TResult>>)lambda);
}
if (attribute.IsIgnored)
{
this.Ignore<TResult>((Expression<Func<T, TResult>>)lambda);
}
}
public void SetRequired<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute) where TResult : class
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.IsRequired)
{
this.HasRequired<TResult>((Expression<Func<T, TResult>>)lambda);
}
}
}
But i got the compilation error of
Error 1 The type 'TResult' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Data.Entity.ModelConfiguration.Configuration.StructuralTypeConfiguration.Property(System.Linq.Expressions.Expression>)' D:\R&D\UpdateStorePOC\UpdateStorePOC\Data\GenericEntityConfiguration.cs 63 17 UpdateStorePOC
which for these two statements:
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsRequired();
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsOptional();
that means that I need to put a constraint on my method to restrict it to a value type. In C#, this is done with the ‘struct’ keyword.
public void SetPropertyConfiguration<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute) Where TResult : struct
But Its not the solution since my property type can be a class e.g string or int, bool double, etc . So it is not at all clear that I can send them into this method. Please help me to solve this issue whether there is any other way to do it.
I don't want any EF dependency in models.
With fluent mapping you're almost there and you won't come any closer. Your attributes, even though intended to be moved to a configuration file, don't make your model any more free of any EF footprint.1 Worse, they only add a second mapping layer (if you like) between your model and EF's mapping. I only see drawbacks:
You still have to maintain meta data for your model, probably not any less than regular fluent mapping and (probably) in awkward manually edited XML without compile-time checking.
You will keep expanding your code to cover cases that EF's mapping covers but yours doesn't yet.2 So it's a waste of energy: in the end you'll basically have rewritten EF's mapping methods.
You'll have to keep your fingers crossed when you want to upgrade EF.
With bugs/problems you're on your own: hard to get support from the community.
So my answer to your question help me to solve this issue would be: use fluent mapping out of the box. Keep it simple.
1 For example, you would still have to use the virtual modifier to enable proxies for lazy loading.
2 Like support for inheritance, unmapped foreign keys, max length, db data type, ... this could go on for a while.
I'd like to define in class declaration which items are index, something like:
public class MyClass {
public int SomeNum { get; set; }
[THISISANINDEX]
public string SomeProperty { get; set; }
}
so to have the same effect as ensureIndex("SomeProperty")
Is this possible?
I think this is a nice idea, but you have to do this yourself, there's no built-in support for it. If you have an access layer you can do it in there. You'd need an attribute class, something like this;
public enum IndexConstraints
{
Normal = 0x00000001, // Ascending, non-indexed
Descending = 0x00000010,
Unique = 0x00000100,
Sparse = 0x00001000, // allows nulls in the indexed fields
}
// Applied to a member
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class EnsureIndexAttribute : EnsureIndexes
{
public EnsureIndex(IndexConstraints ic = IndexConstraints.Normal) : base(ic) { }
}
// Applied to a class
[AttributeUsage(AttributeTargets.Class)]
public class EnsureIndexesAttribute : Attribute
{
public bool Descending { get; private set; }
public bool Unique { get; private set; }
public bool Sparse { get; private set; }
public string[] Keys { get; private set; }
public EnsureIndexes(params string[] keys) : this(IndexConstraints.Normal, keys) {}
public EnsureIndexes(IndexConstraints ic, params string[] keys)
{
this.Descending = ((ic & IndexConstraints.Descending) != 0);
this.Unique = ((ic & IndexConstraints.Unique) != 0); ;
this.Sparse = ((ic & IndexConstraints.Sparse) != 0); ;
this.Keys = keys;
}
}//class EnsureIndexes
You could then apply attributes at either the class or member level as follows. I found that adding at member level was less likely to get out of sync with the schema compared to adding at the class level. You need to make sure of course that you get the actual element name as opposed to the C# member name;
[CollectionName("People")]
//[EnsureIndexes("k")]// doing it here would allow for multi-key configs
public class Person
{
[BsonElement("k")] // name mapping in the DB schema
[BsonIgnoreIfNull]
[EnsureIndex(IndexConstraints.Unique|IndexConstraints.Sparse)] // name is implicit here
public string userId{ get; protected set; }
// other properties go here
}
and then in your DB access implementation (or repository), you need something like this;
private void AssureIndexesNotInlinable()
{
// We can only index a collection if there's at least one element, otherwise it does nothing
if (this.collection.Count() > 0)
{
// Check for EnsureIndex Attribute
var theClass = typeof(T);
// Walk the members of the class to see if there are any directly attached index directives
foreach (var m in theClass.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
{
List<string> elementNameOverride = new List<string>(1);
EnsureIndexes indexAttr = null;
// For each members attribs
foreach (Attribute attr in m.GetCustomAttributes())
{
if (attr.GetType() == typeof(EnsureIndex))
indexAttr = (EnsureIndex)attr;
if (attr.GetType() == typeof(RepoElementAttribute))
elementNameOverride.Add(((RepoElementAttribute)attr).ElementName);
if ((indexAttr != null) && (elementNameOverride.Count != 0))
break;
}
// Index
if (indexAttr != null)
{
if (elementNameOverride.Count() > 0)
EnsureIndexesAsDeclared(indexAttr, elementNameOverride);
else
EnsureIndexesAsDeclared(indexAttr);
}
}
// Walk the atributes on the class itself. WARNING: We don't validate the member names here, we just create the indexes
// so if you create a unique index and don't have a field to match you'll get an exception as you try to add the second
// item with a null value on that key
foreach (Attribute attr in theClass.GetCustomAttributes(true))
{
if (attr.GetType() == typeof(EnsureIndexes))
EnsureIndexesAsDeclared((EnsureIndexes)attr);
}//foreach
}//if this.collection.count
}//AssureIndexesNotInlinable()
EnsureIndexes then looks like this;
private void EnsureIndexesAsDeclared(EnsureIndexes attr, List<string> indexFields = null)
{
var eia = attr as EnsureIndexes;
if (indexFields == null)
indexFields = eia.Keys.ToList();
// use driver specific methods to actually create this index on the collection
var db = GetRepositoryManager(); // if you have a repository or some other method of your own
db.EnsureIndexes(indexFields, attr.Descending, attr.Unique, attr.Sparse);
}//EnsureIndexes()
Note that you'll place this after each and every update because if you forget somewhere your indexes may not get created. It's important to ensure therefore that you optimise the call so that it returns quickly if there's no indexing to do before going through all that reflection code. Ideally, you'd do this just once, or at the very least, once per application startup. So one way would be to use a static flag to track whether you've already done so, and you'd need additional lock protection around that, but over-simplistically, it looks something like this;
void AssureIndexes()
{
if (_requiresIndexing)
AssureIndexesInit();
}
So that's the method you'll want in each and every DB update you make, which, if you're lucky would get inlined by the JIT optimizer as well.
See below for a naive implementation which could do with some brains to take the indexing advice from the MongoDb documentation into consideration. Creating indexes based on queries used within the application instead of adding custom attributes to properties might be another option.
using System;
using System.Reflection;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using NUnit.Framework;
using SharpTestsEx;
namespace Mongeek
{
[TestFixture]
class TestDecorateToEnsureIndex
{
[Test]
public void ShouldIndexPropertyWithEnsureIndexAttribute()
{
var server = MongoServer.Create("mongodb://localhost");
var db = server.GetDatabase("IndexTest");
var boatCollection = db.GetCollection<Boat>("Boats");
boatCollection.DropAllIndexes();
var indexer = new Indexer();
indexer.EnsureThat(boatCollection).HasIndexesNeededBy<Boat>();
boatCollection.IndexExists(new[] { "Name" }).Should().Be.True();
}
}
internal class Indexer
{
private MongoCollection _mongoCollection;
public Indexer EnsureThat(MongoCollection mongoCollection)
{
_mongoCollection = mongoCollection;
return this;
}
public Indexer HasIndexesNeededBy<T>()
{
Type t = typeof (T);
foreach(PropertyInfo prop in t.GetProperties() )
{
if (Attribute.IsDefined(prop, typeof (EnsureIndexAttribute)))
{
_mongoCollection.EnsureIndex(new[] {prop.Name});
}
}
return this;
}
}
internal class Boat
{
public Boat(Guid id)
{
Id = id;
}
[BsonId]
public Guid Id { get; private set; }
public int Length { get; set; }
[EnsureIndex]
public string Name { get; set; }
}
internal class EnsureIndexAttribute : Attribute
{
}
}
Example:
We have a conditional field.
This conditional field is a radio button with the following two values “yes” and “no”.
Lets say the name of this radiobutton is “AAA”.
This conditional field “AAA” should only be displayed when another radio button field “BBB” is set to “yes”. (Values of radio button “BBB” are also “yes” and no”) .
But the conditional field “AAA” should be displayed with NO pre-set value, means “yes” nor “no” should be set when the field is first displayed.
The problem occurs based on the requirement that the conditional field “AAA” should ONLY be required when (the non-conditional) field “BBB” is set to “yes” – and not required when the field “BBB” is set to “no”.
(Sounds, that I didn’t heard anything about an if statement, or? But hold on and continue reading ...)
Believe me, it would not be a problem for me to solve this topic when we would use the “Modelstate” – but we are talking here about Validation (Data Annotations) that looks like this here:
public class Input1FormModel
{
[Required(ErrorMessageResourceName="Error_Field_AAA_Empty",
ErrorMessageResourceType=typeof(Resources.MyDialog))]
public int AAA { get; set; }
}
I fully understand ALSO these lines of code - I believe ;-)
...
//property limits
public int UpperBound { get { return DateTime.Now.Year; } }
public int LowerBound { get { return 1900; } }
...
[NotNullValidator]
[PropertyComparisonValidator("LowerBound", ComparisonOperator.GreaterThan)]
[PropertyComparisonValidator("UpperBound", ComparisonOperator.LessThanEqual)]
public int? XYZ { get; set; }
But, how to solve the above described dependency (AAA <-> BBB)?
Changing “return DateTime.Now.Year;” to a function call which checks first the other field and returns then true or false? But how to fetch there the value of the other field?
You may need to use IDataErrorInfo.
See this question, where I answered this:
Check out IDataErrorInfo and this question I asked about IDataErrorInfo vs. DataAnnotations.
You can do this using data annotations but the annotation needs to be operating on the class level and not on the property level as validationattributes are for single properties.
Here is an example I created because post code is optional and state not required if people have said they're in New Zealand, but it is compulsory in Australia. This composite validation with take the whole model as the input value and use reflection to compare the values of the property names passed in from the data annotation.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class NZPostcodeAttribute : ValidationAttribute
{
public const string _defaultErrorMessage = "Postcode and State are required for Australian residents";
private readonly object _typeId = new object();
public NZPostcodeAttribute(string countryProperty, string postcodeProperty, string stateProperty)
{
CountryProperty = countryProperty;
PostcodeProperty = postcodeProperty;
StateProperty = stateProperty;
}
public string CountryProperty { get; private set; }
public string StateProperty { get; private set; }
public string PostcodeProperty { get; private set; }
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return _defaultErrorMessage;
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(value);
object countryValue = props.Find(CountryProperty, true).GetValue(value);
object postcodeValue = props.Find(PostcodeProperty, true).GetValue(value);
object stateValue = props.Find(StateProperty, true).GetValue(value);
string countryString = countryValue == null ? "" : countryValue.ToString();
string postcodeString = postcodeValue == null ? "" : postcodeValue.ToString();
string stateString = stateValue == null ? "" : stateValue.ToString();
bool isValid = true;
if (countryString.ToString().ToLower() == "australia")
{
if (String.IsNullOrEmpty(postcodeString) || String.IsNullOrEmpty(stateString))
{
isValid = false;
}
}
if (!String.IsNullOrEmpty(postcodeString))
{
string isNumeric = "^[0-9]+";
if (!Regex.IsMatch(postcodeString, isNumeric))
isValid = false;
}
return isValid;
}
}
When you want to apply this to your model, it needs to be done on a class level on the model (see the flag AttributeTargets.Class at the top).
Do it as follows:
[NZPostcode("Country", "Postcode", "State")]
public class UserRegistrationModel
{....
You need to point the validation attribute to the property names. It is also possible to add client side validation to this as well, but that would be a whole article on its own.
You can easily adapt the above to your scenario.