Automapper configuration setup - entity-framework

I am using entity framework for my DAL and want to convert entities objects to business objects and vice versa. This is taking place in my BLL project. I am hoping to setup automapper in my BLL project to take... let say Customer.cs auto generated by EF and convert it to CustomerWithDifferentDetail.cs (my business obj)
I attempted to create an AutoMapperBLLConfig.cs under BLL project with the following code:
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile(new CustomerProfile());
});
}
public class CustomerProfile : Profile
{
protected override void Configure()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>();
cfg.CreateMap<CustomerWithDifferentDetail, Customer>();
});
}
}
Then I created CustermerService.cs under BLL project with the following code to test if it's working:
public void CustomerToCustomerWithDifferentDetail()
{
AutoMapperBLLConfiguration.Configure();
Customer source = new Customer
{
Account = 1234,
Purchase_Quantity = 100,
Date = "05/05/2016",
Total = 500
};
Models.CustomerWithDifferentDetail testCustomerDTO = Mapper.Map<Customer, Models.CustomerWithDifferentDetail>(source)
}
I get this Error:
Missing type map configuration or unsupported mapping.
I am not sure what I did wrong. I don't have a start_up or global.aspx. This is a class library. I'm not sure what I'm missing or did wrong.
I have a separate project calls Models which hold all the business objects including CustomerWithDifferentDetail.cs. In this case, CustomerWithDifferentDetail only has two properties: Account and Total. If mapped, it should give me Account = 1234 and Total = 500 - basically the same data as entity object just in different shape.
======================= UPDATE=================================
AutoMapperBLLConfig.cs - stay the same as noted above
CustomerProfile.cs
public class CustomerProfile : Profile
{
protected override void Configure()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>().ReverseMap(); //cut it down to one line with ReverseMap
});
}
CreateMap<Customer, CustomerWithDifferentDetail>().ReverseMap(); //missed this one line before; hence, the error
}
CustomerService.cs
static CustomerService()
{
AutoMapperBLLConfiguration.Configure(); //per #Erik Philips suggestion, move this call to a static constructor
}
public void CustomerToCustomerWithDifferentDetail()
{
Customer source = new Customer
{
Account = 1234,
Purchase_Quantity = 100,
Date = "05/05/2016",
Total = 500
};
Models.CustomerWithDifferentDetail testCustomerDTO = Mapper.Map<Customer, Models.CustomerWithDifferentDetail>(source);
}
Result: my testCustomerDTO returns exactly what I expected.

Since you are using the instance method of AutoMapper:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>();
cfg.CreateMap<CustomerWithDifferentDetail, Customer>();
});
Then you need to use the instance for mapping:
Models.CustomerWithDifferentDetail testCustomerDTO =
config.Map<Customer, Models.CustomerWithDifferentDetail>(source)
I personally haven't really thought this through in my applications (I need to move to the instance method instead of the static method). (Migrating from status API).
Off the cuff, based on your code, I'd probably do something like:
public class PersonDataObject
{
public string Name { get; set; }
}
public class PersonBusinessObject
{
private readonly MapperConfiguration _mapper;
public string Name { get; set; }
PersonBusinessObject()
{
_mapper = new MapperConfiguration(cfg =>
{
cfg.CreateMap<PersonDataObject,PersonBusinessObject>();
});
}
public static PersonBusinessObject MapFrom(PersonDataObject data)
{
return _mapper.Map<PersonBusinessObject>(data);
}
}
Then you can simply:
PersonDataObject data = new PersonDataObject();
PersonBusinessObject business = PersonBusinessObject.MapFrom(data);

Related

EF Core 6 : AutoInclude(false) still loads Navigation

READ THE EDIT!
I have two Entities :
public class Principal {
public Guid Id { get; private set; }
public Collection<Dependant> Dependants { get; init; } = new();
public Principal() { }
}
public class Dependant{
public Guid Id { get; private set; }
public Guid PrincpalId { get; private set; }
public Principal Principal{ get; private set; }
public Dependant() { }
}
I access Principal through a repository :
internal class PrincipalsRepository {
private readonly DbSet<Princpal> db;
public PrincipalsRepository (DbSet<Princpal> db) {
this.db = db;
}
public async Task AddAsync(Principal p) {
await this.db.AddAsync(p).ConfigureAwait(false);
}
public async Task<Principal>> GetByIdAsync(Guid id) {
//Notice how there's no Include here!
return await db
.FirstOrDefaultAsync(p => p.Id == id)
.ConfigureAwait(false);
}
}
I configure them like this :
public void Configure(EntityTypeBuilder<Principal > builder) {
builder
.ToTable("Principals")
.HasKey(p => p.Id);
builder
.Navigation(p => p.Dependants)
.AutoInclude(false); //THIS!!!!!
builder
.OwnsMany(p =>
p.Dependants,
navBuilder => {
navBuilder.ToTable("Dependants");
navBuilder.Property<Guid>("Id"); //Important: without this EF would try to use 'int'
navBuilder.HasKey("Id");
navBuilder
.WithOwner(v => v.Principal)
.HasForeignKey(v => v.PrincipalId);
}
);
}
The repo is used in a DbContext:
//PLEASE NOTE: This code might seem a bit broken to you because it's a trimmed down copy-paste from the real code.
public abstract class MyDatabase<TContext> : DbContext
where TContext : DbContext {
public PrincipalsRepository PrincipalsRepository = new PrincipalsRepository (DbPrincipals);
//This is exposed for unit tests
public DbSet<Principal> DbPrincipals { get; set; }
public MyDatabase(DbContextOptions<TContext> options)
: base(options) {
}
}
I configure an in-memory Db :
//PLEASE NOTE: Not everything is detailed here. It's a copy paste from a bigger code base)
private static Database CreateDatabase() {
var _connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
_contextOptions = new DbContextOptionsBuilder<MyDatabase>()
.UseSqlite(_connection)
.Options;
var context = new MyDatabase(_contextOptions);
return context;
}
I run a unit test where I insert an Principal entity with a Dependant:
// Step 1 : Init
using var context = CreateDatabase();
var repo = new PrincipalsRepository(context.DbPrincipals);
// Step 2 : Insertion
var p = new Principal();
p.Dependants.Add(new Dependant());
await context.PrincipalsRepo.AddAsync(p).ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false);
// Step 3 : Read back
var p2 = context.PrincipalsRepo.GetByIdAsync(p.Id).ConfigureAwait(false);
And then...
Assert.Empty(p2!.Dependants); //The unit test fails because I can see that the Dependant has been loaded
What am I doing wrong? Why is it loaded despite me saying "AutoInclude(false)" ?
Note: After adding AutoInclude(false), creating a new migration changed the Db's model snapshot, but the migration itself was empty. Is that normal???
EDIT:
Like #DavidG and #Gert Arnold suggested in the comments, apparently I need to instantiate a brand new DbContext to do the test, because EF is somehow smart enough to pick up that p2 is the "same" as p, and... populates its navigation links (i.e. does the auto Include) without me asking?!?
I absolutely don't understand what's the logic here (in terms of behaviour consistency).
When I change the test and query p2 from a brand new DbContext instance, it works as I would expect it. I.e. it does find the Principal (p2) but its Dependants collection is empty.
Is this documented anywhere, in one form or another? Even as an implicit sentence that seems obvious on some Microsoft help page?

Is it possible to access a shared TPH column in EF Core without using intermediate classes?

When using shared columns in an EF Core TPH setup, is it possible to access the shared column during projection?
class Program
{
public static readonly ILoggerFactory MyLoggerFactory
= LoggerFactory.Create(builder => {
builder.AddConsole();
});
static async Task Main(string[] args)
{
using (var context = new ClientContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var actions = await context.Actions
.Select(a => new
{
Id = a.Id,
// this works - but really messy and complex in real world code
Message = (a as ActionA).Message ?? (a as ActionB).Message,
// this throws "Either the query source is not an entity type, or the specified property does not exist on the entity type."
// is there any other way to access the shared column Message?
// Message = EF.Property<string>(a, "Message"),
})
.ToListAsync();
actions.ForEach(a => Console.WriteLine(a.Id + a.Message));
}
}
public class ActionBase
{
public int Id { get; set; }
// ... other shared properties
}
public class ActionA : ActionBase
{
// shared with B
[Required]
[Column("Message")]
public string Message { get; set; }
// ... other specific properties
}
public class ActionB : ActionBase
{
// shared with A
[Required]
[Column("Message")]
public string Message { get; set; }
// ... other specific properties
}
public class ActionC : ActionBase
{
public string SomethingElse { get; set; }
// ... other specific properties
}
class ClientContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// TO USE SQL
//optionsBuilder
// .UseLoggerFactory(MyLoggerFactory)
// .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TPHSharedColumn;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30")
// .EnableSensitiveDataLogging(false);
// TO USE INMEMORY
optionsBuilder
.UseLoggerFactory(MyLoggerFactory)
.UseInMemoryDatabase(Guid.NewGuid().ToString());
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ActionA>().HasData(new ActionA()
{
Id = 1,
Message = "A"
});
builder.Entity<ActionB>().HasData(new ActionB()
{
Id = 2,
Message = "B"
});
builder.Entity<ActionC>().HasData(new ActionC()
{
Id = 3,
SomethingElse = "C"
});
}
public DbSet<ActionBase> Actions { get; set; }
}
}
In this simple example, it would of course be possible to move Message to the base class - but that would make it possible to accidentally add an ActionC with a Message since I would need to remove the Required attribute.
I also know I could add a ActionWithRequiredMessage intermediate class to inherit ActionA and ActionB with, but again - in the much more complex real world example this is not feasible since there are also other shared columns and C# does not allow inheriting from multiple classes - and EF Core does not seem to like to use interfaces for this.
I simply would like to find a way to directly access the shared column - and use it in a projection.
Anyone know if this is possible?
I can't find it documented, but in EF Core 5.x you can access the shared column using any of the derived entities having a property mapped to it, e.g. all these work
Message = (a as ActionA).Message,
Message = (a as ActionB).Message,
Message = ((ActionA)a).Message,
Message = ((ActionB)a).Message,

ASP MVC EF6 Multi Tenant based on host

Sorry, another multi tenancy post. I can't find a good solution to site, I have read tons of great posts on multi tenancy for ASP MVC but I still need some good advice.
I have an ASP MVC Entity Framework 6 Code First web application. This app has to work for many different clients using a single database for all of them.
I have an entity for all the clients, and each client can have different hosts.
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
...
public ICollection<ClientHost> Hosts { get; set; }
}
public class ClientHost
{
public int ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
}
I have added a column "ClientId" to all the entities I need to filter, so I can separate data from different clients.
public class SomeEntity
{
public int Id { get; set; }
...
public int ClientId { get; set; }
}
First thing I need is, base on the host, retrieve the ClientId to work with.
private static int GetClientId()
{
var currentClient = Convert.ToInt32(HttpRuntime.Cache[CacheClient]);
if (currentClient != null) return currentClient;
lock (Synclock)
{
using (var dataContext = new MyDataContext())
{
var urlHost = HttpContext.Current.Request.Url.Host;
currentClient = dataContext.Clients
.FirstOrDefault(p => p.Hosts.Any(h => h.Name == urlHost));
if (currentClient == null) return null;
HttpRuntime.Cache.Insert(CacheClient, currentClient, null, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(0), CacheItemPriority.Default, null);
return currentClient;
}
}
}
QUESTION 1
As you see I get the clientId from DB and store it in cache, so I don't have to call DB every time I need it.
I don't know if there is a better approach to get the client Id or, better, to store it.
EDIT
After investigation I have created a variable in DbCOntext and initialize it in the Startup.cs file.
public class MyDataContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public static string ClientId { get; set; }
public MyDataContext() : base("MyDataBase") { }
public static MyDataContext Create()
{
return new myDataContext();
}
....
}
In Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
MyDataContext.ClientId = ClientConfiguration.GetCurrentClientId();
ConfigureAuth(app);
}
}
QUESTION 2
Once I have the ClientId, I need to add a filter to every query that needs it. Doing this manually can take you to make many errors or forget to do it in some places.
I need a way that the application can add the filter to all queries automatically (only those entities that need it), so I don't have to worry about a client getting other client's data. Also I need to add the ClientId to all the Insert and Update commands.
I have read about filtering and/or use EF Interceptors, but after reading some posts about that I can't figure out how to do it. Need some help here.
Thanks in advance.
EDIT
In order to solve QUESTION 2 I have followed this great post by Xabikos:
http://xabikos.com/2014/11/17/Create-a-multitenant-application-with-Entity-Framework-Code-First-Part-1/
I have changed it a little bit, since I don't use Users to get the current tenant and instead I use the host. This is part of the program I don't know yet how I'm going to solve but, assuming I already have the ClientId I can add filters to all the queries without realizing that is happening:
I have replaced all the user logic:
private static void SetTenantParameterValue(DbCommand command)
{
if (MyDataContext.ClientId == 0) return;
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = MyDataContext.ClientId;
}
}
Same in all the places...
Than I only have to mark the entities that have to filter with TenantAware, indicating the property. In this case I do in my base class and then apply that base class to all the entities I need.
[TenantAware("ClientId")]
public abstract class ClientEntity : Entity, IClientEntity
{
public int ClientId { get; set; }
public Client Client { get; set; }
}
Here are a couple of things I have done in the past that might help.
Question 1:
I am not a big fan of session as the web is supposed to be stateless. However, it is sometimes necessary. Your approach is reasonable. You could also use cookies as well. What I use are Json Web Tokens (JWT) via my authentication provider (Auth0.com). For each request as it is authenticated, I look for this client id. Here is an example. This is MVC 6 as well. You could do the same type of things w/ cookies.
public class Auth0ClaimsTransformer : IClaimsTransformer
{
private string _accountId = AdminClaimType.AccountId.DefaultValue;
private string _clientId = AdminClaimType.ClientId.DefaultValue;
private string _isActive = AdminClaimType.IsActive.DefaultValue;
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
foreach (var claim in context.Principal.Claims)
{
switch (claim.Type)
{
case "accountId":
_accountId = claim.Value ?? _accountId;
break;
case "clientId":
_clientId = claim.Value ?? _clientId;
break;
case "isActive":
_isActive = claim.Value ?? _isActive;
break;
}
}
((ClaimsIdentity)context.Principal.Identity)
.AddClaims(new Claim[]
{
new Claim(AdminClaimType.AccountId.DisplayName, _accountId),
new Claim(AdminClaimType.ClientId.DisplayName, _clientId),
new Claim(AdminClaimType.IsActive.DisplayName, _isActive)
});
return Task.FromResult(context.Principal);
}
Then in my Startup.cs Configure method I plug in my claims transformer.
app.UseJwtBearerAuthentication(options);
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new Auth0ClaimsTransformer()
});
Next I use a base authentication controller that parses out my claims into properties I can use in my controller.
[Authorize]
[Route("api/admin/[controller]")]
public class BaseAdminController : Controller
{
private long _accountId;
private long _clientId;
private bool _isActive;
protected long AccountId
{
get
{
var claim = GetClaim(AdminClaimType.AccountId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _accountId);
return _accountId;
}
}
public long ClientId
{
get
{
var claim = GetClaim(AdminClaimType.ClientId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _clientId);
return _clientId;
}
}
public bool IsActive
{
get
{
var claim = GetClaim(AdminClaimType.IsActive);
if (claim == null)
return false;
bool.TryParse(claim.Value, out _isActive);
return _isActive;
}
}
public string Auth0UserId
{
get
{
var claim = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
return claim == null ? string.Empty : claim.Value;
}
}
private Claim GetClaim(AdminClaimType claim)
{
return User.Claims.FirstOrDefault(x => x.Type == claim.DisplayName);
}
Finally in my controller it is trivial to extract which tenant is making the call. e.g.
public FooController : BaseController
{
public async Task<IActionResult> Get(int id)
{
var foo = await _fooService.GetMultiTenantFoo(ClientId, id);
return Ok(foo);
}
}
Question 2:
One of the ways I have used in the past is create a BaseMultiTenant class.
public class BaseMultiTenant
{
public int ClientId {get;set;}
public virtual Client Client {get;set;}//if you are using EF
}
public class ClientHost : BaseMultiTenant
{
public string Name {get;set;}
//etc
}
Then simply create an extension method for multi-tenant based entities. I know this doesn't "do it automatically" but it is an easy way to ensure each multi-tenant entity is being called only by its owner.
public static IQueryable<T> WhereMultiTenant<T>(this IQueryable<T> entity, int clientId, Expression<Func<T, bool>> predicate)
where T : BaseMultiTenant
{
return entity.Where(x => x.ClientId == clientId)
.Where(predicate);
}
Then when someone calls for their resource you can:
var clientHost = _myContext.ClientHosts
.WhereMultiTenant(ClientId,
x => x.Name == "foo")
.FirstOrDefault();
Hope this is helpful.
Also found a similar example using an interface.

SeedData Location in Entity Framework 6

I'm having trouble understanding the rules around when SeedData is called in EF Code First. Specifically, does it have anything to do with migrations? Migrations has it's own place to seed that seems different from the datacontext. I'm showing two examples below and I'm not sure which one is correct to use.
My Main Question: If I have a Migration, does the seeddata method not get called in my MultiTenantContext class? I have examples where it does and it does not and I can't figure out why.
In Migrations configuration.cs:
namespace WebApp.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<WebApp.Models.MultiTenantContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
ContextKey = "WebApp.Models.MultiTenantContext";
}
protected override void Seed(WebApp.Models.MultiTenantContext context)
{
var tenants = new List<Tenant>
{...};
tenants.ForEach(a => context.Tenants.Add(a));
context.SaveChanges();
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
}
And in context class directly:
[DbConfigurationType(typeof(DataConfiguration))]
public class MultiTenantContext : DbContext
{
public DbSet<Tenant> Tenants { get; set; }
}
public class DataConfiguration : DbConfiguration
{
public DataConfiguration()
{
SetDatabaseInitializer(new MultiTenantContextInitializer());
}
}
public class MultiTenantContextInitializer :
CreateDatabaseIfNotExists<MultiTenantContext>
{
protected override void Seed(MultiTenantContext context)
{
var tenants = new List<Tenant>
{...}
tenants.ForEach(a => context.Tenants.Add(a));
context.SaveChanges();
}
}

Entity Framework 5 - Immediately refresh DbContext after saving changes

I have an MVC application that uses Entity Framework 5. In few places I have a code that creates or updates the entities and then have to perform some kind of operations on the updated data. Some of those operations require accessing navigation properties and I can't get them to refresh.
Here's the example (simplified code that I have)
Models
class User : Model
{
public Guid Id { get; set; }
public string Name { get; set; }
}
class Car : Model
{
public Guid Id { get; set; }
public Guid DriverId { get; set; }
public virtual User Driver { get; set; }
[NotMapped]
public string DriverName
{
get { return this.Driver.Name; }
}
}
Controller
public CarController
{
public Create()
{
return this.View();
}
[HttpPost]
public Create(Car car)
{
if (this.ModelState.IsValid)
{
this.Context.Cars.Create(booking);
this.Context.SaveChanges();
// here I need to access some of the resolved nav properties
var test = booking.DriverName;
}
// error handling (I'm removing it in the example as it's not important)
}
}
The example above is for the Create method but I also have the same problem with Update method which is very similar it just takes the object from the context in GET action and stores it using Update method in POST action.
public virtual void Create(TObject obj)
{
return this.DbSet.Add(obj);
}
public virtual void Update(TObject obj)
{
var currentEntry = this.DbSet.Find(obj.Id);
this.Context.Entry(currentEntry).CurrentValues.SetValues(obj);
currentEntry.LastModifiedDate = DateTime.Now;
}
Now I've tried several different approaches that I googled or found on stack but nothing seems to be working for me.
In my latest attempt I've tried forcing a reload after calling SaveChanges method and requerying the data from the database. Here's what I've done.
I've ovewrite the SaveChanges method to refresh object context immediately after save
public int SaveChanges()
{
var rowsNumber = this.Context.SaveChanges();
var objectContext = ((IObjectContextAdapter)this.Context).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, this.Context.Bookings);
return rowsNumber;
}
I've tried getting the updated object data by adding this line of code immediately after SaveChanges call in my HTTP Create and Update actions:
car = this.Context.Cars.Find(car.Id);
Unfortunately the navigation property is still null. How can I properly refresh the DbContext immediately after modifying the data?
EDIT
I forgot to originally mention that I know a workaround but it's ugly and I don't like it. Whenever I use navigation property I can check if it's null and if it is I can manually create new DbContext and update the data. But I'd really like to avoid hacks like this.
class Car : Model
{
[NotMapped]
public string DriverName
{
get
{
if (this.Driver == null)
{
using (var context = new DbContext())
{
this.Driver = this.context.Users.Find(this.DriverId);
}
}
return this.Driver.Name;
}
}
}
The problem is probably due to the fact that the item you are adding to the context is not a proxy with all of the necessary components for lazy loading. Even after calling SaveChanges() the item will not be converted into a proxied instance.
I suggest you try using the DbSet.Create() method and copy across all the values from the entity that you receive over the wire:
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
this.Context.Entry(newEntry).CurrentValues.SetValues(obj);
return newEntry;
}
UPDATE
If SetValues() is giving an issue then I suggest you try automapper to transfer the data from the passed in entity to the created proxy before Adding the new proxy instance to the DbSet. Something like this:
private bool mapCreated = false;
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
if (!mapCreated)
{
Mapper.CreateMap(obj.GetType(), newEntry.GetType());
mapCreated = true;
}
newEntry = Mapper.Map(obj, newEntry);
this.DbSet.Add(newEntry;
return newEntry;
}
I use next workaround: detach entity and load again
public T Reload<T>(T entity) where T : class, IEntityId
{
((IObjectContextAdapter)_dbContext).ObjectContext.Detach(entity);
return _dbContext.Set<T>().FirstOrDefault(x => x.Id == entity.Id);
}