jasonbock / inlinemapping Goto Github PK
View Code? Open in Web Editor NEWUsing the Compiler API for object mapping
License: MIT License
Using the Compiler API for object mapping
License: MIT License
Basically I'm missing some closing curly braces. Fix this!
If the types are in the same assembly, then they can "see" internal properties. I'm not handling this right now, need to fix that.
I'd like to be able to map into an existing object.
Use-case: I get the entity from the database, I have the model posted into the action. I don't want to replace the entity, just update those properties I got from the model.
Proposed:
public void MapToExistingDestination(this Source source, Destionation destination)
{
destination.Name = source.Name;
destination.Reason = source.Reason;
}
Then in my controller action (or service) I can have code like this:
[HttpPut("{id}")]
public ActionResult<Model> Put(int id, [FromBody]CustomerModel model)
{
Customer customer = customerRepository.GetById(id);
if (customer == null)
{
return NotFound();
}
model.MapToExistingCustomer(customer);
customerRepository.Save(customer);
return model;
}
This shouldn't change any behavior, it just makes the code arguably cleaner by pushing the semantic model into the receiver and verifying the nodes right there.
Want a "GettingStarted.md" file which describes what's required to run, and what a developer needs to do.
Also create "IMxxxx.md" files which will have documentation on each analyzer.
Make sure I add tests for MappingBuilder
and MappingInformation
, and ensure tests are up-to-date and addressing the right concerns.
This way I can reference the IDs, title, etc. from other assemblies.
I think I can move the types from that project into InlineMapping
. Basically remove this from <ProjectReference>
:
ReferenceOutputAssembly="false"
Then it should work.
I've heard of Scriban, and I think it's worth investigating to see how it would work compared to using IndentedTextWriter
. Would be interesting to see if it improves readability, see if it's faster, etc.
Create a warning if a source property doesn't have a map to a destination. Maybe do the same for destination as well - e.g. remove it from the list once it's found, and if there are any left, warn about those.
Similar to what I do in my Rocks projects, have a method on the class that contains the values for the diagnostic to create a diagnostic, rather than doing it in MappingInformation
.
In .NET 6, incremental generation was added. This requires changes to the source generator, outlined rather nicely in this article. I have a bunch of generator projects that I can up, but I figured I'd start with InlineMapping and use my experiences for my other generator repos.
Self-explanatory. See this.
I believe that Source Generators have a great use case for object mapping. As you've stated, the memory and execution time is vastly better if these object mappings are generated at compile-time instead of at runtime.
However, there are so many features that are available in AutoMapper that make the developer tooling better.
e.g. AutoMapper has a nested mapping feature described here.
I believe this project has a lot of valid use-cases but lacks features in comparison to its competitors. I would like to develop this project with you if there is a project roadmap in some capacity.
Add Stryker and update tests as needed.
Similar to what I did with CSLA: create the helpUrl
based on the ID name and where the .md file exists in GitHub.
Self-explanatory. Follow the stuff I did in Rocks. I'm not sure if you can actually reference a source generator within a NuGet package just yet, but I at least want the package there.
Someone mentioned to me to add something like this to this list:
using Newtonsoft.Json;
public static class MappingThroughSerialization
{
public static T Clone<T>(this object srcObject) where T : class
{
var destObject = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(srcObject, JsonSettings.DefaultOptions_TypeNamePartial), JsonSettings.DefaultOptions_TypeNamePartial);
return destObject;
}
}
Basically, use serialization to map an object. I'm guessing that would be slower, but it would be interesting to add for comparison purposes.
I think I can clean up the incremental generator a bit...
IsSyntaxTargetForGeneration()
, just look for AttributeSyntax
nodes that have expected "names" like Map
or MapAttribute
(along with the other two options)TransformTargets()
(SyntaxNode node, INamedTypeSymbol source, INamedTypeSymbol destination, MappingContext context)?
MapToAttribute
or MapFromAttribute
, return the right values for that.MapAttribute
, return the right values.null
CreateOutput()
nodes
type to the tuple, and rename to targets
GetTargets()
(and the method itself)mappings
dictionary.If you put #nullable enable
at the top of each file then you won't need to do this:
public Destination MapToDestination(this Source source)
{
source == null ? throw new IHateYouException(nameof(source))
: new Destination {
// ...
};
}
but instead can do this:
public Destination MapToDestination(this Source source)
{
new Destination {
// ...
};
}
public Destination? MapToDestinationNullable(this Source? source)
{
source == null
? null
: new Destination {
// ...
};
}
It's time :)
This basically means I "trash" what's there (it's really old, targets .NET Framework 4.6.1, etc.), and start over with a source generators project. The only thing I really want to save is UsingInlineMapperCodeFix
- well, the code within it.
The main idea now is to create a MapToAttribute
that is either applicable to a class or an assembly. If it's a class, it will take a Type
that defines the destination it can be mapped to. If it's on an assembly, it can take a source and destination type. For the first release, I'll just support the class-targeted version.
The source generator will look for these attributes on classes, and generate extension methods with inlined mapping code that I've already created in UsingInlineMapperCodeFix
. For example, if I have this:
[MapTo(typeof(Destination))]
[MapTo(typeof(AnotherDestination))]
public sealed class Source { }
The source generator will create this:
using AnyNamespacesThatDestinationTypesAreIn;
namespace TheNamespaceSourceIsIn
{
public static class SourceMapToExtensions
{
public static Destination MapToDestination(this Source self) { ... }
public static AnotherDestination MapToAnotherDestination(this Source self) { ... }
}
}
Then a developer can write this:
var source = new Source();
var destination = source.MapToDestination();
var anotherDestination = source.MapToAnotherDestination();
Of course, there are collisions to be worried about here. For example, if AnotherDestination
exists twice in two different namespaces, and a developer wants to support mappings for both, I can't define MapToAnotherDestination()
two times. One possibility is to see if there would be any collisions, and then use the full type name (namespace + type name) in the extension method name. That would look kind of ugly, but it would work.
I'll also want to update it with the core setup I typically do with projects these days (see Rocks for the baseline).
If I have this scenario:
namespace SomeNamespace
{
public class Destination { }
}
namespace SomeNamespace.SubNamespace
{
public class Source { }
}
There's no reason to create a using
statement for Destination
.
Or, another thought is to be very specific with the destination type name with an alias. Maybe do this:
using D = SomeNamespace.Destination;
...
public static D MapToDestination(this Source @this) => new D() ...
That way I'm always creating the right destination type (the source we'll put in its current namespace, and the only collision I could possibly run into is if someone has a class with the name of the extension class with the same method name. Possible, but for now I'll live with that)
If there's a type that has been set to be mappable, and two properties exist between the source and destination that use that type, I should use the MapTo...()
method between them, rather than simply setting one equal to another. Or at least give the user a way to configure that behavior.
There's issues with using the mapping method. I can easily fall into a recursive loop: A
has a property of type B
, B
has a property of type C
, C
has a property of type A
, and....I have to figure out a strategy to catch that and decide what to do with that. Keep going? Have a limit? Let the user configure the behavior?
Right now the target type needs a no-argument constructor, so InlineMapping can create an instance of it, and then it maps the properties. This can get in the way of the new record syntax:
[MapTo(Destination)]
public record Source(Guid Id, string Name);
public record Destination(Guid Id, string Name);
Arguably, it would be nice to have MapToDestination()
use the constructor and pass the values in from the source properties. But this is a very slippery slope. What if the two types aren't records? Or they are records and don't use this shorthand notation? If the properties are PascalCased, and the parameters camelCased, how would I know for sure what maps to what?
I'll leave this issue for now, but I think I'd need to constrain this in some way. Meaning, I only match for constructors if there isn't a no-argument constructor, and then I do a case-insensitive match.
Right now I only map properties if the names are the same, the getters and setters are accessible, and the types are exactly the same. In other words, this won't map:
public class BaseType { }
public class SubType
: BaseType { }
[MapTo(typeof(Destination))]
public class Source
{
public SubType A { get; set; }
}
public class Destination
{
public BaseType A { get; set; }
}
Maybe I add this:
[MapTo(typeof(Destination), allowAssignableTypes: true)]
Essentially, state that yes, you'd let this happen. Or...I make this the default, and I reverse the requirement:
[MapTo(typeof(Destination), typesMustMatchExactly: true)]
It's possible that the developer doesn't have access to the source code for the source and/or destination types. Therefore, let's create two new attributes, MapFromAttribute
and MapAttribute
. MapFromAttribute
would be used if the destination type is editable, but not the source:
[MapFrom(typeof(Source))]
public class Destination
{
...
}
If both the source and destination types are not editable, MapAttribute
would be used:
[assembly: Map(typeof(Source), typeof(Destination))]
Note that this has to happen as the assembly level.
In both cases, the extension method would still be created for the source type.
Move all the source into \src
and create a \docs
folder as well - that will be used for analyzer documentation, general documentation, etc.
I have a handful right now, but I need to finish them out.
One idea is to allow the developer to provide hooks to the mapping process:
[MapTo(typeof(Destination), createDestination: true, postCustomization: true)]
If createDestination
is true
, a Func<Destination>
parameter is created to allow the developer to create it. This may be useful when the destination type has a constructor with parameters. Note that if this is true
, the diagnostic that looks for public, no-argument constructors would have to be updated. If postCustomization
is true
, a Action<Source, Destination>
parameter is created to allow the developer to handle complex mapping cases. Here's an example if both of these flags are set to true
:
var destination = source.MapToDestination(
() => new Destination(3),
(s, d) =>
{
d.MapThis = s.ToThis;
});
I'm not sure about postCustomization
. A developer could simply do this if they want:
var destination = source.MapToDestination();
destination.MapThis = source.ToThis;
I'm basically putting this idea out to think about and come back to later. I guess if you needed to do this customization, having the callback as a parameter "forces" the requirement, rather than having to remember to do the post-processing. But it also feels like it's somewhat unnecessary.
The createDestination
flag I think has more value. It's definitely possible that destination types will not always have no-argument constructors, so giving the developer the ability to create the destination has value. However, I'm not sure how this will work with init
properties as well, so there be dragons here.
If you have this:
[MapTo(typeof(Destination))]
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public int Identifier { get; set; }
}
You could map those after InlineMapping is done, but maybe I provide this:
public class Destination
{
[MappingName(nameof(Source.Id)]
public int Identifier { get; set; }
}
If this attribute exists on a property, it will be used as the mapping name.
I'm not convinced this is a good idea - in fact, I think this may be a really bad idea. Adding even more metadata on the type definitions can get to be ugly. Also, if you don't own the type, I'd have to provide this:
[assembly: MappingName(typeof(Source), typeof(Destination), nameof(Source.Id), nameof(Destination.Identifier))]
Right now, I've found these:
For the rc4 package, I had to remove some stuff to get my head around creating a package that worked. Now that I have that figured out (I think) I want to add the .snupkg package as well before I do a 1.0.0 release.
Rather than writing my handcrafted stuff for testing, look into these packages and see if they can be used instead.
Right now I think there's an issue with records in source generators. I've added an issue to track this. There's really nothing I can do to fix this, hopefully this will just eventually work once I can receive RecordDeclarationSyntax
types.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.