How to dynamically select the DbSet column name? - c#

I have the following DbContext and entity (.Net core 3.11 console program).
public partial class MyDbContext : DbContext
{
private readonly string _connectionString;
public MyDbContext(string connectionString) => _connectionString = connectionString;
public DbSet<MyEntity1> MyEntity1 { get; set; }
public DbSet<MyEntityX> MyEntityX { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.UseSqlServer(_connectionString);
}
public class MyEntity1 { .... }
public class MyEntityX { .... }
I want to create a generic function with two type parameters for the entity class and column data type, and a string parameter for column name. The function will return
List<TColumn> F<TEntity, TColumn>(string colName)
{
var list = dbContext.Set<TEntity>()
.Select(x => x."colName?") // need to dynamic select the value of column
.ToList();
return list;
}

In this link (https://www.strathweb.com/2018/01/easy-way-to-create-a-c-lambda-expression-from-a-string-with-roslyn/), you can see how to create lambda expression from string. In there, he uses an example in Where(), but you should be able to create a Select() expression such as
var selectString = "x => x.colName";
var options = ScriptOptions.Default.AddReferences(typeof(TEntity).Assembly);
Func<TEntity, bool> selectExpression = await CSharpScript.EvaluateAsync<Func<TEntity, bool>>(selectString, options);
var selectedData = dbContext.Set<TEntity>().Select(selectExpression);
NOTE: you must always remember to use options to AddReferences() to your type.

Related

How to add a custom code to EFCore that executes on server

Problem:
I'm using EFCore 3.1 ( a project recently migrated from an older version) where I have to retrieve records based on if they are soft deleted or not. Any soft deleted record is not considered for any operation performed on the table.
Soft deleted is implemented in a form of IsDeleted table column which is a bit. 1 = soft deleted, 0 = row is available. In the C#, there is an interface IActiveEntity with a bool property of IsDeleted
interface IActiveEntity
{
bool IsDeleted { get; set; }
}
Certain operation implemented in generic form and therefore they check if the entity is of type IActiveEntity. I read a series of articles. https://www.thinktecture.com/en/entity-framework-core/custom-functions-using-hasdbfunction-in-2-1/
But it's not working quite the way as mentioned in article. There is also not enough documentation available.
I implemented the function extensions for EF.Functions:
public static class FunctionsExtension
{
public static MethodInfo GetIsDeletedMethodInfo()
{
var methods = typeof(FunctionsExtension)
.GetMethods()
.Where(x=> x.Name == "IsDeleted" && !x.GetParameters().Any())
.ToList();
return methods.FirstOrDefault();
}
public static bool IsDeleted(this DbFunctions sys, object entity)
{
throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation.");
}
public static bool IsDeleted()
{
throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation.");
}
}
Then I created corresponding class to translate the expression into correct sql code.
public class IsDeletedExpression : SqlExpression
{
public IsDeletedExpression() : base(typeof(object), new BoolTypeMapping("MSSQLSERVER", DbType.Boolean))
{
}
protected override Expression Accept(ExpressionVisitor visitor)
{
if(visitor is IQuerySqlGeneratorFactory fac)
{
var gen = fac.Create();
gen.Visit(new SqlFragmentExpression("([DeletedOn] IS NULL)"));
return this;
}
return base.Accept(visitor);
}
public override bool CanReduce => false;
public override void Print(ExpressionPrinter printer) => printer.Print(this);
protected override Expression VisitChildren(ExpressionVisitor visitor)=> base.VisitChildren(visitor);
}
Then the method call translator for the IsDeleted expression:
public class IsDeletedTranslator : IMethodCallTranslator
{
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList<SqlExpression> arguments)
{
if(method.Name == "IsDeleted")
return new IsDeletedExpression();
return null;
}
}
A class to register the Method call translator
public sealed class IsDeletedMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin
{
public IEnumerable<IMethodCallTranslator> Translators =>
new List<IMethodCallTranslator> {new IsDeletedTranslator()};
}
After this these two classes to add to DBContext
public sealed class MyContextOptionsExtension : IDbContextOptionsExtension
{
public void ApplyServices(IServiceCollection services) => services.AddSingleton<IMethodCallTranslatorPlugin, IsDeletedMethodCallTranslatorPlugin>();
public void Validate(IDbContextOptions options){}
public DbContextOptionsExtensionInfo Info => new MyContextExtensionInfo(this);
}
public sealed class MyContextExtensionInfo : DbContextOptionsExtensionInfo
{
public MyContextExtensionInfo(IDbContextOptionsExtension extension) : base(extension){}
public override long GetServiceProviderHashCode() => 0;
public override bool IsDatabaseProvider => true;
public override string LogFragment => "([DeletedOn] IS NULL)";
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo){}
}
And this is how add it to DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
builder
.HasDbFunction(FunctionsExtension.GetIsDeletedMethodInfo())
.HasTranslation((list) => new IsDeletedExpression());
}
public static MyContext GetInstance(string cnn)
{
var optionsBuilder = new DbContextOptionsBuilder<MyContext>();
optionsBuilder.UseSqlServer(cnn, cb =>
{
});
var extension = optionsBuilder.Options.FindExtension<MyContextOptionsExtension>() ?? new MyContextOptionsExtension();
optionsBuilder.Options.WithExtension(extension);
var context = new MyContext(optionsBuilder.Options);
return context;
}
When I run the code it calls the Extension method IsDeleted and throws exception instead of performing any sort of translation. What am I missing so that EF Core start recognizing the call to be translated into SQL and not execute the local function.
Update:
This is how I use the code:
var maxAge = employees.Where(x => !EF.Functions.IsDeleted(x)).Max(x=> x.Age)
this is the error I get:
The LINQ expression
.Where(n => __Functions_0
.IsDeleted(n))
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

Map a property to list of object using AutoMapper CreateMap

First of all, sorry my English :)
I am using AutoMapper to map between classes.
I have structure of classes like the below.
public class OrderDto {
int Id { get; set; }
}
public class OrderDtoList {
OrderDto[] Orders { get; set; }
}
public class Order {
int Id { get; set; }
}
My issue is that, I want to map OrderDtoList object to List using AutoMapping Profile.
public class OrderMappingProfile : Profile {
public OrderMappingProfile() {
CreateMap<OrderDto, Order>();
CreateMap<OrderDtoList, List<Order>(); // <<<<<<<< I cannot figure out this.
}
}
But I cannot figure out how to write CreateMap<>() function.
Please help.
You can create a class which implements ITypeConverter< OrderDtoList, List > and create the mapping using ConvertUsing method.
public class OrderDtoListMapper : ITypeConverter<OrderDtoList, List<Order>>
{
public List<Order> Convert(OrderDtoList source, List<Order> destination, ResolutionContext context)
{
return context.Mapper.Map<List<Order>>(source.Orders);
}
}
Then, you can create your mapping like this:
public class MapperProfile : Profile
{
public MapperProfile()
{
CreateMap<OrderDto, Order>();
CreateMap<OrderDtoList, List<Order>>().ConvertUsing<OrderDtoListMapper>();
}
}
Hope this is what you were looking for!
It's a little difficult to get it from scratch, but it is possible. You have to create a ITypeConverter<,> and apply this to be used for the conversion.
Be aware, that the ConvertUsing() method has also an overload to simply add an inline function, but you need to have access to the mapper itself to call it for mapping the inner objects to the desired result objects and that's only available with the type converter interface.
public class OrderMappingProfile : Profile
{
public OrderMappingProfile()
{
CreateMap<OrderDto, Order>();
CreateMap<OrderDtoList, List<Order>>().ConvertUsing<CustomConverter>();
}
}
public class CustomConverter : ITypeConverter<OrderDtoList, List<Order>>
{
public List<Order> Convert(OrderDtoList source, List<Order> destination, ResolutionContext context)
{
return context.Mapper.Map<List<Order>>(source.Orders);
}
}
With this in place, you can create the desired list right from the DTO:
public static class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<OrderMappingProfile>());
var mapper = config.CreateMapper();
var orderList = new OrderDtoList { Orders = Enumerable.Range(1, 10).Select(i => new OrderDto { Id = i }).ToArray() };
var orders = mapper.Map<List<Order>>(orderList);
}
}
As Lucian mentioned, there is an overload of ConvertUsing() that provides the context. So you could also inline this, instead of using an own class:
// Use lambda method
CreateMap<OrderDtoList, List<Order>>()
.ConvertUsing((source, _, context) => context.Mapper.Map<List<Order>>(source.Orders));
// Use static method
CreateMap<OrderDtoList, List<Order>>().ConvertUsing(ListConverter);
private static List<Order> ListConverter(OrderDtoList source, List<Order> destination, ResolutionContext context)
{
return context.Mapper.Map<List<Order>>(source.Orders);
}
below is a generic extension, if you have not used an extension before please observe that the GenericExtensions class is a static class
public static class GenericExtensions {
public static object Map<T>(this T source)
{
var fullName = source.GetType().FullName;
var sourceType = source.GetType();
var baseType = ObjectContext.GetObjectType(source.GetType());
var config = new MapperConfiguration(cfg =>
cfg.CreateMap(sourceType, baseType));
var mapper = config.CreateMapper();
var entity = mapper.Map(source, sourceType, baseType);
return entity;
}
}
public static List<T> Map<T>(this List<T> original)
{
var config = new MapperConfiguration(cfg =>
cfg.CreateMap(typeof(T), typeof(T)));
var mapper = config.CreateMapper();
return original.Select(mapper.Map<T, T>).ToList();
}
Usage:
for single entity:
var originalObject = new Order();
originalObject.Id = 4;
var clonedObject = originalObject.Map();
for list of entities:
var objectList = db.ORDERS.ToList();
var clonedList = objectList.Map();
Hope this helps!

How to test database views using Entity Framework Core's in memory DB provider?

When I tried to add objects to views, it throws exception saying unable to track an instance of type because it is a query type. Is there a way to get around this?
Query Types are read-only by definition (for all database providers, not only for in memory):
Are never tracked for changes on the DbContext and therefore are never inserted, updated or deleted on the database.
However, additionally to their usual usage scenarios of
Mapping to database views.
Mapping to tables that do not have a primary key defined.
they allow
Mapping to queries defined in the model.
or in other words
May be mapped to a defining query - A defining query is a secondary query declared in the model that acts a data source for a query type.
which is achieved with ToQuery fluent API:
Configures a query used to provide data for a query type.
So for testing query types with in memory database, you should utilize the defining query mapping capability.
For instance, inside OnModelCreating override you could add something like this:
if (Database.IsInMemory())
{
// In memory test query type mappings
modelBuilder.Query<MyQueryType>().ToQuery(() => LINQ_query);
// ... similar for other query types
}
else
{
// Database query type mappings
modelBuilder.Query<MyQueryType>().ToView("MyQueryTypeView");
// ...
}
where LINQ_query is a normal LINQ query accessing context DbSets and DbQuerys and projecting to MyQueryType.
Then the test would feed the involved entities with data and the queries using DbQuerys will retrieve the data from the defining query.
The above should be the recommended way to test views with in memory database.
Just for completeness, it's possible to directly feed the DbQuerys with data (basically mocking them) by creating some sort of query repository, but with the following restriction - it must be shared (static), because currently EF Core does not handle correctly db context members (like global query filter does) other than DbSet<T> and DbQuery<T>.
Something like this:
public static class FakeQueryProvider
{
static Dictionary<Type, IQueryable> queries = new Dictionary<Type, IQueryable>();
public static void SetQuery<T>(IQueryable<T> query)
{
lock (queries)
queries[typeof(T)] = query;
}
public static IQueryable<T> GetQuery<T>()
{
lock (queries)
return queries.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder)
where T : class
{
return builder.ToQuery(() => GetQuery<T>());
}
}
then instead of
.ToQuery(() => LINQ_query);
you would use
.ToFakeQuery();
and would feed it inside the test like this
List<MyQueryType> data = ...;
FakeQueryProvider.SetQuery(data.AsQueryable());
Still I recommend the first approach due to shared storage limiting the ability to run MyQueryType related tests in parallel.
I ended up refactoring Ivan's extension class code based on his suggestions/recommendations, as follows. I added overloads to the ToFakeQuery method to take in a dictionary.
public static class InMemoryQueryProviderExtensions
{
static Dictionary<Type, IQueryable> queries = new Dictionary<Type, IQueryable>();
public static void SetQuery<T>(IQueryable<T> query)
{
lock (queries)
queries[typeof(T)] = query;
}
public static IQueryable<T> GetQuery<T>()
{
lock (queries)
return queries.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
private static IQueryable<T> GetQuery<T>(Dictionary<Type, IQueryable> queryDictionary)
{
return queryDictionary.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder)
where T : class
{
return builder.ToQuery(() => GetQuery<T>());
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder, Dictionary<Type, IQueryable> queryDictionary)
where T : class
{
return builder.ToQuery(() => GetQuery<T>(queryDictionary));
}
}
And then, creating a new derived class for my DBContext as follows. Basically, making the derived instance of the in-memory DBContext maintain the dictionary.
public class TlInMemoryDbContext : TlDbContext
{
public TlInMemoryDbContext(DbContextOptions<TlDbContext> options)
: base(options)
{ }
Dictionary<Type, IQueryable> queries = new Dictionary<Type, IQueryable>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Query<EffectiveTimeEntry>().ToFakeQuery(queries);
}
public void SetQuery<T>(IQueryable<T> query)
{
lock (queries)
queries[typeof(T)] = query;
}
}
I used the above as suggested by Ivan Stoev.
This is how it looked
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if(Database.IsSqlServer())
{
modelBuilder.Query<ProviderRating>(entity =>
{
entity.ToView("vGetProviderRatingData", "dbo");
entity.Property(e => e.col1)
.HasMaxLength(10)
.IsUnicode(false);
entity.Property(e => e.col2)
.HasMaxLength(60)
.IsUnicode(false);
entity.Property(e => e.col3)
.HasMaxLength(10)
.IsUnicode(false);
});
}
else
{
modelBuilder.Query<ProviderRating>().ToQuery(() =>
ProviderRatingFake.Select(m => new ProviderRating()
{
col1 = m.col1,
col2 = m.col2,
col3 = m.col3,
}
));
}
}
The ProviderRatingFake class exactly similar to ProviderRating class
I also added this code in DbContext file (ProviderQualityContext)
public virtual DbSet<ProviderRatingFake> ProviderRatingFake { get; set; }
public virtual DbQuery<ProviderRating> ProviderRating { get; set; }
Then I tested like this
[TestMethod]
public void TestingWithInMemoryDb()
{
var options = new DbContextOptionsBuilder<ProviderQualityContext>()
.UseInMemoryDatabase(databaseName: "Read_From_Database")
.Options;
var fakeProviderRating = new ProviderRatingFake
{
col1 = 1,
col2 = "Something",
col3 = "Something",
};
using (var context = new ProviderQualityContext(options))
{
context.ProviderRatingFake.Add(fakeProviderRating);
context.SaveChanges();
}
//use the newly created context and inject it into controller or repository
using (var context = new ProviderQualityContext(options))
{
//use the test context here and make assertions that you are returning the
//fake data
//Note that the actual code uses the Query like this
//This query will be populated with fake data using the else block
//in the method OnModelCreating
var returnedData = this.dbContext.Query<ProviderRating>().Where(m => m.col1 ==
"Something")
}
}
public static class InMemoryQueryProviderExtensions
{
static Dictionary<Type, IQueryable> queries = new Dictionary<Type, IQueryable>();
public static void SetQuery<T>(IQueryable<T> query)
{
lock (queries)
queries[typeof(T)] = query;
}
public static IQueryable<T> GetQuery<T>()
{
lock (queries)
return queries.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
private static IQueryable<T> GetQuery<T>(Dictionary<Type, IQueryable> queryDictionary)
{
return queryDictionary.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder)
where T : class
{
return builder.ToQuery(() => GetQuery<T>());
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder, Dictionary<Type, IQueryable> queryDictionary)
where T : class
{
return builder.ToQuery(() => GetQuery<T>(queryDictionary));
}
}

C# MVC4 noob - define interface with DbContext Entry method included

I am trying to learn C# coming from a classic ASP/VBScript background.
Up front (just in case someone can answer without all the following background info and code) - My DbContext interface doesn't allow me to do this:
_dbcontext.Entry(model).State = EntityState.Modified;
It balks at me trying to use the Entry method with the following error:
'MyNamespace.Models.IMyDataContext' does not contain a definition for 'Entry' and no extension method 'Entry' accepting a first argument of type 'MyNamespace.Models.IMyDataContext' could be found (are you missing a using directive or an assembly reference?)
How can I properly define my interface so that it will include the Entry method from the DbContext class?
BACKGROUND
I had someone who (supposedly) knows their stuff help me get the following code setup for connecting to MSSQL or MySQL based on data we retrieve from a common connection info table. The schema in MSSQL and MySQL is identical for the data model.
public interface IMyDataContext
{
DbSet<MyModel> ModelData { get; set; }
}
public class dbMySQL : DbContext, IMyDataContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var table = modelBuilder.Entity<MyModel>().ToTable("tablename");
table.HasKey(t => t.Id);
table.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
table.Property(t => t.Key);
table.Property(t => t.Value);
base.OnModelCreating(modelBuilder);
}
public dbMySQL(DbConnection existingConnection, boolcontextOwnsConnection) : base(existingConnection, contextOwnsConnection) { }
public DbSet<MyModel> ModelData { get; set; }
}
public class dbMSSQL : DbContext, IMyDataContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var table = modelBuilder.Entity<MyModel>().ToTable("tablename");
table.HasKey(t => t.Id);
table.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
table.Property(t => t.Key);
table.Property(t => t.Value);
base.OnModelCreating(modelBuilder);
}
public dbMSSQL(string connectionString) : base(connectionString) { }
public DbSet<MyModel> ModelData { get; set; }
}
Using the above code, I have been able to successfully grab connection info from a table and return a DbContext as follows:
private IMyDataContext selectDbProvider(int Id)
{
// Get database connection info
var connInfo = _db.ConnModel.Find(Id);
string dbProvider = connInfo.dbType.ToString();
IMyDataContext _dbd;
if (dbProvider == "MySql.Data.MySqlClient")
{
var connectionStringBuilder = new MySqlConnectionStringBuilder();
connectionStringBuilder.Server = connInfo.dbServer;
connectionStringBuilder.UserID = connInfo.dbUser;
connectionStringBuilder.Password = connInfo.dbPassword;
connectionStringBuilder.Database = connInfo.dbName;
connectionStringBuilder.Port = 3306;
_mysqlconn = new MySqlConnection(connectionStringBuilder.ConnectionString);
_dbd = new dbMySQL(_mysqlconn, false);
}
else
{
var connectionStringBuilder = new SqlConnectionStringBuilder();
connectionStringBuilder.DataSource = connInfo.dbServer;
connectionStringBuilder.UserID = connInfo.dbUser;
connectionStringBuilder.Password = connInfo.dbPassword;
connectionStringBuilder.InitialCatalog = connInfo.dbName;
_dbd = new dbMSSQL(connectionStringBuilder.ConnectionString);
}
return _dbd;
}
Using all of the above, I can successfully access data in either MySQL or MSSQL:
_dbd = selectDbProvider(Id);
model = _dbd.ModelData.ToList();
However, when I try to do an update operation, I get the error message I mentioned at the top. How can I properly define my interface so that it will include the Entry method from the DbContext class?
Add a method to your interface for it.
DbEntityEntry Entry(Object entity)
https://msdn.microsoft.com/en-us/library/gg696238(v=vs.113).aspx
EDIT:
public class dbMyContext : DbContext
{
//snip
public dbMyContext(DbConnection existingConnection, boolcontextOwnsConnection) : base(existingConnection, contextOwnsConnection) { }
public dbMyContext(string connectionString) : base(connectionString) { }
//snip
}
Adjust your selectDbProvider class to use dbMyContext instead of dbMySQL and dbMSSQL.
Now you're using an O/RM properly. :)

C# Automapper How to resolve using property from customresolver

I am using the following mapping to map my data object to viewmodel object.
ObjectMapper.cs
public static class ObjectMapper
{
public static void Configure()
{
Mapper.CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Title,
opt => opt.ResolveUsing<TitleValueResolver>())
.ForMember(dest => dest.Name,
opt => opt.ResolveUsing<NameValueResolver >())
.ForMember(dest => dest.ShortName,
opt => opt.ResolveUsing<ShortNameValueResolver >());
}
}
Parser
public class Parser{
public string GetTitle(string title){
/* add some logic */
return title;
}
public string GetName(string title){
/* add some logic */
return name;
}
public string GetShortName(string title){
/* add some logic */
return shortname;
}
}
AutoMapperCustomResolvers.cs
public class TitleValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public TitleValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetTitle(source.TITLE);
}
}
public class NameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public NameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetName(source.TITLE);
}
}
public class ShortNameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public ShortNameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetShortName(source.TITLE);
}
}
I am using the above code to add logic to the destination property using the separate custom value resolvers. Not sure is this the right approach.
i) Is there a better way to achieve this?
ii) And how to use unity to resolve in case i want to inject some dependency to custom resolver constructor?
Thanks
As I understand your question, you want to utilize a ValueResolver, that resolves multiple source properties into an intermediate data object, which is used to resolve multiple target properties. As an example, I assume the following source, target, intermediate and resolver types:
// source
class User
{
public string UserTitle { get; set; }
}
// target
class UserViewModel
{
public string VM_Title { get; set; }
public string VM_OtherValue { get; set; }
}
// intermediate from ValueResolver
class UserTitleParserResult
{
public string TransferTitle { get; set; }
}
class TypeValueResolver : ValueResolver<User, UserTitleParserResult>
{
protected override UserTitleParserResult ResolveCore(User source)
{
return new UserTitleParserResult { TransferTitle = source.UserTitle };
}
}
You need a target property in order to utilize opt.ResolveUsing<TypeValueResolver>(). This means, you can establish a mapping, where an appropriate target property is available.
So, for the moment, lets wrap the result into an appropriate container type:
class Container<TType>
{
public TType Value { get; set; }
}
And create a mapping
Mapper.CreateMap<User, Container<UserViewModel>>()
.ForMember(d => d.Value, c => c.ResolveUsing<TypeValueResolver>());
And another mapping
Mapper.CreateMap<UserTitleParserResult, UserViewModel>()
.ForMember(d => d.VM_Title, c => c.MapFrom(s => s.TransferTitle))
.ForMember(d => d.VM_OtherValue, c => c.Ignore());
And another mapping
Mapper.CreateMap<User, UserViewModel>()
.BeforeMap((s, d) =>
{
Mapper.Map<User, Container<UserViewModel>>(s, new Container<UserViewModel> { Value = d });
})
.ForAllMembers(c => c.Ignore());
// establish more rules for properties...
The last mapping is a bit special, since it relies on a nested mapping in order to update the destination with values from source via separately configured mapping rules. You can have multiple different transfer mappings for different properties by adding appropriate intermediate mappings and calls in BeforeMap of the actual mapped type. The properties that are handled in other mappings need to be ignored, since AutoMapper doesn't know about the mapping in BeforeMap
Small usage example:
var user = new User() { UserTitle = "User 1" };
// create by mapping
UserViewModel vm1 = Mapper.Map<UserViewModel>(user);
UserViewModel vm2 = new UserViewModel() { VM_Title = "Title 2", VM_OtherValue = "Value 2" };
// map source properties into existing target
Mapper.Map(user, vm2);
Dunno if this helps you. There might be better ways if you rephrase your question to describe your initial problem instead of what you suspect to be a solution.

Categories

Resources