Making AutoMapper reuse previously mapped instances - c#

I would like AutoMapper to map same object instances Source to the same instances of Target.
I'm sure that AutoMapper can be configured to do this, but how? I need to convert an object graph A to B, keeping the references between the mapped objects.
For example, this fails:
[Fact]
public void Same_instances_are_mapped_to_same_instances()
{
var configuration = new MapperConfiguration(config => config.CreateMap<Source, Target>());
var mapper = configuration.CreateMapper();
var source = new Source();
var list = mapper.Map<IEnumerable<Target>>(new[] { source, source, source });
list.Distinct().Should().HaveCount(1);
}
public class Source
{
}
public class Target
{
}
It would like to make this test pass.
Also, please notice that the mapper should "remember" instances mapped during the current Map call.

That's the nearest you can get:
public static class Program
{
public static void Main()
{
var config = new MapperConfiguration(conf => conf.CreateMap<Source, Target>().PreserveReferences());
var mapper = config.CreateMapper();
var source = new Source();
var list = new[] { source, source, source };
var firstRun = mapper.Map<IEnumerable<Target>>(list);
var secondRun = mapper.Map<IEnumerable<Target>>(list);
// Returns two items
var diffs = firstRun.Concat(secondRun).Distinct();
foreach (var item in diffs)
{
Console.WriteLine(item.Id);
}
}
}
public class Source
{
public Guid Id { get; } = Guid.NewGuid();
}
public class Target
{
public string Id { get; set; }
}
This means, that you only get the same item back in case of each call to mapper.Map(), but if you call the mapper multiple times, you'll get back new items for each call (which makes sense, otherwise the mapper had to hold references to all given and created instances over it's whole lifetime, which could lead to some serious memory problems).

Related

Create Intersect of HashSet and remove Items from class structure with extracted ID's

lets assume I have the following classes:
public class ServiceStatistics
{
public string LocalId { get; set; }
public string OrganizationId { get; set; }
public List<StatisticElements> Elements { get; } = new List<StatisticElements>();
}
public class StatisticElements
{
public string StatisticId { get; set; }
public string Type { get; set; }
public string ServiceName { get; set; }
}
I retrieve such ServiceStatistics by a soap service and I use serialization/deserialization.
Each ServiceStatistics contains a set of StatisticElements. I also have a static list of StatisticElements-ID's which are relevant for calculation. All other incoming StatisticElements-ID's can be dropped. I need to do this on my side
because the SOAP Service does not support selecting specific StatisticElements-ID's
So I have generated a static Class with a HashSet:
public static class RelevantDutyPlans
{
private static HashSet<int> relevantDutyPlans;
static RelevantDutyPlans()
{
// only a subset of the original ID's
relevantDutyPlans = new HashSet<int>()
{
530,
1150,
1095,
};
}
public static HashSet<int> GetRelevantDutyPlans()
{
return relevantDutyPlans;
}
public static bool Contains(int planId)
{
return relevantDutyPlans.Contains(planId);
}
// Extracts all DutyPlans which are relevant (HashSet) for validation from
// the incoming data
public static List<int> ExtractRelevantDutyPlans(List<int> planIds)
{
var relevantPlans = new HashSet<int>(planIds);
relevantPlans.IntersectWith(relevantDutyPlans);
return relevantDutyPlans.ToList();
}
}
So my thought was, to create an Intersect like this:
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
List<int> incomingIds = new List<int>();
foreach(var item in statistics)
{
foreach(var element in item.Statistic)
{
incomingIds.Add(int.Parse(element.StatisticId));
}
}
List<int> extract = RelevantDutyPlans.ExtractRelevantDutyPlans(incomingIds);
So now I have a List of ID's which are relevant for further processing. What I want to achieve is to remove all class elements "StatisticElements" with "StatisticId" not contained in the the extract list generated above.
Any ideas?
Any help is very appreciated
How about a little bit different approach. Simply remove irrelevant plans right away!
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
foreach(var item in statistics)
{
item.Elements.RemoveAll(x => !RelevantDutyPlans.Contains(int.Parse(x.StatisticId)));
}
Now you are only left with the relevant once.
Hope you can use selectMany to flatten the collection and proceed the filter.
var filteredItems = statistics.SelectMany(s => s.Elements)
.Where(s => extract.Contains(Convert.ToInt32(s.StatisticId)))
.ToList();
You could also use LINQ to create a new List<> if you need to keep the original statistcs intact - e.g. if you might run multiple plans against it.
var relevantStatistics = statistics.Select(s => new {
LocalId = s.LocalId,
OrganizationId = s.OrganizationId,
Elements = s.Elements.Where(e => !RelevantDutyPlans.Contains(Convert.ToInt32(e.StatisticId))).ToList()
});
Since ServiceStatistics doesn't provide for construction, I return an anonymous object instead, but you could create an appropriate DTO class.

Automatically generating a UI based on the Class that has been passed

this might be a very basic question but I am cracking my head at this since months.
I want to create a simple UI for creating objects of the class I pass into the UI constructor.
Let say I have 2 classes:
class Test1
{
public static List<Test1> objectList;
public string code;
public string name;
}
class Test2
{
public static List<Test2> objectList;
public string code;
public string name;
public int value;
}
(the static classes would contain all the objects created out of that class)
what I would like to do is to is to create a Code which takes a class as a variable (maybe a generic class?) and based on that creates all the labels and textboxes based on the fields available in the class.
e.g.
public RegisterUI<T> ()
{
Grid grd = new Grid();
DataGrid dg = new DataGrid();
Button saveBtn = new Button();
//Binding the static list of objects to the DataGrid
dg.ItemSource = T.objectList;
//Adding the UI elemnts to the grid
grd.children.add(dg);
grd.children.add(saveBtn);
//Creating for each field in the Class T a lable based on its name and a combobox + binding
foreach(field in T)
{
Lable lbl = new Lable(field.getName);
ComboBox cbx = new ComboBox();
grd.children.add(lbl);
grd.children.add(cbx);
}
}
Is this even possible? I hope I was not to vague with the mockup code, and you can understand what I am heading for.
Any advice would be highly appreciated. Thanks a lot :)
Yes, it's possible. I've done this exact thing for the purpose of automatically creating settings dialogs (I got tired of making a custom form anytime one of my programs had settings that needed modified by the user).
How?
You're going to need to look into "reflection" which provides you a way to interrogate the structure of objects dynamically.
I don't use generics for this, but rather interrogate the Type from within the class.
If I pass in Test1 into my class I get this:
I wish I could supply source code, but alas, it belongs to my employer. I can, however, supply a short snippet to get you started:
Type type = typeof(Test1);
//Get public fields
List<FieldInfo> fieldInfo = type.GetFields().ToList();
//Get private fields. Ensure they are not a backing field.
IEnumerable<FieldInfo> privateFieldInfo = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(f => !f.Name.Contains("k__BackingField"));
//Get public properties
List<PropertyInfo> properties = type.GetProperties().ToList();
//Get private properties
properties.AddRange(type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance));
Hmm, looks like something an old demo might help solve:
Note: I'm using JSONs and NewtonSoft's JSON library for my implementation to read a JSON and build the object / UI from that:
private void LoadConfig()
{
JsonSerializerSettings jss = new JsonSerializerSettings()
{
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate
};
var cfg = ConfigIO.OpenDefault();
ConfigItem ci = JsonConvert.DeserializeObject<ConfigItem>(cfg.Object);
IEnumerable<MemberInfo> atts = ConfigInterOps.GetAttributes(typeof(ConfigItem));
FillForm(ci, atts);
}
private void FillForm(Object referenceObject, IEnumerable<MemberInfo> atts)
{
int location = 5;
foreach (var att in atts)
{
var cfg = new ConfigurationBox(att.Name, referenceObject.GetType()
.GetProperty(att.Name).GetValue(referenceObject, null));
cfg.Name = $"cfg_ {att.Name}";
cfg.Top = 3 * location;
location += 10;
Controls["flowLayoutPanel1"].Controls.Add(cfg);
}
}
A couple classes I made and use that are referenced above:
public static class ConfigInterOps
{
public static IEnumerable<MemberInfo> GetAttributes(Type type)
{
return type.GetMembers()
.Where(x => x.MemberType == MemberTypes.Property ||
x.MemberType == MemberTypes.Field);
}
}
public static class ConfigIO
{
public static void Save(Config cfg)
{
UseDefaultLocation(cfg);
if (!File.Exists(cfg.FileLocation))
{
File.Create(cfg.FileLocation);
}
File.WriteAllText(cfg.FileLocation, JsonConvert.SerializeObject(cfg));
}
private static void UseDefaultLocation(Config cfg)
{
cfg.FileLocation = cfg.FileLocation ?? Path.Combine($"{AppContext.BaseDirectory}", "conf.jobj");
}
public static Config OpenDefault()
{
var cfg = new Config();
UseDefaultLocation(cfg);
return Open(cfg);
}
public static Config Open(Config config)
{
var text = File.ReadAllText(config.FileLocation);
Config openedCfg = JsonConvert.DeserializeObject<Config>(text);
return openedCfg;
}
}
the reference to ConfigurationBox is a custom control:
And after the config is loaded it looks like:
Obviously it is rough, but it should provide all the basics you need to do something similar.

How can I use the Entity Framework Core capabilities to change my database?

I am building an application on top of Entity Framework Core and I want to, sort of, apply a migration at runtime.
My intended approach is to have the current database model in memory and create a new model, then calculate the difference between the two models using IMigrationsModelDiffer.GetDifferences().
From there, instead of printing the differences into a Migration class, I want to create the MigrationCommands directly and apply those commands to my database.
The above sounds fairly straightforward but I'm having a lot of issues with the Dependency Injection system.
This is the code I have right now:
static DbContextOptions GetOptions(IModel model)
{
var builder = new DbContextOptionsBuilder();
builder
.UseSqlServer(connStr)
.UseModel(model);
return builder.Options;
}
class Test1ModelAEntity
{
public int Id { get; set; }
public string StrProp { get; set; }
}
static void Main(string[] args)
{
var sqlServerServices = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var conventions = new ConventionSet();
sqlServerServices.GetRequiredService<IConventionSetBuilder>().AddConventions(conventions);
var emptyModelBuilder = new ModelBuilder(conventions);
var emptyModel = emptyModelBuilder.Model;
var test1ModelBuilder = new ModelBuilder(conventions);
test1ModelBuilder.Entity<Test1ModelAEntity>()
.ToTable("ModelA");
var test1Model = test1ModelBuilder.Model;
using (TestContext ctx = new TestContext(GetOptions(emptyModel)))
{
var migrationServices = new ServiceCollection()
.AddDbContextDesignTimeServices(ctx)
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(emptyModel, test1Model);
var commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test1Model);
var connection = migrationServices.GetRequiredService<IRelationalConnection>();
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
}
}
This code throws a NullReferenceException with this stack trace:
at Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping.<>c.<GetRootType>b__10_0(IEntityType t)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.GetSortedProperties(TableMapping target)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.<Add>d__37.MoveNext()
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.<DiffCollection>d__73`1.MoveNext()
at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.Sort(IEnumerable`1 operations, DiffContext diffContext)
at Sandbox.Program.Main(String[] args) in D:\Sandbox\Program.cs:line 108
I have inspected the source code and it appears that there's an issue with the way EFCore is interpreting my model. I am using EFCore version 2.1 preview 2.
Really I'm mostly trying random configurations on my IServiceCollections because I have no idea how to set this up. I am also trying to stay away from EFCore internal classes but if needed be I may use one or two for the time being.
Is there a way to take advantage of EFCore's built-in capabilities to generate some SQL given a pair of IModels? If so, how do I set up DI to have all the required services?
Thank you for the comments which pointed me in the correct direction.
In summary, I was trying to create my models using an empty convention set. This obviously leads to all sorts of problems as you have to generate the entire model explicitly, which is very complex.
To use the expected convention set I had to get it from my context using ConventionSet.CreateConventionSet. I also had to manually validate my model before being able to use it in queries and insert commands. The rest of the logic is pretty much the same.
Here's my final code including the tests I ran to ensure everything worked as expected:
static DbContextOptions GetOptions(IModel model)
{
var builder = new DbContextOptionsBuilder();
builder
.UseSqlServer(connStr)
.UseModel(model);
return builder.Options;
}
//Test 1
class Test1EntityA
{
public int Id { get; set; }
public string StrProp { get; set; }
}
//Test 2
class Test2EntityA
{
public int Id { get; set; }
public string StrProp { get; set; }
public ICollection<Test2ModelBEntity> Children { get; set; }
}
class Test2EntityB
{
public int Id { get; set; }
public int EntityAId { get; set; }
public Test2EntityA EntityA { get; set; }
}
static void Main(string[] args)
{
var emptyModelBuilder = new ModelBuilder(new ConventionSet());
var emptyModel = emptyModelBuilder.Model;
using (var baseCtx = new TestContext(GetOptions(emptyModel)))
{
//Get all services we need from the base context
var conventions = ConventionSet.CreateConventionSet(baseCtx);
var migrationServices = new ServiceCollection()
.AddDbContextDesignTimeServices(baseCtx)
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
//Test 1
var test1ModelBuilder = new ModelBuilder(conventions);
test1ModelBuilder.Entity<Test1EntityA>()
.ToTable("EntityA");
var test1Model = test1ModelBuilder.GetInfrastructure().Metadata;
test1Model.Validate();
var operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(emptyModel, test1Model);
var commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test1Model);
var connection = migrationServices.GetRequiredService<IRelationalConnection>();
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
using (TestContext ctx = new TestContext(GetOptions(test1Model)))
{
ctx.Set<Test1EntityA>().Add(new Test1EntityA
{
StrProp = "test1",
});
ctx.SaveChanges();
}
//Test 2
var test2ModelBuilder = new ModelBuilder(conventions);
test2ModelBuilder.Entity<Test2EntityA>()
.ToTable("EntityA");
test2ModelBuilder.Entity<Test2EntityB>()
.ToTable("EntityB");
var test2Model = test2ModelBuilder.GetInfrastructure().Metadata;
test2Model.Validate();
operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(test1Model, test2Model);
commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test2Model);
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
using (TestContext ctx = new TestContext(GetOptions(test2Model)))
{
var e = new Test2EntityA
{
StrProp = "test2",
};
ctx.Set<Test2EntityA>().Add(e);
ctx.Set<Test2EntityB>().Add(new Test2EntityB
{
EntityA = e,
});
ctx.SaveChanges();
Console.WriteLine(ctx.Set<Test2EntityB>().Where(b => b.EntityA.StrProp == "test2").Count());
}
}
}

How to mock nested properties and objects and their functions?

I have the code below which I would like to test, but I'm not sure whether it is possible or not.
I have EF repositories and they are put together to a class as public properties. I don't know exactly whether it is bad solution or not, but it is easier to manage the code and its dependencies. Only the testability is still a question.
Purpose of my test is injecting data via
administrationRepository.ModuleScreen.GetAll()
method and catch the result. I know that it can be tested once it is deployed, but I want the tests in build time in order to have as fast feedback as possible.
I went through questions and answers here, but I cannot find answers. In my code I got to the point where the property is set up, but when I call the administrationRepoMock.Object.ModuleScreen.GetAll() ReSharper offers only the methods coming from Entitiy Framework and not the Moq related functions.
It is possible what I want? If so, how? Is my design suitable for this? If not can you give me articles, urls where I can see examples?
Repository:
public interface IModuleScreen
{
IEnumerable<DomainModel.Administration.ModuleScreen> GetAll();
}
public interface IAdministrationRepository
{
IModuleScreen ModuleScreen { get; }
}
public partial class AdministrationRepository : IAdministrationRepository
{
public virtual IModuleScreen ModuleScreen { get; private set; }
public AdministrationRepository( IModuleScreen moduleScreen )
{
this.ModuleScreen = moduleScreen;
}
}
Application:
public partial class DigitalLibraryApplication : IDigitalLibraryApplication
{
private IAdministrationRepository _administrationRepository;
private IMapper.IMapper.IMapper _mapper;
private IDiLibApplicationHelper _dilibApplicationHelper;
#region Ctor
public DigitalLibraryApplication( IAdministrationRepository administrationRepository, IMapper.IMapper.IMapper mapper, IDiLibApplicationHelper diLibApplicationHelper)
{
_administrationRepository = administrationRepository;
_mapper = mapper;
_dilibApplicationHelper = diLibApplicationHelper;
}
#endregion
public IEnumerable<ModuleScreenContract> GetModuleScreens()
{
//inject data here
IEnumerable<ModuleScreen> result = _administrationRepository.ModuleScreen.GetAll();
List<ModuleScreenContract> mappedResult = _mapper.MapModuleScreenToModuleScreenContracts(result);
return mappedResult;
}
}
Test code:
[Test]
public void ItCalls_ModuleRepository_Get_Method()
{
List<SayusiAndo.DiLib.DomainModel.Administration.ModuleScreen> queryResult = new List<SayusiAndo.DiLib.DomainModel.Administration.ModuleScreen>()
{
new DomainModel.Administration.ModuleScreen()
{
Id = 100,
},
};
var moduleScreenMock = new Mock<IModuleScreen>();
moduleScreenMock.Setup(c => c.GetAll()).Returns(queryResult);
administrationRepoMock.SetupProperty(c => c.ModuleScreen, moduleScreenMock.Object);
var mapperMock = new Mock<IMapper.IMapper.IMapper>();
var dilibApplicationHerlperMock = new Mock<IDiLibApplicationHelper>();
IDigitalLibraryApplication app = new DigitalLibraryApplication( administrationRepoMock.Object, mapperMock.Object, dilibApplicationHerlperMock.Object );
app.GetModules();
//issue is here
administrationRepoMock.Object.ModuleScreen.GetAll() //???
}
Here is a refactoring of your test that passes when run. You can update the pass criteria to suit you definition of a successful test.
[Test]
public void ItCalls_ModuleRepository_Get_Method() {
// Arrange
List<ModuleScreen> queryResult = new List<ModuleScreen>()
{
new ModuleScreen()
{
Id = 100,
},
};
//Building mapped result from query to compare results later
List<ModuleScreenContract> expectedMappedResult = queryResult
.Select(m => new ModuleScreenContract { Id = m.Id })
.ToList();
var moduleScreenMock = new Mock<IModuleScreen>();
moduleScreenMock
.Setup(c => c.GetAll())
.Returns(queryResult)
.Verifiable();
var administrationRepoMock = new Mock<IAdministrationRepository>();
administrationRepoMock
.Setup(c => c.ModuleScreen)
.Returns(moduleScreenMock.Object)
.Verifiable();
var mapperMock = new Mock<IMapper>();
mapperMock.Setup(c => c.MapModuleScreenToModuleScreenContracts(queryResult))
.Returns(expectedMappedResult)
.Verifiable();
//NOTE: Not seeing this guy doing anything. What's its purpose
var dilibApplicationHerlperMock = new Mock<IDiLibApplicationHelper>();
IDigitalLibraryApplication app = new DigitalLibraryApplication(administrationRepoMock.Object, mapperMock.Object, dilibApplicationHerlperMock.Object);
//Act (Call the method under test)
var actualMappedResult = app.GetModuleScreens();
//Assert
//Verify that configured methods were actually called. If not, test will fail.
moduleScreenMock.Verify();
mapperMock.Verify();
administrationRepoMock.Verify();
//there should actually be a result.
Assert.IsNotNull(actualMappedResult);
//with items
CollectionAssert.AllItemsAreNotNull(actualMappedResult.ToList());
//There lengths should be equal
Assert.AreEqual(queryResult.Count, actualMappedResult.Count());
//And there should be a mapped object with the same id (Assumption)
var expected = queryResult.First().Id;
var actual = actualMappedResult.First().Id;
Assert.AreEqual(expected, actual);
}

C# - How to return an Array to set a Class property

I am trying to create a Class Method which can be called to Query the Database. The function itself works but for some reason, when the Array is returned, they're not set.
My function code is:
public Configuration[] tbl_bus(string type, string match)
{
// Create Obejct Instance
var db = new rkdb_07022016Entities2();
// Create List
List<Configuration> ConfigurationList = new List<Configuration>();
// Allow Query
if (type.ToLower() == "bustype")
{
foreach (var toCheck in db.tblbus_business.Where(b => b.BusType == match))
{
// Create Class Instance
var model = new Configuration { Name = toCheck.Name, BusinessID = toCheck.BusinessID };
// Append to the property
ConfigurationList.Add(model);
}
}
else if (type.ToLower() == "businessid")
{
foreach (var toCheck in db.tblbus_business.Where(b => b.BusinessID == match))
{
// Create Class Instance
var model = new Configuration { Name = toCheck.Name, BusinessID = toCheck.BusinessID };
// Append to the property
ConfigurationList.Add(model);
}
}
return ConfigurationList.ToArray();
}
And my Configuration code is:
public class Configuration
{
// Properties of the Database
public string Name { get; set; }
public string BusinessID { get; set; }
public string Address { get; set; }
}
public Configuration Config { get; set; }
public Controller()
{
this.Config = new Configuration();
}
On my Handler I am doing:
// Inside the NameSpace area
Controller ctrl;
// Inside the Main Void
ctrl = new Controller();
ctrl.tbl_bus("bustype", "CUS");
context.Response.Write(ctrl.Config.Name);
I tried watching the Class function and it does create the Array, only, when I watch the ctrl.Config.Name it is always set to NULL. Could anyone possibly help me in understanding why the return isn't actually setting the properties inside the Configuration class?
Edit: The function does run and it fetches 3006 rows of Data when matching the bus_type to customer. (Its a large Database) - Only, the properties are never set on return.
Edit: Is there a specific way to return an Array to a Class to set the Properties?
Thanks in advance!
Change your Configs in Controller to array
public Configuration[] Configs { get; set; }
Change your tbl_bus function to void, and set the Configs inside the function.
public void tbl_bus(string type, string match)
{
// do your code
// set the configs here
Configs = ConfigurationList.ToArray();
}
Hope it helps.
Although this is not a complete answer to your question, the problem probably lies in the fact that you're not doing anything with the array returned by the method. You're simply discarding it right away. If you change your code to
ctrl = new Controller();
Configuration[] config = ctrl.tbl_bus("bustype", "CUS");
you will be able to reference the array later on.
Console.WriteLine(config.Length);
Now you can use it to set any properties you like.

Categories