从 'flat' table 加载导航属性(多对多)
Load navigation properties from 'flat' table (many to many)
我必须与现有的数据库集成,并使用 EF 实现了数据加载(我们只读,但从不使用此应用程序写入数据)。最后缺少的步骤是加载本地化数据。
模型如下所示:
UDC (UserDefinedCode) table 包含所有本地化值。每个其他实体都可以有 0-n 代码,必须从 UDC table.
中解析
这里有一个 SQL Sequenz 字段的例子:
SELECT sequenz.Zulassungsstatus,
udc.Beschreibung1
FROM [dbo].[Sequenz] sequenz
LEFT JOIN [dbo].[UserDefinedCode] udc ON udc.UserDefinedCode = 'MA_STATUS' AND udc.CodeValue = sequenz.Zulassungsstatus
相关实体:
public class SequenzEntity : TemporalEntity
{
public int Zulassungsnummer { get; set; }
public int Sequenznummer { get; set; }
public string Zulassungsstatus { get; set; }
public DateTime WiderrufVerzichtDatum { get; set; }
public string SequenzName { get; set; }
public string Zulassungsart { get; set; }
public int BasisSequenzNummer { get; set; }
public string Anwendungsgebiet { get; set; }
public int ChargenblockadeAktiv { get; set; }
public ApplikationsartEntity Applikationsart { get; set; }
public PraeparatEntity Praeparat { get; set; }
public IEnumerable<PackungEntity> Packungen { get; set; }
public IEnumerable<DeklarationEntity> Deklarationen { get; set; }
}
public class UserDefinedCodeEntity : TemporalEntity
{
public string SystemCode { get; set; }
public string UserDefinedCode { get; set; }
public string CodeValue { get; set; }
public string SprachCode { get; set; }
public string Beschreibung1 { get; set; }
public string Beschreibung2 { get; set; }
public string BeschreibungLang { get; set; }
}
正在 DbContext.OnModelCreating
中配置 EF 模型:
protected override void OnModelCreating(ModelBuilder builder)
{
// Configure our entities
builder.Entity<SequenzEntity>(ConfigureSequenzEntity);
builder.Entity<UserDefinedCodeEntity>(ConfigureUserDefinedCodeEntity);
[...]
base.OnModelCreating(builder);
}
private static void ConfigureSequenzEntity(EntityTypeBuilder<SequenzEntity> builder)
{
ConfigureTemporalEntity(builder);
builder.ToTable("Sequenz");
builder.HasKey(sequenzEntity => new {sequenzEntity.Zulassungsnummer, sequenzEntity.Sequenznummer});
builder.HasMany(sequenzEntity => sequenzEntity.Deklarationen)
.WithOne(deklarationEntity => deklarationEntity.Sequenz);
builder.HasMany(sequenzEntity => sequenzEntity.Packungen)
.WithOne(packungEntity => packungEntity.Sequenz)
.HasForeignKey(packungEntity => new {packungEntity.Zulassungsnummer, packungEntity.Sequenznummer});
}
private static void ConfigureUserDefinedCodeEntity(EntityTypeBuilder<UserDefinedCodeEntity> builder)
{
ConfigureTemporalEntity(builder);
builder.ToTable("UserDefinedCode");
builder.HasKey(userDefinedCodeEntity => new
{
userDefinedCodeEntity.SystemCode,
userDefinedCodeEntity.UserDefinedCode,
userDefinedCodeEntity.CodeValue,
userDefinedCodeEntity.SprachCode
});
}
private static void ConfigureTemporalEntity<TEntity>(EntityTypeBuilder<TEntity> builder)
where TEntity : TemporalEntity
{
ConfigureBaseEntity(builder);
builder.HasTemporalTable();
builder.Property(baseEntity => baseEntity.VersionValidityStart)
.HasColumnName("SysStartTime");
builder.Property(baseEntity => baseEntity.VersionValidityEnd)
.HasColumnName("SysEndTime");
}
private static void ConfigureBaseEntity<TEntity>(EntityTypeBuilder<TEntity> builder)
where TEntity : BaseEntity
{
builder.HasKey(baseEntity => baseEntity.Id);
}
我很难搞清楚如何设置模型和配置 EF 以加载导航属性。如果我添加
- 序列实体:
public IEnumerable<UserDefinedCodeEntity> ZulassungsstatusCodes { get; set; }
- 用户定义代码实体:
public IEnumerable<SequenzEntity> Sequenzen{ get; set; }
我显然得到了一个无法在 EF 中配置的多对多关系。
是否可以像我希望的那样使用 EF 加入 UDC table?
由于我找不到使用 EF 执行此操作的简洁方法,因此我选择了另一种方法,其中实体本身不知道语言相关属性。现在是业务层的工作来聚合字段。
创建一个包含所有必需属性的对象(投影):
public override async Task<PackungDto> GetAsync(int id, DateTime? asOf = null)
{
var packungProjection = new PackungDetailProjection
{
SprachCode = CultureInfo.CurrentUICulture.GetOneLetterLanguage(),
Packung = await _packungRepository.GetAsync(id, asOf, _includePropertiesGet),
ZulassungsstatusCodes = await _codeRepository.GetCodesAsync(CodeType.Zulassungsstatus, asOf: asOf)
};
return Mapper.Map<PackungDto>(packungProjection);
}
并让 AutoMapper 使用此配置文件进行映射:
internal sealed class PackungDetailProjectionToPackungDtoMappingProfile : Profile
{
#region Public Methods
public PackungDetailProjectionToPackungDtoMappingProfile()
{
CreateMap<PackungDetailProjection, PackungDto>()
// BaseEntity
.ForMember(dest => dest.Id, exp => exp.MapFrom(src => src.Packung.Id))
// Temporal Entity
.ForMember(dest => dest.VersionValidityStart, exp => exp.MapFrom(src => src.Packung.VersionValidityStart))
.ForMember(dest => dest.VersionValidityEnd, exp => exp.MapFrom(src => src.Packung.VersionValidityEnd))
// Packung
.ForMember(dest => dest.Gtin, exp => exp.MapFrom(src => src.Packung.Gtin))
.ForMember(dest => dest.Zulassungsnummer, exp => exp.MapFrom(src => src.Packung.Zulassungsnummer))
.ForMember(dest => dest.Sequenznummer, exp => exp.MapFrom(src => src.Packung.Sequenznummer))
.ForMember(dest => dest.Packungscode, exp => exp.MapFrom(src => src.Packung.Packungscode))
.ForMember(dest => dest.Name, exp => exp.MapFrom(src => $"{src.Packung.Sequenz.SequenzName.Trim()} {src.Packung.Packungsgroesse.Trim()}"))
// Codes
.ForMember(dest => dest.ZulassungsstatusDescription,
exp => exp.MapFrom(src => GetCodeDescription(src.ZulassungsstatusCodes.Where(entity => entity.CodeValue == src.Packung.Zulassungsstatus).ToList(),
src.SprachCode,
entity => entity.Beschreibung1)));
}
#endregion
#region Private Methods
/// <summary>
/// Gets the code description.
/// </summary>
/// <param name="codes">The codes.</param>
/// <param name="sprachCode">The sprach code.</param>
/// <param name="propertyValueSelector">
/// The selector specifying which property to use.
/// Usually <see cref="UserDefinedCodeEntity.Beschreibung1" />, <see cref="UserDefinedCodeEntity.Beschreibung2" /> or
/// <see cref="UserDefinedCodeEntity.BeschreibungLang" />
/// </param>
/// <returns>
/// The code description determined by following order:<br />
/// 1. Using the user langauge.<br />
/// 2. Using the default language.<br />
/// 3. <c>Null</c>.
/// </returns>
private static string GetCodeDescription(ICollection<UserDefinedCodeEntity> codes, string sprachCode, Func<UserDefinedCodeEntity, string> propertyValueSelector)
{
var codeDescription = codes.FirstOrDefault(entity => entity.SprachCode == sprachCode);
// Fall back to default langauge if the code could not be found for the user language
if (codeDescription == null && sprachCode != LanguageOneLetterConstants.DefaultCode)
{
codeDescription = codes.FirstOrDefault(entity => entity.SprachCode == LanguageOneLetterConstants.DefaultCode);
}
return codeDescription == null
? null
: propertyValueSelector(codeDescription);
}
#endregion
我必须与现有的数据库集成,并使用 EF 实现了数据加载(我们只读,但从不使用此应用程序写入数据)。最后缺少的步骤是加载本地化数据。
模型如下所示:
UDC (UserDefinedCode) table 包含所有本地化值。每个其他实体都可以有 0-n 代码,必须从 UDC table.
中解析这里有一个 SQL Sequenz 字段的例子:
SELECT sequenz.Zulassungsstatus,
udc.Beschreibung1
FROM [dbo].[Sequenz] sequenz
LEFT JOIN [dbo].[UserDefinedCode] udc ON udc.UserDefinedCode = 'MA_STATUS' AND udc.CodeValue = sequenz.Zulassungsstatus
相关实体:
public class SequenzEntity : TemporalEntity
{
public int Zulassungsnummer { get; set; }
public int Sequenznummer { get; set; }
public string Zulassungsstatus { get; set; }
public DateTime WiderrufVerzichtDatum { get; set; }
public string SequenzName { get; set; }
public string Zulassungsart { get; set; }
public int BasisSequenzNummer { get; set; }
public string Anwendungsgebiet { get; set; }
public int ChargenblockadeAktiv { get; set; }
public ApplikationsartEntity Applikationsart { get; set; }
public PraeparatEntity Praeparat { get; set; }
public IEnumerable<PackungEntity> Packungen { get; set; }
public IEnumerable<DeklarationEntity> Deklarationen { get; set; }
}
public class UserDefinedCodeEntity : TemporalEntity
{
public string SystemCode { get; set; }
public string UserDefinedCode { get; set; }
public string CodeValue { get; set; }
public string SprachCode { get; set; }
public string Beschreibung1 { get; set; }
public string Beschreibung2 { get; set; }
public string BeschreibungLang { get; set; }
}
正在 DbContext.OnModelCreating
中配置 EF 模型:
protected override void OnModelCreating(ModelBuilder builder)
{
// Configure our entities
builder.Entity<SequenzEntity>(ConfigureSequenzEntity);
builder.Entity<UserDefinedCodeEntity>(ConfigureUserDefinedCodeEntity);
[...]
base.OnModelCreating(builder);
}
private static void ConfigureSequenzEntity(EntityTypeBuilder<SequenzEntity> builder)
{
ConfigureTemporalEntity(builder);
builder.ToTable("Sequenz");
builder.HasKey(sequenzEntity => new {sequenzEntity.Zulassungsnummer, sequenzEntity.Sequenznummer});
builder.HasMany(sequenzEntity => sequenzEntity.Deklarationen)
.WithOne(deklarationEntity => deklarationEntity.Sequenz);
builder.HasMany(sequenzEntity => sequenzEntity.Packungen)
.WithOne(packungEntity => packungEntity.Sequenz)
.HasForeignKey(packungEntity => new {packungEntity.Zulassungsnummer, packungEntity.Sequenznummer});
}
private static void ConfigureUserDefinedCodeEntity(EntityTypeBuilder<UserDefinedCodeEntity> builder)
{
ConfigureTemporalEntity(builder);
builder.ToTable("UserDefinedCode");
builder.HasKey(userDefinedCodeEntity => new
{
userDefinedCodeEntity.SystemCode,
userDefinedCodeEntity.UserDefinedCode,
userDefinedCodeEntity.CodeValue,
userDefinedCodeEntity.SprachCode
});
}
private static void ConfigureTemporalEntity<TEntity>(EntityTypeBuilder<TEntity> builder)
where TEntity : TemporalEntity
{
ConfigureBaseEntity(builder);
builder.HasTemporalTable();
builder.Property(baseEntity => baseEntity.VersionValidityStart)
.HasColumnName("SysStartTime");
builder.Property(baseEntity => baseEntity.VersionValidityEnd)
.HasColumnName("SysEndTime");
}
private static void ConfigureBaseEntity<TEntity>(EntityTypeBuilder<TEntity> builder)
where TEntity : BaseEntity
{
builder.HasKey(baseEntity => baseEntity.Id);
}
我很难搞清楚如何设置模型和配置 EF 以加载导航属性。如果我添加
- 序列实体:
public IEnumerable<UserDefinedCodeEntity> ZulassungsstatusCodes { get; set; }
- 用户定义代码实体:
public IEnumerable<SequenzEntity> Sequenzen{ get; set; }
我显然得到了一个无法在 EF 中配置的多对多关系。
是否可以像我希望的那样使用 EF 加入 UDC table?
由于我找不到使用 EF 执行此操作的简洁方法,因此我选择了另一种方法,其中实体本身不知道语言相关属性。现在是业务层的工作来聚合字段。
创建一个包含所有必需属性的对象(投影):
public override async Task<PackungDto> GetAsync(int id, DateTime? asOf = null)
{
var packungProjection = new PackungDetailProjection
{
SprachCode = CultureInfo.CurrentUICulture.GetOneLetterLanguage(),
Packung = await _packungRepository.GetAsync(id, asOf, _includePropertiesGet),
ZulassungsstatusCodes = await _codeRepository.GetCodesAsync(CodeType.Zulassungsstatus, asOf: asOf)
};
return Mapper.Map<PackungDto>(packungProjection);
}
并让 AutoMapper 使用此配置文件进行映射:
internal sealed class PackungDetailProjectionToPackungDtoMappingProfile : Profile
{
#region Public Methods
public PackungDetailProjectionToPackungDtoMappingProfile()
{
CreateMap<PackungDetailProjection, PackungDto>()
// BaseEntity
.ForMember(dest => dest.Id, exp => exp.MapFrom(src => src.Packung.Id))
// Temporal Entity
.ForMember(dest => dest.VersionValidityStart, exp => exp.MapFrom(src => src.Packung.VersionValidityStart))
.ForMember(dest => dest.VersionValidityEnd, exp => exp.MapFrom(src => src.Packung.VersionValidityEnd))
// Packung
.ForMember(dest => dest.Gtin, exp => exp.MapFrom(src => src.Packung.Gtin))
.ForMember(dest => dest.Zulassungsnummer, exp => exp.MapFrom(src => src.Packung.Zulassungsnummer))
.ForMember(dest => dest.Sequenznummer, exp => exp.MapFrom(src => src.Packung.Sequenznummer))
.ForMember(dest => dest.Packungscode, exp => exp.MapFrom(src => src.Packung.Packungscode))
.ForMember(dest => dest.Name, exp => exp.MapFrom(src => $"{src.Packung.Sequenz.SequenzName.Trim()} {src.Packung.Packungsgroesse.Trim()}"))
// Codes
.ForMember(dest => dest.ZulassungsstatusDescription,
exp => exp.MapFrom(src => GetCodeDescription(src.ZulassungsstatusCodes.Where(entity => entity.CodeValue == src.Packung.Zulassungsstatus).ToList(),
src.SprachCode,
entity => entity.Beschreibung1)));
}
#endregion
#region Private Methods
/// <summary>
/// Gets the code description.
/// </summary>
/// <param name="codes">The codes.</param>
/// <param name="sprachCode">The sprach code.</param>
/// <param name="propertyValueSelector">
/// The selector specifying which property to use.
/// Usually <see cref="UserDefinedCodeEntity.Beschreibung1" />, <see cref="UserDefinedCodeEntity.Beschreibung2" /> or
/// <see cref="UserDefinedCodeEntity.BeschreibungLang" />
/// </param>
/// <returns>
/// The code description determined by following order:<br />
/// 1. Using the user langauge.<br />
/// 2. Using the default language.<br />
/// 3. <c>Null</c>.
/// </returns>
private static string GetCodeDescription(ICollection<UserDefinedCodeEntity> codes, string sprachCode, Func<UserDefinedCodeEntity, string> propertyValueSelector)
{
var codeDescription = codes.FirstOrDefault(entity => entity.SprachCode == sprachCode);
// Fall back to default langauge if the code could not be found for the user language
if (codeDescription == null && sprachCode != LanguageOneLetterConstants.DefaultCode)
{
codeDescription = codes.FirstOrDefault(entity => entity.SprachCode == LanguageOneLetterConstants.DefaultCode);
}
return codeDescription == null
? null
: propertyValueSelector(codeDescription);
}
#endregion