I have a problem attaching metadata class to ADO.NET entity data model generated classes.
According to the following links...
http://blogs.microsoft.co.il/blogs/gilf/archive/2011/01/20/adding-metadata-to-entities-in-the-data-model.aspx
http://msdn.microsoft.com/en-us/library/cc679243.aspx
http://goneale.com/2009/03/04/using-metadatatype-attribute-with-aspnet-mvc-xval-validation-framework/
http://davidhayden.com/blog/dave/archive/2008/01/06/ASPNETDynamicDataTutorialBuddyMetadataProviderCustomMetadataProviders.aspx
http://davidhayden.com/blog/dave/archive/2008/05/15/DynamicDataWebsitesScaffoldTableScaffoldColumnAttributes.aspx
I created a metadata class to add some Attributes to properties. I could add this attributes to properties in generated classes and It works but I wanted to avoid loosing this attributes every time I have to update and recreate my ADO.NET entity data model.
My question is, what am I doing wrong ? Why in runtime properties does not have my custom attributes ?
This is a part of generated data class
[EdmEntityTypeAttribute(NamespaceName="HelpMeHowModel", Name="Article")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
[MetadataType(typeof(ArticleMetaData))]
public partial class Article : EntityObject
{
#region Primitive Properties
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Boolean IsPublished
{
get
{
return _IsPublished;
}
set
{
OnIsPublishedChanging(value);
ReportPropertyChanging("IsPublished");
_IsPublished = StructuralObject.SetValidValue(value);
ReportPropertyChanged("IsPublished");
OnIsPublishedChanged();
}
}
private global::System.Boolean _IsPublished;
partial void OnIsPublishedChanging(global::System.Boolean value);
partial void OnIsPublishedChanged();
...
.. and this is my metadata class
public class ArticleMetaData
{
#region Primitive Properties
[BoolFunction(BoolFunction.ThreeStateRadioButton)]
public global::System.Boolean IsPublished { get; set; }
For everybody looking for the solution for the same problem...
Adding custom attributes to partial MetadataType class is possible and it works but there is a little problem.
Using
PropertyInfo pi;
pi.GetCustomAttributes(...)
will get the Attributes from the main class only and not from the class used as MetadataType.
Based on solution explained here
Attribute.IsDefined doesn't see attributes applied with MetadataType class
I created two extension methods for PropertyInfo class to get all attributes.
namespace FAIN.Framework.Commons
{
public static class PropertyInfoEx
{
public static object[] GetAllCustomAttributes(this PropertyInfo pi, bool inherit)
{
return GetAllCustomAttributes(pi, null, inherit);
}
/// <summary>
/// Get Custom Attributes + attributes added in MetadataType
/// </summary>
/// <param name="pi"></param>
/// <param name="attributeType"></param>
/// <param name="inherit"></param>
/// <returns></returns>
public static object[] GetAllCustomAttributes(this PropertyInfo pi, Type attributeType, bool inherit)
{
if (pi == null) return null;
List<object> allAttributes = new List<object>();
object[] attributes = null;
if (attributeType != null)
{
attributes = pi.GetCustomAttributes(attributeType, inherit);
}
else
{
attributes = pi.GetCustomAttributes(inherit);
}
allAttributes.AddRange(attributes);
// search all the Metadata of the class declaring the property to get all CustomAttributes if there are any
MetadataTypeAttribute[] metadataTypes = pi.DeclaringType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray();
foreach (MetadataTypeAttribute metadata in metadataTypes)
{
if (metadata != null)
{
PropertyInfo[] properties = metadata.MetadataClassType.GetProperties();
PropertyInfo propertyInfo = properties.Where(p => p.Name == pi.Name).FirstOrDefault();
if (propertyInfo != null)
{
if (attributeType != null)
{
attributes = propertyInfo.GetCustomAttributes(attributeType, inherit);
}
else
{
attributes = propertyInfo.GetCustomAttributes(inherit);
}
allAttributes.AddRange(attributes);
}
}
}
return allAttributes.ToArray();
}
}
}
Related
I try to moq my DbContext like in memory db. I use PostgreSql in my app, so I have entities with jsonb properties. For example:
[Table("examples")]
public class Example
{
/// <summary>
/// id (autogenerated by DB)
/// </summary>
[Column("id", TypeName = "bigserial")]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
/// <inheritdoc/>
[Column("layout_config", TypeName = "jsonb")]
[Required]
public LayoutConfigDto LayoutConfig { get; set; }
}
[Keyless]
public class LayoutConfigDto
{
/// <summary>
/// Координата X расположения виджета
/// </summary>
public byte X { get; set; }
/// <summary>
/// Координата Y расположения виджета
/// </summary>
public byte Y { get; set; }
}
so LayoutConfigDto just a model for JSON, that doesn't need a table. And doesn't need any relation or configuration for table.
Then I create Test class:
[TestFixture]
public class ExampleServiceTests
{
private IExampleService _exampleService;
[SetUp]
public void Setup()
{
DbContextOptions<ExampleDbContext> options = new DbContextOptionsBuilder<ExampleDbContext>()
.UseInMemoryDatabase(databaseName: "InMemoryExampleDatabase")
.Options;
ExampleDbContext dbContext = new(options);
new FakeDatabaseDataGenerator().Generate(dbContext);
Mock<ILogger<ExampleService>> mock = new();
_exampleService = new ExampleService(dbContext, mock.Object);
}
[Test]
[TestCase(0)]
[TestCase(3)]
public async Task GetExampleTest(long id)
{
ExampleModel example = await _exampleService.GetExample(id);
if (id <= 0)
{
Assert.AreEqual(example, null);
return;
}
Assert.AreNotEqual(null, example);
}
}
When I run GetExampleTest it fails on Exceptions like:
System.InvalidOperationException : Unable to determine the relationship represented by navigation 'UserWidgetModel.LayoutConfig' of type 'LayoutConfigDto'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I can't use [NotMapped] attribute on LayoutConfig field, because I need to get it from DB and with pgsql driver all works and serializes. But with in memory db it fails. How can I change my Test to make it works? Is there any other options to mock db context?
The error it returns is pretty clear; it tells you what you need to do. If you can't use the [NotMapped] attribute, you need to configure a relationship for the fields it needs
I'm trying do upgrade Unity from version 3.0.1304.1 to the latest, 5.11.1. Unsurprisingly, there are some issues (3.0.1304.1 is quite old). The code below accomplished two things:
Use a ContainerControlledLifetimeManager for all classes deriving from some base class X, without specifying that for every class derived from X.
Use the default constructor when resolving for all classes deriving from base class Y, without specifying that for every class derived from Y.
Unity documentation is scarce nowadays, and lacks examples. I've read the upgrade notes and regular documentation but I can't make this work with the new version.
So far I have:
Replaced IConstructorSelectorPolicy with ISelect<ConstructorInfo>
Replaced IBuilderContext with BuilderContext (or ref BuilderContext in the PreBuildUp function)
But what to do with the LifetimeManagerFactory? Should I derive IOCConstructorSelectorPolicy from ISelect<ConstructorInfo> and should IOCConstructorSelectorPolicy.SelectConstructor then be replaced by Select (as per the ISelect interface) and how to implement this Select function?
I know this is a very long shot, and I'll post this on the unity github as well (as a request for documentation of sorts), but hopefully there is someone who can give me some pointers.
/// <summary>
/// This class merely exists for readability: avoid having to write the class it derives from.
/// </summary>
public class IOCConfigLifetimeExtension : DefaultLifetimeManagerExtension<ContainerControlledLifetimeManager, X>
{}
/// <summary>
/// An IOC strategy to use a specific LifetimeManager (<typeparamref name="TLifetimeManager"/>) for subclasses of <typeparamref name="TBaseClass"/>.
/// </summary>
public class DefaultLifetimeManagerExtension<TLifetimeManager, TBaseClass> : UnityContainerExtension where TLifetimeManager : LifetimeManager, new()
{
protected override void Initialize()
{
var theFactory = new LifetimeManagerFactory(Context, typeof(TLifetimeManager));
Context.Strategies.Add(new DefaultLifetimeManagerStrategy(theFactory, TypePredicate), UnityBuildStage.TypeMapping);
}
private bool TypePredicate(Type type)
{
return typeof (TBaseClass).IsAssignableFrom(type);
}
}
public class DefaultLifetimeManagerStrategy : BuilderStrategy
{
public DefaultLifetimeManagerStrategy(LifetimeManagerFactory factory, Predicate<Type> typePredicate)
{
mFactory = factory;
mTypePredicate = typePredicate;
}
public override void PreBuildUp(IBuilderContext context)
{
if (context.Existing == null) {
var theLifetime = context.Policies.GetNoDefault<ILifetimePolicy>(context.BuildKey, false);
if (theLifetime == null && mTypePredicate(context.BuildKey.Type)) {
theLifetime = mFactory.CreateLifetimePolicy();
context.PersistentPolicies.Set(theLifetime, context.BuildKey);
}
}
}
private readonly LifetimeManagerFactory mFactory;
private readonly Predicate<Type> mTypePredicate;
}
/// <summary>
/// A Unity extension that prioritizes the default constructor for classes derived of Y when available, otherwise the default resolve method is used.
/// </summary>
public class IOCExtension : UnityContainerExtension
{
protected override void Initialize()
{
var theDefaultConstructorSelectorPolicy = Context.Policies.Get<IConstructorSelectorPolicy>(null);
Context.Policies.SetDefault<IConstructorSelectorPolicy>(new IOCConstructorSelectorPolicy(theDefaultConstructorSelectorPolicy));
}
public class IOCConstructorSelectorPolicy : IConstructorSelectorPolicy
{
public IOCConstructorSelectorPolicy(IConstructorSelectorPolicy defaultConstructorSelectorPolicy)
{
mDefaultConstructorSelectorPolicy = defaultConstructorSelectorPolicy;
}
public SelectedConstructor SelectConstructor(IBuilderContext context, IPolicyList resolverPolicyDestination)
{
Type theType = context.BuildKey.Type;
if (typeof(DataNode).IsAssignableFrom(theType)) {
ConstructorInfo theDefaultConstructorInfo = theType.GetConstructor(Type.EmptyTypes);
if (theDefaultConstructorInfo != null && theDefaultConstructorInfo.IsPublic) {
return new SelectedConstructor(theDefaultConstructorInfo);
}
}
return mDefaultConstructorSelectorPolicy.SelectConstructor(context, resolverPolicyDestination);
}
private readonly IConstructorSelectorPolicy mDefaultConstructorSelectorPolicy;
}
}
By default convention, strings properties in an entity model that are not explicitly given a max length are set to nvarchar(max) in the database. We wish to override this convention and give strings a max length of nvarchar(100) if they are not already explicitly set otherwise.
I discovered the PropertyMaxLengthConvention built-in convention, which by its description and documentation would seem to be what I am looking for. However, it either doesn't work or I'm using it wrong or it just doesn't do what I think it does.
I've tried simply adding the convention:
modelBuilder.Conventions.Add(new PropertyMaxLengthConvention(100));
Then I thought maybe the default one is already being used, so I tried removing it first:
modelBuilder.Conventions.Remove<PropertyMaxLengthConvention>();
modelBuilder.Conventions.Add(new PropertyMaxLengthConvention(100));
I even tried explictly adding the convention before and after the default one:
modelBuilder.Conventions.AddBefore<PropertyMaxLengthConvention>(new PropertyMaxLengthConvention(100));
modelBuilder.Conventions.AddAfter<PropertyMaxLengthConvention>(new PropertyMaxLengthConvention(100));
No joy. When I add migrations, the columns are still created as nvarchar(max).
Is there a way to use that convention to do what I want? If not, can I write a custom convention that will default string properties to nvarchar(100) but will still allow me to explicitly set them to a different value including maxlength?
After tracking down the source code for the aforementioned convention, I discovered that it only sets the default max length for properties that are specified to have fixed length. (Bizarre!)
So I took the source code and modified it to create my own convention. Now string properties with unspecified max length will have a default max length instead of being nvarchar(max). The only downside is there doesn't appear to be a way to detect when the IsMaxLength() configuration is explicitly applied. So if I have a column that I do want to have created as nvarchar(max) I can't use IsMaxLength() to do it.
To address this, I created an extension method for StringPropertyConfiguration called ForceMaxLength() that configures the property with HasMaxLength(int.MaxValue) - ordinarily an invalid value, but one for which I can easily test in my custom convention. When I detect it, I simply set the MaxLength back to null and set the IsMaxLength to true and let the property configuration continue as normal.
Here's the custom convention:
using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace MyProject.CustomConventions
{
public class CustomPropertyMaxLengthConvention : IConceptualModelConvention<EntityType>, IConceptualModelConvention<ComplexType>
{
private const int DefaultLength = 128;
private readonly int length;
public CustomPropertyMaxLengthConvention()
: this(DefaultLength)
{
}
public CustomPropertyMaxLengthConvention(int length)
{
if (length <= 0)
{
throw new ArgumentOutOfRangeException("length", "Invalid Max Length Size");
}
this.length = length;
}
public virtual void Apply(EntityType item, DbModel model)
{
SetLength(item.DeclaredProperties);
}
public virtual void Apply(ComplexType item, DbModel model)
{
SetLength(item.Properties);
}
private void SetLength(IEnumerable<EdmProperty> properties)
{
foreach (EdmProperty current in properties)
{
if (current.IsPrimitiveType)
{
if (current.PrimitiveType == PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String))
{
SetStringDefaults(current);
}
if (current.PrimitiveType == PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Binary))
{
SetBinaryDefaults(current);
}
}
}
}
private void SetStringDefaults(EdmProperty property)
{
if (property.IsUnicode == null)
{
property.IsUnicode = true;
}
SetBinaryDefaults(property);
}
private void SetBinaryDefaults(EdmProperty property)
{
if (property.MaxLength == int.MaxValue)
{
property.MaxLength = null;
property.IsMaxLength = true;
}
else if (property.MaxLength == null || !property.IsMaxLength)
{
property.MaxLength = length;
}
}
}
}
Here's the extension method:
using System.Data.Entity.ModelConfiguration.Configuration;
namespace MyProject.Model.Mapping
{
public static class MappingExtensions
{
public static void ForceMaxLength(this StringPropertyConfiguration obj)
{
obj.HasMaxLength(int.MaxValue);
}
}
}
Here's how it's used:
using System.Data.Entity.ModelConfiguration;
namespace MyProject.Model.Mapping
{
public class MyEntityMap : EntityTypeConfiguration<MyEntity>
{
public MyEntityMap()
{
Property(v => v.StringValue).ForceMaxLength();
}
}
}
Or just
public class StringConventions : Convention
{
public StringConventions()
{
this.Properties<string>().Configure(x => x.HasMaxLength(100));
}
}
In EF6 you can use a custom code first convention, but you will also need to have a way to specify nvarchar(max) data type to a string property. So, I came up with the following solution.
/// <summary>
/// Set this attribute to string property to have nvarchar(max) type for db table column.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class TextAttribute : Attribute
{
}
/// <summary>
/// Changes all string properties without System.ComponentModel.DataAnnotations.StringLength or
/// Text attributes to use string length 16 (i.e nvarchar(16) instead of nvarchar(max) by default).
/// Use TextAttribute to a property to have nvarchar(max) data type.
/// </summary>
public class StringLength16Convention : Convention
{
public StringLength16Convention()
{
Properties<string>()
.Where(p => !p.GetCustomAttributes(false).OfType<DatabaseGeneratedAttribute>().Any())
.Configure(p => p.HasMaxLength(16));
Properties()
.Where(p => p.GetCustomAttributes(false).OfType<TextAttribute>().Any())
.Configure(p => p.IsMaxLength());
}
}
public class CoreContext : DbContext, ICoreContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Change string length default behavior.
modelBuilder.Conventions.Add(new StringLength16Convention());
}
}
public class LogMessage
{
[Key]
public Guid Id { get; set; }
[StringLength(25)] // Explicit data length. Result data type is nvarchar(25)
public string Computer { get; set; }
//[StringLength(25)] // Implicit data length. Result data type is nvarchar(16)
public string AgencyName { get; set; }
[Text] // Explicit max data length. Result data type is nvarchar(max)
public string Message { get; set; }
}
I am using NewtonSoft.JSON. When running
JsonConvert.SerializeObject(myObject)
it is adding an $id value to my JSON - like this:
"$id": "1",
"BookingId": 0,
"CompanyId": 0,
"IsCashBooking": false,
"PaymentMethod": 0,
"IsReferral": false,
"IsReferralPercent": false,
"ReferralPaymentType": 0,
"ReferralDues": 0,
"PassengerId": 0,
"DepartmentID": 0,
"CostCenterID": 0,
"DeadMiles": 0
Can we remove this $id with some JsonSerializerSettings or by any other method?
If yes - then how...
I added this code to my WebApiConfig register method and I got rid of all $id in JSON.
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
If for some reason you're using a custom ContractResolver, take a look at this other stack overflow;
Json.Net adding $id to EF objects despite setting PreserveReferencesHandling to "None"
To remove the $id in JSON for my web API. I included [JsonObject(IsReference = false)] for my class objects and [JsonProperty(IsReference = false)] for my properties that are of object types. In my case the RespObj property is generic Type T and could take any object type I pass to it including collections so I had to use [JsonProperty(IsReference = false)] to get rid of the $id in the serialized JSON string.
I did not change my WebApiConfig because I was using the MVC help page for WEB API and it required me to have this configuration in my webApiconfig:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;
json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
The class object
[DataContract(IsReference = true)]
[JsonObject(IsReference = false)]
public class QMResponse<T> where T : new()
{
public QMResponse()
{
}
/// <summary>
/// The response code
/// </summary>
public string RespCode { get; set; }
/// <summary>
/// The response message
/// </summary>
public string RespMxg { get; set; }
/// <summary>
/// The exception message
/// </summary>
public string exception { get; set; }
/// <summary>
/// The object type returned
/// </summary>
[JsonProperty(IsReference = false)]
public T RespObj { get; set; }
/// <summary>
/// No of records returned
/// </summary>
public long RecordCount { get; set; }
/// <summary>
/// The Session Object
/// </summary>
public string session_id { get; set; }
}
In case 'id' is a property of your class then apply [JsonIgnore] attribute on it.
Otherwise probably here is the answer for your question:
http://james.newtonking.com/json/help/index.html?topic=html/PreserveObjectReferences.htm
You can keep the basic configuration:
Newtonsoft.Json.PreserveReferencesHandling.All;
I used this code format for my methods
public JsonResult<T> Get()
{
return Json(result);
}
It works fine for me.
The custom ContractResolver setting overrides the PreserveReferencesHandling setting.
In your implementation of DefaultContractResolver/IContractResolver, add this;
public override JsonContract ResolveContract(Type type) {
var contract = base.ResolveContract(type);
contract.IsReference = false;
return contract;
}
This behaves similarly to the PreserveReferencesHandling.None setting without a custom ContractResolver
I’m trying to decide which of the two factory patterns I should use in my Asp.Net applications:
1 : All DAL providers derive from same abstract base class DepartmentsProvider, which defines public interface of the class ( all the necessary CRUD methods for which derived classes (providers ) provide a concrete implementation ). BLL layer instantiates correct provider by calling DepartmentsProvider.Instance:
public abstract class DepartmentsProvider
{
static private DepartmentsProvider _instance = null;
/// <summary>
/// Returns an instance of the provider type specified in the config file
/// </summary>
static public DepartmentsProvider Instance
{
get
{
if (_instance == null)
_instance = (DepartmentsProvider)Activator.CreateInstance(
Type.GetType(Settings.Departments.ProviderType));
return _instance;
}
}
public abstract List<Department> GetDepartments ();
}
/// Concrete provider
public class SqlDepartmentsProvider : DepartmentsProvider
{
public override List<Department> GetDepartments()
{
using (SqlConnection cn = new SqlConnection(this.ConnectionString))
{...}
}
}
2 :
public class DepartmentsProvider
{
static private DbProviderFactory _instance = null;
/// <summary>
/// Returns an instance of the FactoryProvider type specified in the config file
/// </summary>
static public DbProviderFactory Instance
{
get
{
if (_instance == null)
_instance = (DepartmentsProvider)Activator.CreateInstance(
Type.GetType(Settings.Departments.ProviderType));
return _instance;
}
}
public static List<ForumDetails> GetDepartments()
{
DbConnection cn = Instance.CreateConnection();
...
}
}
In first case we implement new provider by deriving from DepartmentsProvider class, while in second case new provider is implemented by deriving from DBProviderFactory.
What are pros and cons of each implementation?
Much appreciated
Like RPM1984 I would suggest looking at the repository pattern and dependency injection.
You wouldn't want to use a singleton here as the connections may remain open and you may run into unforeseen issues.