我已经使用Entity Framework大约8个月了,我最近从版本4或5更新到6.1.13。
我编写了以下通用DAO类:
using System;
using System.Linq;
using System.Reflection;
using System.Data.Entity;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Entidades;
using Repositorio.Context;
namespace Repositorio.DAO
{
/// <summary>
/// Classe generica para operacoes (CRUD) relacionadas com o banco de dados.
/// Para cada entidade do tipo TBean, um DAO devera ser criado.
/// </summary>
public class DAO<TBean> : IDisposable where TBean : Bean
{
/// <summary>
/// Set de entidades
/// </summary>
protected DbSet<TBean> set;
/// <summary>
/// Contexto das entidades
/// </summary>
protected DatabaseContext context;
/// <summary>
/// Construtor. Inicializa atributos internos.
/// </summary>
public DAO()
{
context = new DatabaseContext();
set = context.Set<TBean>();
}
/// <summary>
/// Destroi este DAO.
/// </summary>
public void Dispose()
{
if (context != null)
{
context.Dispose();
}
}
/// <summary>
/// Metodo de commit para transacoes com auto-commit desabilitado
/// </summary>
public void SaveChanges()
{
context.SaveChanges();
}
/// <summary>
/// Insere ou atualiza um objeto do tipo TBean. Em atualizacoes, todos os atributos sao sobrepostos (atualizados).
/// </summary>
public void CreateUpdate(TBean bean, bool commit = true)
{
// Se entidade nao tiver um id, ela sera adicionada
if (bean.Id.Equals(default(int)))
{
Create(bean, commit);
}
else
{
Update(bean, commit);
}
}
/// <summary>
/// Cria uma entidade do tipo TBean.
/// </summary>
public virtual void Create(TBean bean, bool commit = true, int committer = 0)
{
SetBeanState(EntityState.Added, bean, commit);
}
/// <summary>
/// Atualiza/sobescreve uma entidade do tipo TBean.
/// </summary>
public virtual void Update(TBean bean, bool commit = true, int committer = 0)
{
SetBeanState(EntityState.Modified, bean, commit);
}
/// <summary>
/// Deleta uma entidade do tipo TBean.
/// </summary>
public virtual void Delete(TBean bean, bool commit = true, int committer = 0)
{
SetBeanState(EntityState.Deleted, bean, commit);
}
/// <summary>
/// Carrega, de forma lazy (preguicosa), todos os resultados de um objeto do tipo TBean.
/// </summary>
public virtual IQueryable<TBean> List()
{
return ListEager(bean => true, null);
}
/// <summary>
/// Carrega, de forma lazy (preguicosa), todos os resultados de um objeto do tipo TBean, dado um filtro.
/// </summary>
public virtual IQueryable<TBean> List(Expression<Func<TBean, bool>> filter)
{
return ListEager(filter, null);
}
/// <summary>
/// Carrega, de forma eager (gulosa), todos os resultados de um objeto do tipo TBean.
/// </summary>
public IQueryable<TBean> ListEager(Expression<Func<TBean, object>> include)
{
return ListEager(bean => true, include);
}
/// <summary>
/// Carrega, de forma eager (gulosa), todos os resultados de um objeto do tipo TBean.
/// </summary>
public IQueryable<TBean> ListEager(Expression<Func<TBean, bool>> filter, Expression<Func<TBean, object>> include)
{
IQueryable<TBean> query = set.AsQueryable();
// Se houver includes, valida e adiciona
if (include != null)
{
foreach (var member in RetrieveForeignKeyMembers(include))
{
query = query.Include(member.Name);
}
}
return query.Where(filter);
}
/// <summary>
/// Procura por um objeto do tipo TBean, dado um identificador
/// </summary>
public virtual TBean Search(int id)
{
return SearchEager(bean => bean.Id == id, null);
}
/// <summary>
/// Procura por um objeto do tipo TBean, dado um filtro
/// </summary>
public virtual TBean Search(Expression<Func<TBean, bool>> filter)
{
return SearchEager(filter, null);
}
/// <summary>
/// Procura por um objeto do tipo TBean, usando eager loading, dado um identificador.
/// </summary>
public TBean SearchEager(int id, Expression<Func<TBean, object>> include)
{
return SearchEager(bean => bean.Id == id, include);
}
/// <summary>
/// Procura por um objeto do tipo TBean, usando eager loading, dado um filtro.
/// </summary>
public TBean SearchEager(Expression<Func<TBean, bool>> filter, Expression<Func<TBean, object>> include)
{
IQueryable<TBean> query = set.AsQueryable();
// Se houver includes, valida e adiciona
if (include != null)
{
foreach (var member in RetrieveForeignKeyMembers(include))
{
query = query.Include(member.Name);
}
}
return query.FirstOrDefault(filter);
}
/// <summary>
/// Retorna todos os membros definidos como chave estrangeira, a partir de uma Expression.
/// Ex.:
/// 1. attr => attr.AlgumaCoisa
/// 2. attrs => new { attr.AlgumaCoisa, attr.AlgumaOutraCoisa, attr.MaisCoisa }
/// </summary>
protected List<MemberInfo> RetrieveForeignKeyMembers(Expression<Func<TBean, object>> expression)
{
List<MemberInfo> members = new List<MemberInfo>();
// Apenas expressoes lambdas sao validas para definicao de uma ForeignKey
if (expression.NodeType.Equals(ExpressionType.Lambda))
{
// Captura membros. Em new expressions, Members nao contem CustomAttributes!
switch (expression.Body.NodeType)
{
case ExpressionType.MemberAccess:
members.Add((expression.Body as MemberExpression).Member);
break;
case ExpressionType.New:
members.AddRange((expression.Body as NewExpression).Arguments.Select(arg => (arg as MemberExpression).Member));
break;
}
}
// Para cada membro, checa se este possui um ForeignKeyAttribute
if (members.Any(member => member.GetCustomAttributes<ForeignKeyAttribute>().Count() == 0))
{
throw new ArgumentException("Include inválido!", "expression");
}
return members;
}
protected void SetBeanState(EntityState state, TBean bean, bool commit = true)
{
// Atributos correspondentes a chave primaria nao podem ser invalidos!
context.Entry(bean).State = state;
if (commit)
{
context.SaveChanges();
}
}
}
}
我认为它按预期工作,直到昨天。我注意到即使将'LazyLoadingEnabled'设置为true(默认)并且没有Include()调用,也会加载虚拟字段。
让我们以这个bean类为例(不幸的是我不能粘贴所有东西,比如Bean,IAjustepreco,IAuditable接口等)。另请注意,所有bean配置都在Configuration类中完成。 ForeignKey属性仅用于标记导航属性,因此忽略它们的“NAVIGATION_PROPERTY”值,因为它们不能为空或为空。这是:
using System;
using System.ComponentModel.DataAnnotations.Schema;
using Entidades.Enums;
using Entidades.Interfaces;
using Entidades.Interfaces.Auditoria;
namespace Entidades
{
/// <summary>
/// Ajustes de preco de um determinado material.
/// Assessores poderao propor novos precos para seus clientes,
/// submetendo-os a um workflow de aprovacao por parte dos seus gerentes,
/// superintendente, diretores comerciais e diretor de logistica.
/// </summary>
[Table("TB_AJUSTE_PRECO", Schema = "dbo")]
public class AjustePreco : Bean, IAjustePreco, IAuditable<AjustePrecoAudition>
{
/// <summary>
/// Identificador deste objeto.
/// </summary>
[Column("ID")]
public override int Id { get; set; }
/// <summary>
/// ID do usuario que solicitou o ajuste (FK).
/// </summary>
[Column("ID_USUARIO_SOLICITANTE")]
public int UsuarioCriador { get; set; }
/// <summary>
/// Data em que este ajuste foi solicitado.
/// </summary>
[Column("DT_SOLICITACAO")]
public DateTime DataCriacao { get; set; }
/// <summary>
/// ID do motivo pelo qual este ajuste foi submetido (FK).
/// </summary>
[Column("ID_MOTIVO_SUBMISSAO")]
public int MotivoSubmissaoId { get; set; }
/// <summary>
/// ID do perfil ao qual este ajuste se encontra pendente.
/// </summary>
[Column("TP_PERFIL")]
public PerfilAcesso Perfil { get; set; }
/// <summary>
/// Estado atual deste ajuste.
/// </summary>
[Column("STATUS")]
public AjustePrecoStatus Status { get; set; }
/// <summary>
/// Data de validade (vingencia) desse ajuste.
/// </summary>
[Column("DT_VALIDADE")]
public DateTime DataValidade { get; set; }
/// <summary>
/// Valor do preco final proposto por alguem (assessor).
/// Este valor sera sugerido como um novo valor para algum material de algum cliente.
/// </summary>
[Column("PRECO_FINAL_NOVO")]
public decimal PrecoFinalNovo { get; set; }
/// <summary>
/// Valor do preco final do material no momento em que este ajuste foi solicitado (valor atual).
/// Este valor e utilizado apenas para validacao da atualizacao deste ajuste.
/// </summary>
[Column("PRECO_FINAL_ATUAL")]
public decimal PrecoFinalAtual { get; set; }
/// <summary>
/// Valor do acrescimo/desconto do material no momento em que este ajuste foi solicitado (valor atual).
/// </summary>
[Column("VL_AJUSTE_ATUAL")]
public decimal ValorAjusteAtual { get; set; }
/// <summary>
/// Valor do novo ajuste. Em casos onde nao ha imposto ICMS ST, o calculo desta
/// variavel sera (PrecoFinalNovo - PrecoFinalAtual) + ValorAjusteAtual.
/// Caso contrario, havera um webservice para calcular este valor.
/// Ao concluir o workflow (todas as aprovacoes), este valor sera enviado
/// para o SAP para substituir o ajuste corrente de um usuario.
/// </summary>
[Column("VL_AJUSTE_NOVO")]
public decimal ValorAjusteNovo { get; set; }
/// <summary>
/// Nome do cliente favorecido.
/// </summary>
[Column("NM_CLIENTE")]
public string NomeCliente { get; set; }
/// <summary>
/// Nome do material ao qual este ajuste se refere.
/// </summary>
[Column("NM_MATERIAL")]
public string NomeMaterial { get; set; }
/// <summary>
/// Nome do centro
/// </summary>
[Column("NM_CENTRO")]
public string NomeCentro { get; set; }
/// <summary>
/// Nome da condição de pagamento.
/// </summary>
[Column("NM_CONDICAO_PAGAMENTO")]
public string NomeCondicaoPagamento { get; set; }
/// <summary>
/// Nome da bandeira do cliente.
/// </summary>
[Column("NM_BANDEIRA")]
public string NomeBandeira { get; set; }
/// <summary>
/// Nome do frete utilizado.
/// </summary>
[Column("NM_FRETE")]
public string NomeFrete { get; set; }
/// <summary>
/// Id do cliente, no SAP, o qual sera atribuido o ajuste
/// </summary>
[Column("CD_CLIENTE")]
public string CodigoCliente { get; set; }
/// <summary>
/// Codigo do material, em string, com padding left zeros.
/// </summary>
[Column("CD_MATERIAL")]
public string CodigoMaterial { get; set; }
/// <summary>
/// Codigo do Centro, em string.
/// Apesar de nao conter letras em seus codigos, algum dia o tipo de
/// dados podera ser alterado. Isto explica o tipo 'string'.
/// </summary>
[Column("CD_CENTRO")]
public string CodigoCentro { get; set; }
/// <summary>
/// Codigo da condição de pagamento: C000, C001 e etc.
/// </summary>
[Column("CD_CONDICAO_PAGAMENTO")]
public string CodigoCondicaoPagamento { get; set; }
/// <summary>
/// Codigo da bandeira do cliente favorecido.
/// </summary>
[Column("CD_BANDEIRA")]
public string CodigoBandeira { get; set; }
/// <summary>
/// Codigo do frete utilizado.
/// </summary>
[Column("CD_FRETE")]
public string CodigoFrete { get; set; }
/// <summary>
/// Distancia entre o cliente e a base mais proxima
/// </summary>
[Column("DISTANCIA_ATUAL")]
public decimal DistanciaAtual { get; set; }
/// <summary>
/// [NotMapped]
/// Usuario que solicitou este ajuste (Navigation property).
/// </summary>
[ForeignKey("NAVIGATION_PROPERTY")]
public Usuario Solicitante { get; private set; }
/// <summary>
/// [NotMapped]
/// Motivo de submissao deste ajuste (Navigation property).
/// </summary>
[ForeignKey("NAVIGATION_PROPERTY")]
public AjustePrecoMotivo MotivoSubmissao { get; private set; }
/// <summary>
/// [NotMapped]
/// Preco de custo do material ao qual este ajuste se refere.
/// Esta diretamente relacionada a ZFMSD_CALCULA_PRECO_PORTAL.E_VL_CUSTO_MEDIO_MOVEL.
/// </summary>
//[Column("PRECO_CUSTO_MEDIA_MOVEL")]
[NotMapped]
public decimal PrecoCustoMediaMovel { get; set; }
/// <summary>
/// [NotMapped]
/// </summary>
[NotMapped]
public decimal PrecoCustoUtilizado
{
get { return PrecoBase - ValorMargem - ValorOverhead; }
}
/// <summary>
/// [NotMapped]
/// Valor da diferenca entre o preco proposto e o preco atual.
/// </summary>
[NotMapped]
public decimal PrecoFinalDelta
{
get { return PrecoFinalAtual - PrecoFinalNovo; }
}
/// <summary>
/// [NotMapped]
/// Preco base do material ao qual este ajuste se refere.
/// Este dado e obtido atraves de webservice.
/// </summary>
[NotMapped]
public decimal PrecoBase { get; set; }
/// <summary>
/// [NotMapped]
/// Valor do juros aplicado sobre a condicao de pagamento do material
/// ao qual este ajuste se refere.
/// Este dado e obtido atraves de webservice.
/// </summary>
[NotMapped]
public decimal ValorFinanceiro { get; set; }
/// <summary>
/// [NotMapped]
/// Valor do frete do material ao qual este ajuste se refere.
/// Este dado e obtido atraves de webservice.
/// </summary>
[NotMapped]
public decimal ValorFrete { get; set; }
/// <summary>
/// [NotMapped]
/// Valor do investimento (ROI) do material ao qual este ajuste se refere.
/// Este dado e obtido atraves de webservice.
/// </summary>
[NotMapped]
public decimal ValorInvestimento { get; set; }
/// <summary>
/// [NotMapped]
/// Valor do rateio de todas as despesas da empresa incluso ao preco
/// de um material, o qual este ajuste se refere.
/// Este dado e obtido atraves de webservice.
/// </summary>
[NotMapped]
public decimal ValorOverhead { get; set; }
/// <summary>
/// [NotMapped]
/// Margem de lucro de um material ao qual este ajuste se refere.
/// Este dado e obtido atraves de webservice.
/// </summary>
[NotMapped]
public decimal ValorMargem { get; set; }
/// <summary>
/// [NotMapped]
/// Formula: ValorMargem + ValorInvestimento + ValorAjusteNovo.
/// </summary>
[NotMapped]
public decimal ValorMargemAjustada
{
get { return ValorMargem + ValorInvestimento + ValorAjusteNovo; }
}
/// <summary>
/// [NotMapped]
/// Formula: PrecoFinalAtual - ValorCustoUtilizado - ValorInvestimento - ValorFrete - ValorFinanceiro.
/// </summary>
[NotMapped]
public decimal ValorMargemBruta
{
get { return PrecoFinalAtual - ValorInvestimento - ValorFrete - ValorFinanceiro; } // - ValorCustoUtilizado
}
/// <summary>
/// [NotMapped]
/// Identifica se este ajuste e um acrescimo ou um desconto.
/// </summary>
[NotMapped]
public bool IsAcrescimo
{
get { return PrecoFinalNovo - PrecoFinalAtual > 0M; }
}
/// <summary>
/// Construtor. Configura valores default.
/// </summary>
/// <remarks>
/// Atributos decimais devem ter as casas decimais configuradas,
/// caso contrario os valores persistidos serao "0" ao inves de "0.0000".
/// </remarks>
public AjustePreco()
{
PrecoFinalNovo = 0.0000M;
PrecoFinalAtual = 0.0000M;
ValorAjusteAtual = 0.0000M;
}
/// <summary>
/// Cria seu objeto de auditoria a partir do ponteiro para este objeto
/// </summary>
public AjustePrecoAudition CreateAudition()
{
return new AjustePrecoAudition(this);
}
}
}
现在,有缺陷的部分。这段代码按预期工作:
public static IEnumerable<AjustePreco> Listar(bool eager = true)
{
using (var dao = new AjustePrecoDAO())
{
if (eager)
{
return new List<AjustePreco>(dao.ListEager(ap => new { ap.Solicitante, ap.MotivoSubmissao }));
}
else
{
//var x = new List<AjustePreco>(dao.List()); return x; // Navigation properties are set (unexpected)
return new List<AjustePreco>(dao.List()); // Navigation properties are disposed (expected)
}
}
}
虽然这个没有(注意:下面真的很奇怪的行为):
public static void Test(Usuario usuario)
{
IEnumerable<AjustePreco> x0 = ManterAjustePreco.Listar(false);
IEnumerable<AjustePreco> x1 = null;
IEnumerable<AjustePreco> x2 = null;
IEnumerable<AjustePreco> x3 = null;
IEnumerable<AjustePreco> x4 = null;
using (var dao = new AjustePrecoDAO())
{
x1 = dao.List().ToList();
x2 = new List<AjustePreco>(dao.List(u => u.Perfil == PerfilAcesso.Gerente));
x3 = new List<AjustePreco>(dao.ListEager(i => i.Solicitante));
x4 = new List<AjustePreco>(dao.ListEager(a => a.Perfil == PerfilAcesso.Gerente, i => i.Solicitante));
}
// THIS IS THE WEIRDEST PART:
// Sometimes, running this same function multiple times would cause an exception in any of the next 3 lines.
var f = x0.ElementAt(0).MotivoSubmissao;
var g = x1.ElementAt(0).MotivoSubmissao;
var h = x2.ElementAt(0).MotivoSubmissao;
System.Diagnostics.Debug.WriteLine("Done");
}
这太奇怪了,我甚至不确定我是否可以清楚地解释它。如果不清楚,请告诉我。我很绝望。
非常感谢!
修改
在与Ivan进行简短讨论后,我从导航属性中删除了virtual
关键字。这导致我期待的真实行为,虽然我面临着另一个奇怪的事情,如下:
IEnumerable<AjustePreco> x1 = null;
IEnumerable<AjustePreco> x3 = null;
using (var dao = new AjustePrecoDAO())
{
x1 = dao.List().ToList(); // Gets modified
x1 = new List<AjustePreco>(dao.List()); // Gets modified
x1 = new List<AjustePreco>(dao.List()).AsReadOnly(); // Gets modified
x3 = new List<AjustePreco>(dao.ListEager(i => i.MotivoSubmissao));
}
所有3 x1装载都可以。确定,我的意思是所有导航属性都为null(正如预期的那样)。但是,当它加载x3(包括MotivoSubmissao导航属性)时,它会更改x1元素MotivoSubmissao值(从null到其他值)。我知道每个选择都会更新DbSet缓存,但如果它是一个sepparate var,它如何更改x1列表?!