Giter Site home page Giter Site logo

inlinemapping's People

Contributors

jasonbock avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

inlinemapping's Issues

Map into existing object

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;
}

Create Documentation

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.

Update Tests

Make sure I add tests for MappingBuilder and MappingInformation, and ensure tests are up-to-date and addressing the right concerns.

Remove InlineMapping.Metadata

I think I can move the types from that project into InlineMapping. Basically remove this from <ProjectReference>:

ReferenceOutputAssembly="false"

Then it should work.

Give Scriban a Whirl

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.

Diagnostics for Missed Source Property Mapping

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.

Create Diagnostics in Constants Classes

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.

InlineMapping roadmap?

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.

Create NuGet Package

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.

Add Mapping Via Serialization to the Performance Tests

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.

Streamline Generation

I think I can clean up the incremental generator a bit...

  • First, in IsSyntaxTargetForGeneration(), just look for AttributeSyntax nodes that have expected "names" like Map or MapAttribute (along with the other two options)
  • Next, in TransformTargets()
    • Return (SyntaxNode node, INamedTypeSymbol source, INamedTypeSymbol destination, MappingContext context)?
    • If the given node is a MapToAttribute or MapFromAttribute, return the right values for that.
    • Else, if it's MapAttribute, return the right values.
    • Else, return null
  • Finally, in CreateOutput()
    • Change nodes type to the tuple, and rename to targets
    • Remove the call to GetTargets() (and the method itself)
    • Note that I'll still have to keep the mappings dictionary.

#nullable enable

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 {
      // ...
    };
}

Update for Source Generators

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).

Don't Create Using if Destination is "In" Source Namespace

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)

Handle Mappable Properties

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?

Consider Supporting Custom Constructors

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.

Relax Type Equality Between Properties

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)]

Create MapFromAttribute and MapAttribute Attributes

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.

Restructure Files

Move all the source into \src and create a \docs folder as well - that will be used for analyzer documentation, general documentation, etc.

Provide Customization Flags

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.

Add a MappingNameAttribute

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))]

Add .snupkg Back to InlineMapping

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.

Support Records

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.