我有一些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到位来解决这个问题,但我不喜欢让我的对象看起来的方式。希望这有助于某人。
答案 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 )。