您当前的位置: 首页 >  c#

寒冰屋

暂无认证

  • 3浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

在C#中的DiponRoy简单模型/实体映射器的第三个化身

寒冰屋 发布时间:2022-07-26 22:42:05 ,浏览量:3

 

目录

介绍

代码更改

执行

什么是“In”扩展方法?

一个简单的测试程序

结论

下载程序 - 1.9 KB

介绍

我修改前两篇关于Code Project的文章的目的是因为我想进行一些有用的更改,而使用AutoMapper之类的东西是矫枉过正的。虽然我很欣赏IL一代,但我真的不想在MapperConfiguration对象中注册我的CreateMap映射。我只想在需要时映射两个对象,用几个铃声,没有口哨声。我生活中的一个主题似乎是KISS原则,这就是为什么我最终经常自己动手!

代码更改

我对代码做了三处更改:

首先,映射方法CreateMapped从this对象中确定源类型,所以不要写:

Student source = new Student() { Id = 1, Name = "Smith" };
StudentLog newMapped = source.CreateMapped();

可以写:

Student source = new Student() { Id = 1, Name = "Smith" };
StudentLog newMapped = source.CreateMapped();

注意删除了泛型参数Student。

其次,我添加了一个属性MapperPropertyAttribute,用于在目标属性具有不同名称时指定源属性。

例如,我有一个类User:

public class User
{
  public int Id { get; set; }
  public string UserName { get; set; }
  public string Password { get; set; }
  public string Salt { get; set; }
  public string AccessToken { get; set; }
  public string RefreshToken { get; set; }
  public bool IsSysAdmin { get; set; }
  public DateTime? LastLogin { get; set; }
  public int? ExpiresIn { get; set; }
  public long? ExpiresOn { get; set; }
  public bool Deleted { get; set; }
}

但我希望登录响应返回具有不同名称的属性子集。MapperProperty用于指定目标类中的属性名称转换:

public class LoginResponse
{
  [MapperProperty(Name = "AccessToken")]
  public string access_token { get; set; }

  [MapperProperty(Name = "RefreshToken")]
  public string refresh_token { get; set; }

  [MapperProperty(Name = "ExpiresIn")]
  public int expires_in { get; set; }

  [MapperProperty(Name = "ExpiresOn")]
  public long expires_on { get; set; }

  public string token_type { get; set; } = "Bearer";
}

一个示例用例片段是:

var response = user.CreateMapped();

第三,我在某些地方重命名了变量名。

执行

属性很简单:

public class MapperPropertyAttribute : Attribute
{
  public string Name { get; set; }

  public MapperPropertyAttribute() { }
}

扩展方法已被修改以提供两种共享通用private实现的public方法。

public static class MapExtensionMethods
{
  public static TTarget MapTo(this TSource source, TTarget target)
  {
    var ret = MapTo(source.GetType(), source, target);

    return ret;
  }

  public static TTarget CreateMapped(this object source) where TTarget : new()
  {
    return MapTo(source.GetType(), source, new TTarget());
  }

  private static TTarget MapTo(Type tSource, object source, TTarget target)
  {
    const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | 
                               BindingFlags.NonPublic;

    var srcFields = (from PropertyInfo aProp in tSource.GetProperties(flags)
        where aProp.CanRead //check if prop is readable
        select new
        {
            Name = aProp.Name,
            Alias = (string)null,
            Type = Nullable.GetUnderlyingType(aProp.PropertyType) ?? aProp.PropertyType
        }).ToList();

    var trgFields = (from PropertyInfo aProp in target.GetType().GetProperties(flags)
        where aProp.CanWrite //check if prop is writeable
        select new
        {
            Name = aProp.Name,
            Alias = aProp.GetCustomAttribute()?.Name,
            Type = Nullable.GetUnderlyingType(aProp.PropertyType) ?? aProp.PropertyType
        }).ToList();

    var commonFields = trgFields.In(srcFields, /* T1 */ t => t.Alias ?? 
                                    t.Name, /* T2 */ t => t.Name).ToList();

    foreach (var field in commonFields)
    {
      var value = tSource.GetProperty(field.Alias ?? field.Name).GetValue(source, null);
      PropertyInfo propertyInfos = target.GetType().GetProperty(field.Name);
      propertyInfos.SetValue(target, value, null);
    }

    return target;
  }
}

“秘诀”是在select语句返回的匿名对象中添加Alias属性和null解析运算符??,以确定源属性是使用别名名称还是属性名称。另一件有趣的事情是,由于这些是匿名属性,分配Alias为null需要将null:Alias = (string)null,转换为string。不是你经常看到的东西。

什么是“In”扩展方法?

不幸的是,Linq IntersectBy仅在.NET 6中可用。 为此做了如下的修改。

// See Mr.PoorInglish's rework of my article here:
// https://www.codeproject.com/Articles/5293576/A-Performant-Items-in-List-A-that-are-not-in-List?msg=5782421#xx5782421xx
public static IEnumerable In(
  this IEnumerable items1,
  IEnumerable items2,
  Func keySelector1, Func keySelector2)
  {
    var dict1 = items1.ToDictionary(keySelector1);
    var k1s = dict1.Keys.Intersect(items2.Select(itm2 => keySelector2(itm2)));
    var isIn = k1s.Select(k1 => dict1[k1]);

  return isIn;
}

此外,.NET 6的IntersectedBy实现确实不是我想要的签名,我也不想实现iEqualityComparer,所以我们将使用上面的扩展方法。

一个简单的测试程序

本文的下载有一个示例程序,您可以运行该示例程序来演示此版本的映射器:

public static void Main()
{
  // We declare the epoch to be 1/1/1970.
  var ts = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
  var expiresSeconds = 24 * 60 * 60;

  var user = new User()
  {
    Id = 1,
    UserName = "fubar",
    Password = "fizbin",
    Salt = "pepper",
    AccessToken = Guid.NewGuid().ToString(),
    RefreshToken = Guid.NewGuid().ToString(),
    ExpiresIn = expiresSeconds,
    ExpiresOn = ts + expiresSeconds,
    LastLogin = DateTime.Now,
  };

  var response = user.CreateMapped();

  Console.WriteLine($"access_token: {response.access_token}");
  Console.WriteLine($"refresh_token: {response.refresh_token}");
  Console.WriteLine($"expires_in: {response.expires_in}");
  Console.WriteLine($"expires_on: {response.expires_on}");
  Console.WriteLine($"token_type: {response.token_type}");
}

输出:

access_token: 86384067-9193-449a-a6ff-8023be5fe203
refresh_token: 12e04d46-882e-4a25-a777-d1440f4783cd
expires_in: 86400
expires_on: 1644175047
token_type: Bearer
结论

在这里没什么好总结的——这一切都是近8年前写的一个简短而有用的提示和技巧的第三版!

https://www.codeproject.com/Tips/5324352/A-Third-Incarnation-of-DiponRoys-Simple-Model-Enti

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0471s