从数据库

时间:2017-11-11 15:44:36

标签: c# asp.net .net dapper slapper.automapper

我有一些C#类代表数据库对象,其中一些包含一个或多个其他自定义对象或自定义对象的可枚举。我正在使用dapper进行查询,并使用slapper映射到自定义对象。它适用于单个对象。我可以轻松地从数据库中获取具有特定ID的父对象,执行一些内部联接,并将其“拥有”的所有内容映射到C#中的自定义对象。当我想对多个父ID进行选择时出现问题。

在某些情况下,假设我有一个人,该人有一个爱好列表,其中包含ID和描述,他们可用的天数列表,也有ID和描述,也许还有另一个自定义字段,如他们是否有或甚至愿意在孩子身边,也可以归结为一个简单的ID和描述。我们称之为最后一个字段的子状态。我会写一个这样的选择语句:

SELECT 
  ,person.id as Id
  ,person.first_name as FirstName
  ,person.last_name as LastName
  ,hobby.Id as Hobbies_Id
  ,hobby.Description as Hobbies_Description
  ,avail.Id as Availabilities_Id
  ,avail.Description as Availabities_Description
  ,child.Id as ChildStatus_Id
  ,child.Description as ChildStatus_Description
FROM
  users.users person
JOIN
  users.userhobbies uhobby
ON
  person.id = uhobby.UserId -- one-to-many with relational table
JOIN
  users.avail hobby
ON
  uhobby.HobbyId = hobby.Id
JOIN
  users.useravailabilities uavail
ON
  person.id = uavail.UserId -- one-to-many with relational table
JOIN
  users.availabilities avail
ON
  uavail.AvailId = avail.Id
JOIN
  users.childstatuses child
ON
  person.ChildStatusId = child.Id

然后我想把它映射到这样的用户:

class User 
{
  public Guid Id {get; set;}
  public string FirstName {get; set;}
  public string LastName {get; set;}
  public IEnumerable<Hobby> Hobbies {get; set;}
  public IEnumerable<Availability> Availabilities {get; set;}
  public ChildStatus ChildStatus {get; set;}
}

由于我在这里使用了精确的命名约定和所有内容,因此Dapper和Automapping的查询工作非常好:

  // Using the above sql in a variable
  var data = Connection.Query<dynamic>(sql);

  var dataReal = Slapper.AutoMapper.MapDynamic<User>(data);
  return dataReal;

这很好用,但它只返回一个用户。我有一个类似的方法,它获取一个ID,我可以通过传递ID完美地检索所有测试用户。我已经尝试过在互联网上搜索,查看文档,我发现的只有:https://github.com/SlapperAutoMapper/Slapper.AutoMapper/issues/57他似乎只是滑过了裂缝。我也尝试将动态数据映射到各种其他结构而没有运气。提前谢谢!

更新 我想出了一个有点残酷的“大锤”型解决方案。我不确定在这一点上,当我可能有一个更方便的解决方案时,我强迫自己使用Slapper。但是,我想确保处于类似情况的任何人都有可能使其工作。这是新的C#部分:

  var data = Connection.Query<dynamic>(sql);
  IEnumerable<Guid> Ids = data.Select(row => (Guid)row.id).Distinct();
  List<User> results = new List<User>();

  foreach (Guid Id in Ids)
  {
    IEnumerable<dynamic> rows = data.Where(x => { return ((Guid) x.id).Equals(Id); });
    User model = (Slapper.AutoMapper.MapDynamic<User>(rows, false) as IEnumerable<User>).FirstOrDefault();
    if (model != null)
    {
      results.Add(model); 
    }
  }

  return results;

正如您所看到的,我正在生成一个唯一的“主要对象”ID列表,并将这些行选择到他们自己的列表中,然后我将其传递给Slapper。我已经通过了“cache = false”参数,以避免在第一个之后将不相关的数据压缩到每个对象中。我可以通过实际保持UserHobby / UserAvailability / UserPhoto ID到位来解决这个问题,但我不喜欢让我的对象看起来的方式。希望这有助于某人。

1 个答案:

答案 0 :(得分:0)

我不熟悉Slapper,但我会告诉你我用Dapper做了什么来构建一个带有双向引用的复杂对象图。

简而言之,在调用connection.Query&lt;&gt;之前构造一个Dictionary或KeyedCollection,然后在Dapper lambda表达式中引用它。

此方法返回服务调用列表。每个服务呼叫分配给一名技术人员和一名客户。但是,可以为技术人员分配多个服务呼叫给多个客户。并且客户可能在现场拥有多名技术人员。

public ServiceCallResponse GetServiceCallsDapper(ServiceCallRequest Request)
{
    var queryParameters = new {statuses = Request.Statuses, createDate = Request.CreateDate};
    const string splitOn = "Number,Id"; // Id indicates beginning of second class (Technician).  Number indicates begining of third class (Customer).
    // Note multiple columns are named "Number".  See note below about how Dapper maps columns to class properties.
    // Note Dapper supports parameterized queries to protect against SQL injection attacks, including parameterized "where in" clauses.
    const string query = @"sql query here..."
    ServiceCallResponse response = new ServiceCallResponse();  // Keyed collection properties created in constructor.
    using (IDbConnection connection = new SqlConnection("DB connection string here..."))
    {
        connection.Open();

        // Dapper adds a generic method, Query<>, to the IDbConnection interface.

        // Query<(1)ServiceCall, (2)Technician, (3)Customer, (4)ServiceCall> means
        //   construct a (1)ServiceCall, (2)Technician, and (3)Customer class per row, add to an IEnumerable<(4)ServiceCall> collection, and return the collection.

        // Query<TFirst, TSecond, TThird, TReturn> expects SQL columns to appear in the same order as the generic types.
        // It maps columns to the first class, once it finds a column named "Id" it maps to the second class, etc.
        // To split on a column other than "Id", specify a splitOn parameter.
        // To split for more than two classes, specify a comma-delimited splitOn parameter.

        response.ServiceCalls.AddRange(connection.Query<ServiceCall, Technician, Customer, ServiceCall>(query, (ServiceCall, Technician, Customer) =>
        {
            // Notice Dapper creates many objects that will be discarded immediately (Technician & Customer parameters to lambda expression).
            // The lambda expression sets references to existing objects, so the Dapper-constructed objects will be garbage-collected.
            // So this is the cost of using Dapper.  We trade unnecessary object construction for simpler code (compared to constructing objects from IDataReader).

            // Each row in query results represents a single service call.
            // However, rows repeat technician and customer data through joined tables.
            // Avoid constructing duplicate technician and customer classes.

            // Refer to existing objects in global collections, or add Dapper-mapped objects to global collections.
            // This avoid creating duplicate objects to represent same data.
            // Newtonsoft JSON serializer preserves object instances from service to client.
            Technician technician;
            Customer customer;
            if (response.Technicians.Contains(Technician.Id))
            {
                technician = response.Technicians[Technician.Id];
            }
            else
            {
                response.Technicians.Add(Technician);
                technician = Technician;
            }
            if (response.Customers.Contains(Customer.Number))
            {
                customer = response.Customers[Customer.Number];
            }
            else
            {
                response.Customers.Add(Customer);
                customer = Customer;
            }
            // Set object associations.
            ServiceCall.Technician = technician;
            ServiceCall.Customer = customer;
            technician.ServiceCalls.Add(ServiceCall);
            if (!technician.Customers.Contains(customer))
            {
                technician.Customers.Add(customer);
            }
            customer.ServiceCalls.Add(ServiceCall);
            if (!customer.Technicians.Contains(technician))
            {
                customer.Technicians.Add(technician);
            }
            return ServiceCall;
        }, queryParameters, splitOn: splitOn));
    }
    return response;
}

使用此技术要求您在JsonSerializer类上设置PreserveReferencesHandling = true,以便在客户端保留对象引用。否则,Json.NET将构造重复的对象和技术人员.Customers.Count将始终== 1。

例如,如果在Acme和另一个在Contoso上为John Doe分配了服务调用,那么如果您离开PreserveReferencesHandling == false,他的技术人员.Customers.Count将等于1(Json.NET将构建两个技术人员对象,每个对象名为John Doe )。