EF Core save value from method instead of property - c#

I like to add some data to a column, from a method instead of a property. Is this somehow possible in EF Core?
For example, the config code could look like this:
internal class MyEntityTypeConfiguration : IEntityTypeConfiguration<MyEntity>
{
public void Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.ToTable("Table1");
// Add column "Value1" and set it with the return value of myEntity.GetValue()
builder.Property<string>("Value1").WithValue(myEntity => myEntity.GetValue()); // TODO create WithValue
builder.HasKey(o => o.Id);
}
}
in this case, the WithValue method won't exist.
Example:
For example, I will save 2 entities.
GetValue() for entity 1 returns "I am Entity 1"
GetValue() for entity 2 returns "I am Entity 2"
Then I like store "I am Entity 1" and "I am Entity 2" in the column Value1
Solution
Jairo's solution with the ValueGenerator worked perfect for me! I made the WithValue like this:
internal class ValueRetriever<TEntityEntry, TResult> : Microsoft.EntityFrameworkCore.ValueGeneration.ValueGenerator<TResult>
{
private readonly Func<TEntityEntry, TResult> _retrieve;
public ValueRetriever(Func<TEntityEntry, TResult> retrieve)
{
_retrieve = retrieve;
}
public override bool GeneratesTemporaryValues => false;
public override TResult Next(EntityEntry entry) => _retrieve((TEntityEntry)entry.Entity);
}
WithValue extension:
public static void WithValue<TEntityEntry, TResult>(this PropertyBuilder<TResult> propertyBuilder, Func<TEntityEntry, TResult> retrieve)
{
propertyBuilder.HasValueGenerator((property, type) => new ValueRetriever<TEntityEntry, TResult>(retrieve));
}
Usage:
builder
.Property<string>("Value1")
.WithValue<MyEntity, string>(myEntity => myEntity.GetValue());

I think shadow properties can help you.
EF Core shadow properties let you define & persist non-domain data, data that are not defined in your classes. You define shadow properties in your DbContext, and you use the DbContext to set their values.
To define them:
modelBuilder.Entity<MyEntity>().Property<String>("Value1");
To set their values:
dbContext.Entry(myEntity).Property("Value1").CurrentValue = myEntity.GetValue();
Also, you can use the HasValueGenerator extension method to set a value generator that can get the value from your entity:
modelBuilder.Entity<MyEntity>().Property<string>("Value1").HasValueGenerator<ValueGenerator>();
The Value Generator:
class ValueGenerator : Microsoft.EntityFrameworkCore.ValueGeneration.ValueGenerator
{
public override bool GeneratesTemporaryValues => false;
protected override object NextValue(EntityEntry entry) => ((MyEntity) entry.Entity).GetValue();
}
The entity:
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string GetValue() => $"My Name: {Name}";
}

Related

Entity Framework Core - Setting Value Converter generically

I'm currently trialing Entity Framework Core 2.1 with a view to using it in the company I work for's business applications. I've got most of the way in implementing Value Converters in my test project but my existing knowledge base has let me down at the last hurdle!
What I'm trying to do
My understanding is that for enum values, the built in type converters can convert from the enum value to the string equivalent (EnumToStringConverter) or from the enum value to it's numerical representation (EnumToNumberConverter). However we use a custom string value to represent the enum in our database, so I have written a custom EnumToDbStringEquivalentConvertor to do this conversion and the database string value is specified as an attribute on each of the enum values in my model.
The code is as follows:
Model
public class User
{
[Key] public int ID { get; set; }
public EmployeeType EmployeeType { get; set; }
}
public enum EmployeeType
{
[EnumDbStringValue("D")]
Director,
[EnumDbStringValue("W")]
Weekly,
[EnumDbStringValue("S")]
Salaried
}
DataContext
public class MyDataContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType.IsEnum)
{
property.SetValueConverter(new EnumToDbStringEquivalentConvertor<EmployeeType>());
}
}
}
}
}
Value Converter
public class EnumToDbStringEquivalentConvertor<T> : ValueConverter<T, string>
{
public EnumToDbStringEquivalentConvertor(ConverterMappingHints mappingHints = null) : base(convertToProviderExpression, convertFromProviderExpression, mappingHints)
{ }
private static Expression<Func<T, string>> convertToProviderExpression = x => ToDbString(x);
private static Expression<Func<string, T>> convertFromProviderExpression = x => ToEnum<T>(x);
public static string ToDbString<TEnum>(TEnum tEnum)
{
var enumType = tEnum.GetType();
var enumTypeMemberInfo = enumType.GetMember(tEnum.ToString());
EnumDbStringValueAttribute enumDbStringValueAttribute = (EnumDbStringValueAttribute)enumTypeMemberInfo[0]
.GetCustomAttributes(typeof(EnumDbStringValueAttribute), false)
.FirstOrDefault();
return enumDbStringValueAttribute.StringValue;
}
public static TEnum ToEnum<TEnum>(string stringValue)
{
// Code not included for brevity
}
}
This code (I'm glad to say) seems to be working without any issues.
My problem
The documentation around value converters seems to suggest the way we assign them in the OnModelCreating method is to physically assign each individual type converter to each individual property in the model. I don't want to have to do this - I want my model to be the driver. I'll implement this later but, for now, in the current version of the code I'm looping through the entity types in my model, checking the 'IsEnum' property value and then assigning the value converter at that point.
My problem is that the SetValueConverter extension method that I'm using requires me to pass it a new instance of EnumToDbStringEquivalentConvertor, which in my example is hard coded to be EnumToDbStringEquivalentConvertor which works. However I don't want that to be hardcoded - I want to pass the entity type's ClrType.
I have used reflection to create generic types and generic methods before but I can't seem to find the right code to get this working.
This:
public class MyDataContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType.IsEnum)
{
var converterType = typeof(EnumToDbStringEquivalentConvertor<>);
var genericConverterType = converterType.MakeGenericType(property.ClrType);
MethodInfo setValueConverterMethodInfo = typeof(MutablePropertyExtensions).GetMethod("SetValueConverter");
setValueConverterMethodInfo.Invoke(property,
new object[] { property, Activator.CreateInstance(genericConverterType) });
}
}
}
}
}
gives me an error of "System.MissingMethodException: 'No parameterless constructor defined for this object.'" on the GetModel method in Microsoft.EntityFrameworkCore.Infrastructure
So my question is can anyone advise me of how I can pass my value converter generically to EF Core's 'SetValueConveter' method?
Thank you in advance for your assistance.
You are almost there. The problem is this code
Activator.CreateInstance(genericConverterType)
which tries to find and invoke parameterless constructor of your converter class. But your class constructor does have a parameter, although optional. Optional parameters are just compiler sugar; when using reflection you should pass them explicitly.
So you need to use the CreateInstance overload accepting params object[] args and pass null for mappingHints.
Also, there is no need to call SetValueConverter via reflection - it's part of the public API.
The working code could be like this:
if (property.ClrType.IsEnum)
{
var converterType = typeof(EnumToDbStringEquivalentConvertor<>)
.MakeGenericType(property.ClrType);
var converter = (ValueConverter)Activator.CreateInstance(converterType, (object)null);
property.SetValueConverter(converter);
}

Using Generics or Templates in c# to simplify object retrieval in my application

I have a database that has multiple tables that are objects in my application, Member, Employer, Invoice etc.
I want to create a generic method that will allow me to retrieve a single object from any of the tables. ex. public Object GetRow(ClassType type, ClassKey key, object valueToFind)
so the Member method right now would be
Member member = _manager.Members.Where(m => m.MemberKey == valueToFind).FirstOrDefauilt();
Employer employer = _manager.Employers.Where(e => e.EmployerKey == valueToFind).FirstOrDefault();
Invoice invoice = _manager.Invoices.Where(i => i.InvoiceKey == valueToFind).FirstOrDefault();
How do i write a generic method that would handle all 3 cases?
I am assuming you are using entity framework. In such case you can write a method like:
public T RetrieveFirst<T>( Expression<Func<T, bool>> filter)
{
return _manager.Set<T>().FirstOrDefault(filter);
}
You can then use it as follows:
var member = RetrieveFirst<Member>(m => m. MemberKey = valueToFind );
If you wanted to unify the filtering, the entities would all have to implement an interface and have the same name of the key property.
I think #ajawad987 has written the perfect answer for you :-)
You could implement an interface that all your entities (employee, manager, etc.) inherit and then implement a generic method that works with that interface.
Basically you define a simple interface that defines the common Key property:
public interface IHasKeyProperty
{
int Key { get; set; }
}
public class Manager : IHasKeyProperty
{
public int Key { get; set; }
// Rest of manager code...
}
public class Employee : IHasKeyProperty
{
public int Key { get; set; }
// Rest of employee code...
}
And then you can write a generic query like this:
public TEntity GetByKey<TEntity>(int key)
where TEntity : IHasKeyProperty, class
{
return this._dbContext.Set<TEntity>().FirstOrDefault(x => x.Key == key);
}
I'm assuming you're using Entity Framework Core, hence the _dbContext variable in my snippet above.
Using the method would look like this:
var myEmployee = GetByKey<Employee>(207);
var myManager = GetByKey<Manager>(101);
To extend #ajawad987's answer, if you need to support different key types you could do:
public interface IHasKeyProperty<TId>
{
TId Key { get; set; }
}
public class Manager : IHasKeyProperty<int>
{
public int Key { get; set; }
// Rest of manager code...
}
public class Employee : IHasKeyProperty<Guid>
{
public Guid Key { get; set; }
// Rest of employee code...
}
public TEntity GetByKey<TEntity, TId>(TId key) where TEntity : IHasKeyProperty<TId>
{
return this._dbContext.Set<TEntity>().FirstOrDefault(x => x.Id == key);
}

Sort the list in a class that is requested from the database

I have a class that contains a list of parameters. For example:
public class Container
{
public List<Parameter> Parameters { get; set; }
}
public class Parameter
{
puplic string Name {get; set;}
}
Class Сontainer obtained from the database through Entity Framework. Many classes contain Container. I need to ensure that all classes that contain Сontainer and also retrieved from the database containing the sorted list of Parameters. That is, the Container must sort Parameters or request step or immediately thereafter.
How this can be achieved?
Maybe write to the configuration
internal class ContainerConfiguration : EntityTypeConfiguration<Container>
{
public ContainerConfiguration()
{
ToTable("Container");
HasKey(p => p.Id);
... ???
}
}
Or wright in dataSet
protected override IQueryable<Container> DataSet(DbContext db)
{
return db.Set<ProcessMeasurer>()
.Include(it => it.Parameters.Select(p => p.Parameter));
}
Another option for solving the problem:
Create your attribute and specify which field to use for sorting by default:
public class DefaultOrderFieldAttribute : Attribute
{
public DefaultOrderFieldAttribute()
{
}
public string FieldName { get; set; }
}
[DefaultOrderField(FieldName = "ParameterName")]
public partial class Parameter
{
}
Write a Visitor, which in the case of detection of our attribute modifies select:
public class DefaultOrderVisitor : DefaultExpressionVisitor
{
public override DbExpression Visit(DbScanExpression expression)
{
const string NAMESPACE = "OrderTest";
var type =
Assembly.GetExecutingAssembly().GetType(string.Format("{0}.{1}", NAMESPACE, expression.Target.Name));
var attribute =
type.GetCustomAttributes(typeof (DefaultOrderFieldAttribute)).SingleOrDefault() as
DefaultOrderFieldAttribute;
if (attribute != null)
return expression.OrderBy(ex => ex.Property(attribute.FieldName));
return expression;
}
}
Put in our Visitor Interceptor:
public class DefaultOrderInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace)
{
var queryCommand = interceptionContext.Result as DbQueryCommandTree;
if (queryCommand != null)
{
var newQuery = queryCommand.Query.Accept(new DefaultOrderVisitor());
interceptionContext.Result = new DbQueryCommandTree(queryCommand.MetadataWorkspace,
queryCommand.DataSpace, newQuery);
}
}
}
}
and register it in the configuration (this class just has to be in the same assembly as the model):
public class EntityFrameworkConfiguration : DbConfiguration
{
public EntityFrameworkConfiguration()
{
AddInterceptor(new DefaultOrderInterceptor());
}
}
Need to work with the entity class.
If we want to sort the collection was in all the elements that comprise it, we have to change the appropriate property.
Obvious variant - creating property setter.
private List<Parameter> _parameters;
public List<Parameter> Parameters
{
get { return _parameters; }
set { _parameters = value.OrderBy(...).ToList();
}
But the behavior of the compiler (call the setter once, and the multiple callin to the getter) gave me a reason to assume that the target collection is not put in a property all at once. The items in the query is gradually added to the collection. Therefore, sorting in setter does not always work.
Therefore, we must carry out sorting the return value
get
{
if(_parameters == null) return null;
_parameters = _parameters.OrderBy(...).ToList();
return _parameters;
}
It works. But the problem is that an appeal to the getter, and hence sorting, will be carried out when EntityFramework inserts each value. This affects the performance.
The best variant that I know at the moment is to inherit all entities from the interface with the function Prepare
public interface IEntity
{
void Prepare();
}
and implement it in each class model. Models that comprise other models cause a method to prepare, for each desired properties.
public class SomeModel : IEntity
{
public CustomType SomeProperty { get; set; }
public OneMoreCustomType AnotherProrerty { get; set; }
public void Prepare()
{
SomeProperty.Prepare();
AnotherProperty.Prepare();
}
}
For the respective classes it will take appropriate action. Including sorting.
Сall a method to prepare the Сontainer (in this case) you before using.
For example, in the Business Logic (MVPVM).

Using AutoMapper to map the property of an object to a string

I have the following model:
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
}
I want to be able to use AutoMapper to map the Name property of the Tag type to a string property in one of my viewmodels.
I have created a custom resolver to try to handle this mapping, using the following code:
public class TagToStringResolver : ValueResolver<Tag, string>
{
protected override string ResolveCore(Tag source)
{
return source.Name ?? string.Empty;
}
}
I am mapping using the following code:
Mapper.CreateMap<Tag, String>()
.ForMember(d => d, o => o.ResolveUsing<TagToStringResolver>());
When I run the application I get the error:
Custom configuration for members is only supported for top-level
individual members on a type.
What am I doing wrong?
This is because you are trying to map to the actual destination type rather than a property of the destination type. You can achieve what you want with:
Mapper.CreateMap<Tag, string>().ConvertUsing(source => source.Name ?? string.Empty);
although it would be a lot simpler just to override ToString on the Tag class.
ForMember means you are providing mapping for a member where you want a mapping between type. Instead, use this :
Mapper.CreateMap<Tag, String>().ConvertUsing<TagToStringConverter>();
And Converter is
public class TagToStringConverter : ITypeConverter<Tag, String>
{
public string Convert(ResolutionContext context)
{
return (context.SourceValue as Tag).Name ?? string.Empty;
}
}

Auto mapping a IDictionary<string, myclass> with fluent nhibernate

I have a simple class that looks like this:
public class Item {
// some properties
public virtual IDictionary<string, Detail> Details { get; private set; }
}
and then I have a map that looks like this:
map.HasMany(x => x.Details).AsMap<string>("Name").AsIndexedCollection<string>("Name", c => c.GetIndexMapping()).Cascade.All().KeyColumn("Item_Id"))
with this map I get the following error and I don't know how to solve it?
The type or method has 2 generic parameter(s), but 1 generic argument(s) were provided. A generic argument must be provided for each generic parameter.
I found a workaround for this. Basically I'm preventing the automapper from attempting to map an IDictionary. It forces me to have to map it manually in an override but at least it works.
I'm using an AutomappingConfiguration derived from DefaultAutomappingConfiguration.
public override bool ShouldMap(Member member)
{
if ( member.PropertyType.IsGenericType )
{
if (member.PropertyType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>))
return false;
}
return base.ShouldMap(member);
}
And here's a couple of sample classes and the associated mapping that I'm using to make this happen:
public class ComponentA
{
public virtual string Name { get; set; }
}
public class EntityF : Entity
{
private IDictionary<string, ComponentA> _components = new Dictionary<string, ComponentA>();
public IDictionary<string, ComponentA> Components
{
get { return _components; }
set { _components = value; }
}
}
public class EntityFMap : IAutoMappingOverride<EntityF>
{
public void Override(AutoMapping<EntityF> mapping)
{
mapping.HasMany<ComponentA>(x => x.Components)
.AsMap<string>("IndexKey")
.KeyColumn("EntityF_Id")
.Table("EntityF_Components")
.Component(x =>
{
x.Map(c => c.Name);
})
.Cascade.AllDeleteOrphan();
}
}
I've just spent several hours to make this work, so I hope this saves someone else an evening of hair-pulling.

Categories