2017-08-27 247 views
0

我有一個EF實體,我想使用流暢的語法將其屬性映射到具有函數的DTO。將實體映射到DTO

就拿用戶我能使其工作映射這樣說:

public Task<List<JournalTransactionModel>> GetAllJournalRecords() 
    { 
     var journalRecords = db.JournalTransactions 
      .Include(_ => _.JournalTransactionsAccounts) 
      .Include(_ => _.User) 
      .Select(_ => new JournalTransactionModel 
      { 
       JournalTransactionId = _.JournalTransactionId, 
       Date = _.Date, 
       Description = _.Description, 
       User = new UserModel 
       { 
        UserId = _.UserId, 
        FirstName = _.User.FirstName, 
        LastName = _.User.LastName, 
        FullName = _.User.FirstName + " " + _.User.LastName, 
        Email = _.User.Email, 
        UserName = _.User.UserName, 
        Password = _.User.Password, 
        UserRoleAndPermissions = new UserRoleModel 
        { 
         UserRoleId = _.User.UserRole.UserRoleId, 
         UserRoleName = _.User.UserRole.UserRoleName, 
         CanRead = _.User.UserRole.CanRead, 
         CanWrite = _.User.UserRole.CanWrite 
        } 
       }, 
       TransactionAccounts = _.JournalTransactionsAccounts.Select(j => new JournalTransactionAccountModel 
       { 
        JournalTransactionAccountId = j.Id, 
        JournalTransactionId = j.JournalTransactionId, 
        DebitAccount = j.DebitAccount != null ? new AccountModel 
        { 
         AccountId = j.DebitAccount.AccountId, 
         AccountCategoryName = j.DebitAccount.AccountCategory.AccountCategoryName, 
         AccountCategoryId = j.DebitAccount.AccountCategoryId, 
         AccountName = j.DebitAccount.AccountName, 
         IncreasesWhenDebited = j.DebitAccount.IncreasesWhenDebited 
        } : null, 
        CreditAccount = j.CreditAccount != null ? new AccountModel 
        { 
         AccountId = j.CreditAccount.AccountId, 
         AccountCategoryName = j.CreditAccount.AccountCategory.AccountCategoryName, 
         AccountCategoryId = j.CreditAccount.AccountCategoryId, 
         AccountName = j.CreditAccount.AccountName, 
         IncreasesWhenDebited = j.CreditAccount.IncreasesWhenDebited 
        } : null, 
        Amount = j.Amount, 
        Person = j.Person != null ? new PersonModel 
        { 
         PersonId = j.PersonId, 
         FirstName = j.Person.FirstName, 
         LastName = j.Person.LastName, 
         FullName = j.Person.FirstName + " " + j.Person.LastName, 
         Email = j.Person.Email, 
         SocialSecurityNumber = j.Person.SocialSecurityNumber, 
         PersonType = new PersonTypeModel 
         { 
          Id = j.Person.PeopleType.PeopleTypeId, 
          Name = j.Person.PeopleType.Name 
         } 
        } : null 
       }).ToList() 
      }).ToListAsync(); 
     return journalRecords; 
    } 

但是,當我試圖創建一個返回的usermodel我一直得到例外

public Task<List<JournalTransactionModel>> GetAllJournalRecords() 
    { 
     var journalRecords = db.JournalTransactions 
      .Include(_ => _.JournalTransactionsAccounts) 
      .Include(_ => _.User) 
      .Select(_ => new JournalTransactionModel 
      { 
       JournalTransactionId = _.JournalTransactionId, 
       Date = _.Date, 
       Description = _.Description, 
       User = MapUserToModel(_.User), 
       TransactionAccounts = _.JournalTransactionsAccounts.Select(j => new JournalTransactionAccountModel 
       { 
        JournalTransactionAccountId = j.Id, 
        JournalTransactionId = j.JournalTransactionId, 
        DebitAccount = j.DebitAccount != null ? new AccountModel 
        { 
         AccountId = j.DebitAccount.AccountId, 
         AccountCategoryName = j.DebitAccount.AccountCategory.AccountCategoryName, 
         AccountCategoryId = j.DebitAccount.AccountCategoryId, 
         AccountName = j.DebitAccount.AccountName, 
         IncreasesWhenDebited = j.DebitAccount.IncreasesWhenDebited 
        } : null, 
        CreditAccount = j.CreditAccount != null ? new AccountModel 
        { 
         AccountId = j.CreditAccount.AccountId, 
         AccountCategoryName = j.CreditAccount.AccountCategory.AccountCategoryName, 
         AccountCategoryId = j.CreditAccount.AccountCategoryId, 
         AccountName = j.CreditAccount.AccountName, 
         IncreasesWhenDebited = j.CreditAccount.IncreasesWhenDebited 
        } : null, 
        Amount = j.Amount, 
        Person = j.Person != null ? new PersonModel 
        { 
         PersonId = j.PersonId, 
         FirstName = j.Person.FirstName, 
         LastName = j.Person.LastName, 
         FullName = j.Person.FirstName + " " + j.Person.LastName, 
         Email = j.Person.Email, 
         SocialSecurityNumber = j.Person.SocialSecurityNumber, 
         PersonType = new PersonTypeModel 
         { 
          Id = j.Person.PeopleType.PeopleTypeId, 
          Name = j.Person.PeopleType.Name 
         } 
        } : null 
       }).ToList() 
      }).ToListAsync(); 
     return journalRecords; 

功能那裏有什麼不對?

這是我得到的消息:

「ExceptionMessage」:「LINQ到實體無法識別方法「ACS.Hub.BusinessLogic.Models.UserModel MapUserToModel(ACS.Hub.Repository.User) '方法,並且此方法不能轉換爲商店表達式。「

+0

EF查詢提供程序不支持查詢表達式樹中的自定義方法。如果你想輕鬆定義和重用映射,[AutoMapper](https://github.com/AutoMapper/AutoMapper)包(特別是使用'QueryableExtensions'' ProjectTo')就是爲此。 –

回答

1

LINQ在服務器上查詢表達式翻譯IQueryable查詢。在你的情況下不支持,因爲你的子查詢必須在內存上計算。 MapUserToModel方法想要使用計算的數據。如果要通過MapUserToModel方法填充用戶索引,則可以在Select數據之前使用ToList()獲取內存數據。

var journalRecords = db.JournalTransactions 
      .Include(_ => _.JournalTransactionsAccounts) 
      .Include(_ => _.User) 
      .ToList() 
      .Select(_ => new JournalTransactionModel 
      { 
       .... 
      } 
+0

哦,我現在明白了,但這是不好的做法,你不覺得嗎?如果我只是要求LINQ將所有表格中的所有內容都呈現給我,然後將它們映射到我的用戶模型,這會影響性能。 另一方面,如果我只是每次都映射類似的東西,例如我幾乎在每一個查詢中都使用這個用戶。這會導致如此多的冗餘代碼。 – Willy

+0

@Willy正確。不僅LINQ會創建一個查詢來加載表中的所有數據,但您也將執行In Memory中的映射,這意味着您正在浪費RAM內存,並且還處理在內存中執行計算的時間。儘管處理器的額外利用率非常小,但取決於您的Web應用程序所具有的用戶負載,它可能很容易成爲您的應用程序在單個服務器上可以並行使用多少用戶的負擔。所以記住這一點。 –

+0

@Willy查看我編輯的答案,解決冗餘代碼問題。 –

1

你不能在linq查詢中調用你自定義的方法。原因在於,在後臺LINQ將所有LINQ語句轉換爲有效的SQL語句。而SQL語言當然沒有一個名爲MapUserToModel的函數的定義。一種解決方法是從您的MapUserToModel方法中取出代碼,並將其直接放入LINQ查詢中。


編輯: 我們已經討論過,爲什麼你不能使用查詢中的自定義方法,我們希望在SQL服務器上執行,我們也討論了,這是不是一件好事使用ToList()並在內存中執行映射。但是,無論何時想要映射用戶,您都仍然面臨着編寫冗餘代碼的問題。由於我非常討厭看/看冗餘代碼,所以我花了一些時間來處理這種挑戰,並找到了一個解決方案,它可以讓您只在一個地方寫映射代碼,並在需要的地方使用它。因此,假設我們保留在數據庫中的原始用戶實體名爲User,我們的DTO類名爲UserModel。下面是代碼:

public class UserModel 
{ 
    public string Username { get; set; } 
    public string Email { get; set; } 
    public DateTime Birthday { get; set; } 

    public DbUser DbUser 
    { 
     set 
     { 
      Username = value.UserName; 
      Email = value.Email; 
      Birthday = value.Birthday; 
     } 
    } 
} 

這裏是我們如何使用:

var journalRecords = db.JournalTransactions 
.Include(_ => _.JournalTransactionsAccounts) 
.Include(_ => _.User) 
.Select(_ => new JournalTransactionModel 
{ 
    JournalTransactionId = _.JournalTransactionId, 
    Date = _.Date, 
    Description = _.Description, 
    User = new UserModel 
    { 
     DbUser = _ 
    } 
    ... 
}); 

我覺得代碼是自我解釋,但總之,這裏的技巧是,在我們的DTO的usermodel類我們爲原始DbUser實體添加一個屬性,我們只創建一個setter方法,它訪問DbUser中的每個屬性並將其分配給它自己的每個相應屬性。因此,如果您將來需要爲映射添加或刪除一些屬性,則只需轉到此一個setter方法並在此處實施更改。

1

您需要調用Select()之前調用toList(),但是這可能是嚴重降低查詢的性能。這就是爲什麼你會得到一個錯誤,因爲Linjan不知道如何將你的自定義方法翻譯成SQL,就像Dejanin說的那樣。但是,如果調用toList()方法,則會執行先前的查詢,而不再使用SQL完成操作。