cezarypiatek / MappingGenerator

:arrows_counterclockwise: "AutoMapper" like, Roslyn based, code fix provider that allows to generate mapping code in design time.
https://marketplace.visualstudio.com/items?itemName=54748ff9-45fc-43c2-8ec5-cf7912bc3b84.mappinggenerator
MIT License
1.03k stars 120 forks source link

Complete LINQ selects #101

Closed axelheer closed 4 years ago

axelheer commented 4 years ago

Is there a way to complete LINQ statements too?

var query = from p in context.Personen
            where ...
            select new PersonItemView
            {
                |
            };

Currently, I get the options "Initialize with sample values" and "Initialize with local variables".

Some kind of "Initialize with 'p'" would be very nice.

Awesome project, btw.

cezarypiatek commented 4 years ago

I've never worked with this LINQ syntax (always prefer extensions methods). I will take a look at the possibilities of implementing it and I will let you know if it's doable.

cezarypiatek commented 4 years ago

@axelheer Ok, I've got a working POC. I will try to complete the implementation over the weekend. Could you provide more examples with let, join, group by and query continuations for testing purposes?

axelheer commented 4 years ago

Wow, that rocks. 🥇

I wouldn't expect it to work with more complex queries, but I looked for examples within some projects I'm involved in, which are too much verbose. Thus, I've written some examples using a simple n:m relation and a semantic relation too, which should cover those four use cases.

public class Tenant
{
    public string Key { get; set; }

    public string Description { get; set; }
}

public class User
{
    public int Id { get; set; }

    public string Name { get; set; }

    public IList<UserRequest> Requests { get; } = new List<UserRequest>();

    public string Tenant { get; set; }
}

public class Request
{
    public int Id { get; set; }

    public string Name { get; set; }

    public DateTime TimeStamp { get; set; }

    public string Category { get; set; }

    public IList<UserRequest> Users { get; } = new List<UserRequest>();
}

public class UserRequest
{
    public User User { get; set; }

    public int UserId { get; set; }

    public Request Request { get; set; }

    public int RequestId { get; set; }
}

public class UserView
{
    public int Id { get; set; }

    public string Name { get; set; }

    public int LastRequestId { get; set; }

    public string LastRequestName { get; set; }

    public DateTime LastRequestTimeStamp { get; set; }
}

public class TenantView
{
    public string Key { get; set; }

    public string Description { get; set; }

    public int UserId { get; set; }

    public string UserName { get; set; }
}

public class RequestsView
{
    public int UserId { get; set; }

    public string UserName { get; set; }

    public int Year { get; set; }

    public int Month { get; set; }

    public int Count { get; set; }
}

public class RequestView
{
    public int UserId { get; set; }

    public string UserName { get; set; }

    public int RequestId { get; set; }

    public string RequestName { get; set; }
}

public static class Examples
{
    public static IEnumerable<UserView> LetStatement(IEnumerable<User> users)
    {
        return from u in users
               let ur = u.Requests.OrderBy(r => r.Request.TimeStamp).Last()
               let r = ur.Request
               select new UserView
               {
                   Id = u.Id,
                   Name = u.Name,
                   LastRequestId = r.Id,
                   LastRequestName = r.Name,
                   LastRequestTimeStamp = r.TimeStamp
               };
    }

    public static IEnumerable<TenantView> JoinStatement(IEnumerable<Tenant> tenants, IEnumerable<User> users)
    {
        return from t in tenants
               join u in users on t.Key equals u.Tenant
               select new TenantView
               {
                   Key = t.Key,
                   Description = t.Description,
                   UserId = u.Id,
                   UserName = u.Name
               };
    }

    public static IEnumerable<RequestsView> GroupStatement(IEnumerable<UserRequest> userRequests)
    {
        return from ur in userRequests
               let u = ur.User
               let r = ur.Request
               group r by new
               {
                   u.Id,
                   u.Name,
                   r.TimeStamp.Year,
                   r.TimeStamp.Month
               }
               into g
               select new RequestsView
               {
                   UserId = g.Key.Id,
                   UserName = g.Key.Name,
                   Year = g.Key.Year,
                   Month = g.Key.Month,
                   Count = g.Count()
               };
    }

    public static IEnumerable<RequestView> QueryContinuation(IEnumerable<User> users, int year, int month, string category)
    {
        return from u in users
               from ur in u.Requests
               where ur.Request.TimeStamp.Year == year
                   && ur.Request.TimeStamp.Month == month
                   && ur.Request.Category == category
               select new RequestView
               {
                   UserId = u.Id,
                   UserName = u.Name,
                   RequestId = ur.Request.Id,
                   RequestName = ur.Request.Name
               };
    }
}

Hope this helps.

cezarypiatek commented 4 years ago

With such cryptic variable names it's impossible to create matches as you showed in the examples.

For example, this code can be generated when you change r to lastRequest

    public static IEnumerable<UserView> LetStatement(IEnumerable<User> users)
    {
        return from u in users
               let ur = u.Requests.OrderBy(r => r.Request.TimeStamp).Last()
               let r = ur.Request
               select new UserView
               {
                   Id = u.Id,
                   Name = u.Name,
                   LastRequestId = r.Id,
                   LastRequestName = r.Name,
                   LastRequestTimeStamp = r.TimeStamp
               };
    }

Better version:

return from u in users
       let ur = u.Requests.OrderBy(r => r.Request.TimeStamp).Last()
       let lastRequest = ur.Request
       select new UserView
       {
           Id = u.Id,
           Name = u.Name,
           LastRequestId = lastRequest.Id,
           LastRequestName = lastRequest.Name,
           LastRequestTimeStamp = lastRequest.TimeStamp
       };
cezarypiatek commented 4 years ago

@axelheer axelheer

Here's a pre-release version supporting linq queries. I've also added support for matching against lazy variable's names aka acronym expanding ;) Simply speaking - now it's possible to match LastRequestTimeStamp with lr.TimeStamp. Could you install this version and test if it works according to your expectations?

https://ci.appveyor.com/api/buildjobs/dtwacye299lpxg98/artifacts/MappingGenerator%2FMappingGenerator%2FMappingGenerator.Vsix%2Fbin%2FRelease%2FMappingGenerator.vsix

axelheer commented 4 years ago

❤️ Acronym expanding ❤️

Installing it will be the first thing on Monday morning, when I'm back at work.

Thank you!