我有一个EF实体,我希望在使用流利语法时将其属性映射到带有函数的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 to Entities无法识别方法'ACS.Hub.BusinessLogic.Models.UserModel MapUserToModel(ACS.Hub.Repository.User)'方法,并且此方法无法转换为商店表达式。 “
答案 0 :(得分: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 :(得分:1)
LINQ
查询中翻译的 IQueryable
查询表达式。在您的情况下不受支持,因为您的子查询必须在内存上计算。 MapUserToModel
方法想要使用计算数据。如果您想通过MapUserToModel
方法填充用户属性,则可以在ToList()
数据之前使用Select
获取内存数据。
var journalRecords = db.JournalTransactions
.Include(_ => _.JournalTransactionsAccounts)
.Include(_ => _.User)
.ToList()
.Select(_ => new JournalTransactionModel
{
....
}
答案 2 :(得分:1)
您需要在调用toList()
之前调用Select()
,但这会严重降低查询的性能。这就是你得到错误的原因,因为Linq不知道如何将自定义方法转换为SQL,如Dejanin所说。但是,当您调用toList()
方法时,将执行上一个查询,并且不再使用SQL完成操作。