如何使用AutoMapper将包含多个表的数据集进行映射

8

免责声明:这是从一个旧的stackoverflow帖子中复制并粘贴的内容,现在已经不可用了,但我遇到了完全相同的问题,因此重新发布它似乎是合适的,因为它从未得到答复。

我有一个存储过程,将返回4个结果集(联系人、地址、电子邮件、电话),这些结果将被填充到数据集中。我想使用AutoMapper填充一个复杂对象。

public class Contact 
{
    public Guid ContactId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<Address> Addresses { get; set; }
    public List<Phone> Phones { get; set; }
    public List<Email> Emails { get; set; }
}

public partial class Address:BaseClass
{
    public Guid ContactId { get; set; }
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string Address3 { get; set; }
    public string City { get; set; }
    public string StateProvince { get; set; }
    public string PostalCode { get; set; }
    public string CountryCode { get; set; }   
}

public class Email
{
    public Guid EmailId { get; set; } 
    public Guid ContactId { get; set; } 
    public string EmailAddress { get; set; }
}

public class Phone
{
    public Guid PhoneId { get; set; } 
    public Guid ContactId { get; set; }         
    public string Number { get; set; } 
    public string Extension { get; set; }
}

我有一个方法可以获取数据并返回联系人列表。 DataSet填充后,我会定义表之间的关系。
我发现许多示例都是使用CreateDataReader方法将DataSet(或表)转换为reader,并且我在这里也是这样做的。 该方法实际上会将第一个表解析为对象,但不会枚举相关表。
public List<Contact> GetContacts()
{
    List<Contact> theList = null;

    // Get the data
    Database _db = DatabaseFactory.CreateDatabase();
    DataSet ds = db.ExecuteDataSet(CommandType.StoredProcedure, "GetContacts");

    //The dataset should contain 4 tables
    if (ds.Tables.Count == 4) 
    {    
        //Create the maps
        Mapper.CreateMap<IDataReader, Contact>(); // I think I'm missing something here
        Mapper.CreateMap<IDataReader, Address>();
        Mapper.CreateMap<IDataReader, Email>();
        Mapper.CreateMap<IDataReader, Phone>();

        //Define the relationships        
        ds.Relations.Add("ContactAddresses", ds.Tables[0].Columns["ContactId"], ds.Tables[1].Columns["ContactId"]);
        ds.Relations.Add("ContactEmails", ds.Tables[0].Columns["ContactId"], ds.Tables[2].Columns["ContactId"]);
        ds.Relations.Add("ContactPhones", ds.Tables[0].Columns["ContactId"], ds.Tables[3].Columns["ContactId"]);

        IDataReader dr = ds.CreateDataReader();
        theList = Mapper.Map<List<Contact>>(dr);    
    }

    return (theList);    
}

我感觉在联系人对象的映射上缺少了一些东西,但我找不到一个好的例子可以跟随。

如果我手动填充联系人对象,然后将其传递给控制器,它将使用直接映射正确加载ContactModel对象。

public ActionResult Index()
{
    //From the ContactController
    Mapper.CreateMap<Contact, Models.ContactModel>();
    Mapper.CreateMap<Address, Models.AddressModel>();

    List<Models.ContactModel> theList = Mapper.Map<List<Contact>, List<Models.ContactModel>>(contacts);

    return View(theList);
}

我想做的事情是否可能实现?
2个回答

9
IDataReader映射器非常简单,它可以通过数据读取器将对象填充出来,其中它通过列名映射对象属性。它并不旨在创建具有关系等复杂数据结构。
此外,DataSet.CreateDataReader将生成多个结果集数据读取器 - 即读取器将对每个表有几个结果集,但它不会保留关系。
因此,为了获得所需内容,您需要为每个表创建一个读取器,将每个读取器映射到不同的集合,然后使用这些结果来创建最终的复杂对象。
这里我提供了简单的方法,但您可以随意创建自定义解析器等,以封装所有内容。
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using AutoMapper;
using NUnit.Framework;

namespace StackOverflowExample.Automapper
{
    public class Contact
    {
        public Guid ContactId { get; set; }
        public string Name { get; set; }
        public List<Address> Addresses { get; set; }
    }

    public partial class Address
    {
        public Guid AddressId { get; set; }
        public Guid ContactId { get; set; }
        public string StreetAddress { get; set; }
    }

    [TestFixture]
    public class DatasetRelations
    {
        [Test]
        public void RelationMappingTest()
        {
            //arrange
            var firstContactGuid = Guid.NewGuid();
            var secondContactGuid = Guid.NewGuid();

            var addressTable = new DataTable("Addresses");
            addressTable.Columns.Add("AddressId");
            addressTable.Columns.Add("ContactId");
            addressTable.Columns.Add("StreetAddress");
            addressTable.Rows.Add(Guid.NewGuid(), firstContactGuid, "c1 a1");
            addressTable.Rows.Add(Guid.NewGuid(), firstContactGuid, "c1 a2");
            addressTable.Rows.Add(Guid.NewGuid(), secondContactGuid, "c2 a1");

            var contactTable = new DataTable("Contacts");
            contactTable.Columns.Add("ContactId");
            contactTable.Columns.Add("Name");
            contactTable.Rows.Add(firstContactGuid, "contact1");
            contactTable.Rows.Add(secondContactGuid, "contact2");

            var dataSet = new DataSet();
            dataSet.Tables.Add(contactTable);
            dataSet.Tables.Add(addressTable);

            Mapper.CreateMap<IDataReader, Address>();
            Mapper.CreateMap<IDataReader, Contact>().ForMember(c=>c.Addresses, opt=>opt.Ignore());

            //act
            var addresses = GetDataFromDataTable<Address>(dataSet, "Addresses");
            var contacts = GetDataFromDataTable<Contact>(dataSet, "Contacts");
            foreach (var contact in contacts)
            {
                contact.Addresses = addresses.Where(a => a.ContactId == contact.ContactId).ToList();
            }
        }

        private IList<T> GetDataFromDataTable<T>(DataSet dataSet, string tableName)
        {
            var table = dataSet.Tables[tableName];
            using (var reader = dataSet.CreateDataReader(table))
            {
                return Mapper.Map<IList<T>>(reader).ToList();
            }
        }
    }
} 

1
我本来就担心会得到这个答案,不过还是谢谢你提供的例子! - Arne Deruwe

1
我很晚才参加这个派对,但希望我的做法能帮到其他人。我使用Json.NET将数据集序列化为JSON字符串。
var datasetSerialized = JsonConvert.SerializeObject(dataset, Formatting.Indented);

在Visual Studio中调试时,将JSON视为字符串并将其复制到剪贴板。

然后在Visual Studio中转到编辑 -> 粘贴特殊 -> 以类形式粘贴JSON

然后,您将拥有每个带有关系的表的POCO。

最后,将您的JSON反序列化为当您粘贴JSON As Classes时创建的"RootObject"。

var rootObj = JsonConvert.DeserializeObject<RootObject>(datasetSerialized);

1
这是一个有趣的观察,但非常好。我会检查运行它。 - SO19

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接