此 EF6 行为的原因是什么 Find() 不 return 代理但 Single() return 代理
What is the cause of this EF6 behaviour Find() does not return a proxy but Single() does return a proxy
我有一个 POCO class BankAccount
,它是 public
,所有成员都是 public properties
,导航属性设置为 virtual
。
Entity Framework 6.1.2 使用Find()
方法从数据库中正确加载它作为POCO。但是,据我所知,它应该是 return 代理 class 实例而不是 POCO 实例!事实上,当我使用 Single()
、SingleOrDefault()
、First()
或 FirstOrDefault()
时,Proxy class 实例是正确的 returned.
这是怎么回事,这是预期的行为吗?如果不是,是什么导致了这种情况的发生?
这是 POCO class:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Runtime.Serialization;
namespace AbcBankModels
{
//[DataContract]
[Table("BankAccount")]
public class BankAccount
{
[Key]
[Column(Order = 1)]
//[DataMember]
//[Range(0, 9999999)]
//[StringLength(7, MinimumLength = 7)]
//[Display(Name = "Account Number")]
public virtual string BankAccountId { get; set; }
[Key]
[Column(Order = 2)]
//[DataMember]
//[Range(0, 999999)]
//[StringLength(6, MinimumLength = 6)]
//[Display(Name = "Sort Code")]
public virtual string BankBranchId { get; set; }
//[DataMember]
public virtual BankBranch BankBranch { get; set; }
//[DataMember]
//[ForeignKey("ApplicationUser")]
public virtual string ApplicationUserId { get; set; }
//[DataMember]
public virtual User ApplicationUser { get; set; }
//[DataMember]
public virtual ICollection<BankCard> BankCardList { get; set; }
//[DataMember]
public virtual ICollection<BankTransaction> BankTransactionList { get; set; }
//[DataMember]
//[Display(Name = "Account Status")]
//[EnumDataType(typeof(EnumAccountStatus))]
public virtual EnumAccountStatus AccountStatus { get; set; }
//[DataMember]
//[Display(Name = "Account Type")]
//[EnumDataType(typeof(EnumBankAccountType))]
public virtual EnumBankAccountType AccountType { get; set; }
//[DataMember]
//[DataType(DataType.DateTime)]
//[Display(Name = "Date Account Opened")]
public virtual DateTime? AccountCreationDateTime { get; set; }
//[DataMember]
//[DataType(DataType.DateTime)]
//[Display(Name = "Date Account Closed")]
public virtual DateTime? AccountClosureDateTime { get; set; }
//[DataMember]
//[DataType(DataType.Currency)]
//[Display(Name = "Account Overdraft Limit")]
public virtual decimal AccountOverdraft { get; set; }
//[DataMember]
//[Display(Name = "Account Overdraft Interest Rate")]
public virtual decimal AccountOverdraftInterestRate { get; set; }
//[DataMember]
//[Display(Name = "Account Overdraft Usage Monthly Fee")]
public virtual decimal AccountOverdraftFacilityMonthlyCost { get; set; }
//[DataMember]
//[Display(Name = "Account Monthly Fee")]
public virtual decimal AccountMonthlyCost { get; set; }
//[DataMember]
//[Display(Name = "Account Interest Rate")]
public virtual decimal AccountInterestRate { get; set; }
}
}
这是执行 return 代理的方法:
public static BankAccount FindBankAccount(ApplicationDbContext applicationDbContext, string bankAccountId, string bankBranchId, string userId)
{
if (String.IsNullOrWhiteSpace(bankAccountId) || String.IsNullOrWhiteSpace(bankBranchId)) return null;
var bankAccount = applicationDbContext.BankAccountList.SingleOrDefault(a => a.BankAccountId == bankAccountId && a.BankBranchId == bankBranchId);
if (bankAccount == null) return null;
if (string.IsNullOrWhiteSpace(userId)) return bankAccount;
if (bankAccount.ApplicationUserId != userId) return null;
return bankAccount;
}
这里是没有return代理的方法:
public static BankAccount FindBankAccount(ApplicationDbContext applicationDbContext, string bankAccountId,
string bankBranchId, string userId)
{
if (String.IsNullOrWhiteSpace(bankAccountId) || String.IsNullOrWhiteSpace(bankBranchId)) return null;
var bankAccount = applicationDbContext.BankAccountList.Find(bankAccountId, bankBranchId);
if (bankAccount == null) return null;
if (string.IsNullOrWhiteSpace(userId)) return bankAccount;
if (bankAccount.ApplicationUserId != userId) return null;
return bankAccount;
}
如果您的上下文在您查询时已经使用该密钥跟踪非代理 BankAccount,则可能会发生这种情况。
奇怪的是,虽然 First 和 Single 总是查询数据库,但它们应该 return 与 Find 相同的实体。
例如,如果您有一个单元测试运行此代码:
Foo nonProxy = new Foo { Id = 4, Name = "Foo 4" }; // values correspond to an existing entity in db
ApplicationDbContext ctx = new ApplicationDbContext();
ctx.Foos.Attach(nonProxy);
Assert.AreSame(nonProxy, ctx.Foos.Find(nonProxy.Id));
Assert.AreSame(nonProxy, ctx.Foos.First(c => c.Name == "Foo 4"));
Assert.AreSame(nonProxy, ctx.Foos.FirstOrDefault(c => c.Name == "Foo 4"));
Assert.AreSame(nonProxy, ctx.Foos.Single(c => c.Name == "Foo 4"));
Assert.AreSame(nonProxy, ctx.Foos.SingleOrDefault(c => c.Name == "Foo 4"));
ctx = new ApplicationDbContext();
Foo proxy = ctx.Foos.Find(nonProxy.Id);
Assert.AreSame(proxy, ctx.Foos.Find(nonProxy.Id));
Assert.AreSame(proxy, ctx.Foos.First(c => c.Name == "Foo 4"));
Assert.AreSame(proxy, ctx.Foos.FirstOrDefault(c => c.Name == "Foo 4"));
Assert.AreSame(proxy, ctx.Foos.Single(c => c.Name == "Foo 4"));
Assert.AreSame(proxy, ctx.Foos.SingleOrDefault(c => c.Name == "Foo 4"));
那么它应该 运行 没有错误。在上下文跟踪具有相同键的实体后,它们都 return 对同一对象的引用。
// Query for the Blog named ADO.NET Blog
var blog = context.Blogs.Where(b => b.Name == "ADO.NET Blog").FirstOrDefault();
When results are returned from the database, objects that do not exist
in the context are attached to the context. If an object is already in
the context, the existing object is returned (the current and original
values of the object's properties in the entry are not overwritten
with database values).
因此,您可能正在使用 Find 的情况下,已经存在一个非代理实体,该实体具有上下文跟踪的该键,或者您在代码中的某个地方禁用了 Configuration.ProxyCreationEnabled,然后再使用它。
也许还有用:DbSet.Find Method
我有一个 POCO class BankAccount
,它是 public
,所有成员都是 public properties
,导航属性设置为 virtual
。
Entity Framework 6.1.2 使用Find()
方法从数据库中正确加载它作为POCO。但是,据我所知,它应该是 return 代理 class 实例而不是 POCO 实例!事实上,当我使用 Single()
、SingleOrDefault()
、First()
或 FirstOrDefault()
时,Proxy class 实例是正确的 returned.
这是怎么回事,这是预期的行为吗?如果不是,是什么导致了这种情况的发生?
这是 POCO class:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Runtime.Serialization;
namespace AbcBankModels
{
//[DataContract]
[Table("BankAccount")]
public class BankAccount
{
[Key]
[Column(Order = 1)]
//[DataMember]
//[Range(0, 9999999)]
//[StringLength(7, MinimumLength = 7)]
//[Display(Name = "Account Number")]
public virtual string BankAccountId { get; set; }
[Key]
[Column(Order = 2)]
//[DataMember]
//[Range(0, 999999)]
//[StringLength(6, MinimumLength = 6)]
//[Display(Name = "Sort Code")]
public virtual string BankBranchId { get; set; }
//[DataMember]
public virtual BankBranch BankBranch { get; set; }
//[DataMember]
//[ForeignKey("ApplicationUser")]
public virtual string ApplicationUserId { get; set; }
//[DataMember]
public virtual User ApplicationUser { get; set; }
//[DataMember]
public virtual ICollection<BankCard> BankCardList { get; set; }
//[DataMember]
public virtual ICollection<BankTransaction> BankTransactionList { get; set; }
//[DataMember]
//[Display(Name = "Account Status")]
//[EnumDataType(typeof(EnumAccountStatus))]
public virtual EnumAccountStatus AccountStatus { get; set; }
//[DataMember]
//[Display(Name = "Account Type")]
//[EnumDataType(typeof(EnumBankAccountType))]
public virtual EnumBankAccountType AccountType { get; set; }
//[DataMember]
//[DataType(DataType.DateTime)]
//[Display(Name = "Date Account Opened")]
public virtual DateTime? AccountCreationDateTime { get; set; }
//[DataMember]
//[DataType(DataType.DateTime)]
//[Display(Name = "Date Account Closed")]
public virtual DateTime? AccountClosureDateTime { get; set; }
//[DataMember]
//[DataType(DataType.Currency)]
//[Display(Name = "Account Overdraft Limit")]
public virtual decimal AccountOverdraft { get; set; }
//[DataMember]
//[Display(Name = "Account Overdraft Interest Rate")]
public virtual decimal AccountOverdraftInterestRate { get; set; }
//[DataMember]
//[Display(Name = "Account Overdraft Usage Monthly Fee")]
public virtual decimal AccountOverdraftFacilityMonthlyCost { get; set; }
//[DataMember]
//[Display(Name = "Account Monthly Fee")]
public virtual decimal AccountMonthlyCost { get; set; }
//[DataMember]
//[Display(Name = "Account Interest Rate")]
public virtual decimal AccountInterestRate { get; set; }
}
}
这是执行 return 代理的方法:
public static BankAccount FindBankAccount(ApplicationDbContext applicationDbContext, string bankAccountId, string bankBranchId, string userId)
{
if (String.IsNullOrWhiteSpace(bankAccountId) || String.IsNullOrWhiteSpace(bankBranchId)) return null;
var bankAccount = applicationDbContext.BankAccountList.SingleOrDefault(a => a.BankAccountId == bankAccountId && a.BankBranchId == bankBranchId);
if (bankAccount == null) return null;
if (string.IsNullOrWhiteSpace(userId)) return bankAccount;
if (bankAccount.ApplicationUserId != userId) return null;
return bankAccount;
}
这里是没有return代理的方法:
public static BankAccount FindBankAccount(ApplicationDbContext applicationDbContext, string bankAccountId,
string bankBranchId, string userId)
{
if (String.IsNullOrWhiteSpace(bankAccountId) || String.IsNullOrWhiteSpace(bankBranchId)) return null;
var bankAccount = applicationDbContext.BankAccountList.Find(bankAccountId, bankBranchId);
if (bankAccount == null) return null;
if (string.IsNullOrWhiteSpace(userId)) return bankAccount;
if (bankAccount.ApplicationUserId != userId) return null;
return bankAccount;
}
如果您的上下文在您查询时已经使用该密钥跟踪非代理 BankAccount,则可能会发生这种情况。
奇怪的是,虽然 First 和 Single 总是查询数据库,但它们应该 return 与 Find 相同的实体。
例如,如果您有一个单元测试运行此代码:
Foo nonProxy = new Foo { Id = 4, Name = "Foo 4" }; // values correspond to an existing entity in db
ApplicationDbContext ctx = new ApplicationDbContext();
ctx.Foos.Attach(nonProxy);
Assert.AreSame(nonProxy, ctx.Foos.Find(nonProxy.Id));
Assert.AreSame(nonProxy, ctx.Foos.First(c => c.Name == "Foo 4"));
Assert.AreSame(nonProxy, ctx.Foos.FirstOrDefault(c => c.Name == "Foo 4"));
Assert.AreSame(nonProxy, ctx.Foos.Single(c => c.Name == "Foo 4"));
Assert.AreSame(nonProxy, ctx.Foos.SingleOrDefault(c => c.Name == "Foo 4"));
ctx = new ApplicationDbContext();
Foo proxy = ctx.Foos.Find(nonProxy.Id);
Assert.AreSame(proxy, ctx.Foos.Find(nonProxy.Id));
Assert.AreSame(proxy, ctx.Foos.First(c => c.Name == "Foo 4"));
Assert.AreSame(proxy, ctx.Foos.FirstOrDefault(c => c.Name == "Foo 4"));
Assert.AreSame(proxy, ctx.Foos.Single(c => c.Name == "Foo 4"));
Assert.AreSame(proxy, ctx.Foos.SingleOrDefault(c => c.Name == "Foo 4"));
那么它应该 运行 没有错误。在上下文跟踪具有相同键的实体后,它们都 return 对同一对象的引用。
// Query for the Blog named ADO.NET Blog
var blog = context.Blogs.Where(b => b.Name == "ADO.NET Blog").FirstOrDefault();
When results are returned from the database, objects that do not exist in the context are attached to the context. If an object is already in the context, the existing object is returned (the current and original values of the object's properties in the entry are not overwritten with database values).
因此,您可能正在使用 Find 的情况下,已经存在一个非代理实体,该实体具有上下文跟踪的该键,或者您在代码中的某个地方禁用了 Configuration.ProxyCreationEnabled,然后再使用它。
也许还有用:DbSet.Find Method