I am developing a domain model using EF Code First to persist the data. I have to add support for multilanguage and I would like not to contaminate the domain model with location concepts.
I like that in database exists a ProductTranslate table with title and Language fields but in my domain title belongs to the Product entity.
Someone knows how to get this?
Here is what I use and works well with code first.
Define a base Translation class:
using System;
public abstract class Translation<T> where T : Translation<T>, new()
{
public Guid Id { get; set; }
public string CultureName { get; set; }
protected Translation()
{
Id = Guid.NewGuid();
}
}
Define a TranslationCollection class:
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
public class TranslationCollection<T> : Collection<T> where T : Translation<T>, new()
{
public T this[CultureInfo culture]
{
get
{
var translation = this.FirstOrDefault(x => x.CultureName == culture.Name);
if (translation == null)
{
translation = new T();
translation.CultureName = culture.Name;
Add(translation);
}
return translation;
}
set
{
var translation = this.FirstOrDefault(x => x.CultureName == culture.Name);
if (translation != null)
{
Remove(translation);
}
value.CultureName = culture.Name;
Add(value);
}
}
public T this[string culture]
{
get
{
var translation = this.FirstOrDefault(x => x.CultureName == culture);
if (translation == null)
{
translation = new T();
translation.CultureName = culture;
Add(translation);
}
return translation;
}
set
{
var translation = this.FirstOrDefault(x => x.CultureName == culture);
if (translation != null)
{
Remove(translation);
}
value.CultureName = culture;
Add(value);
}
}
public bool HasCulture(string culture)
{
return this.Any(x => x.CultureName == culture);
}
public bool HasCulture(CultureInfo culture)
{
return this.Any(x => x.CultureName == culture.Name);
}
}
You can then use those classes in your entities, e.g.:
using System;
using System.Globalization;
public class HelpTopic
{
public Guid Id { get; set; }
public string Name { get; set; }
public TranslationCollection<HelpTopicTranslation> Translations { get; set; }
public string Content
{
get { return Translations[CultureInfo.CurrentCulture].Content; }
set { Translations[CultureInfo.CurrentCulture].Content = value; }
}
public HelpTopic()
{
Id = Guid.NewGuid();
Translations = new TranslationCollection<HelpTopicTranslation>();
}
}
With HelpTopicTranslation defined as:
using System;
public class HelpTopicTranslation : Translation<HelpTopicTranslation>
{
public Guid Id { get; set; }
public Guid HelpTopicId { get; set; }
public string Content { get; set; }
public HelpTopicTranslation()
{
Id = Guid.NewGuid();
}
}
Now, for the code first specific side of things, use the following configuration:
using System.Data.Entity.ModelConfiguration;
using Model;
internal class HelpTopicConfiguration : EntityTypeConfiguration<HelpTopic>
{
public HelpTopicConfiguration()
{
Ignore(x => x.Content); // Ignore HelpTopic.Content since it's a 'computed' field.
HasMany(x => x.Translations).WithRequired().HasForeignKey(x => x.HelpTopicId);
}
}
And add it to your context configurations:
public class TestContext : DbContext
{
public DbSet<HelpTopic> HelpTopics { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new HelpTopicConfiguration());
}
}
When all of this is done, the following migration is generated:
using System.Data.Entity.Migrations;
public partial class AddHelpTopicTable : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.HelpTopics",
c => new
{
Id = c.Guid(false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.HelpTopicTranslations",
c => new
{
Id = c.Guid(false),
HelpTopicId = c.Guid(false),
Content = c.String(),
CultureName = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.HelpTopics", t => t.HelpTopicId, true)
.Index(t => t.HelpTopicId);
}
public override void Down()
{
DropForeignKey("dbo.HelpTopicTranslations", "HelpTopicId", "dbo.HelpTopics");
DropIndex("dbo.HelpTopicTranslations", new[] { "HelpTopicId" });
DropTable("dbo.HelpTopicTranslations");
DropTable("dbo.HelpTopics");
}
}
Any comments and/or improvements are welcome...
Related
I try to use EF core, but only a part of my model is saved to the database.
This is my model:
public class EngineType
{
public string Name { get; set; }
}
public class Car
{
public long CarId { get; set; }
public string Name { get; set; }
public EngineType Engine { get; set; }
}
The CarId and the Name is saved, but not the EngineType.
This is the test I use, but actual.Engine is always null:
[TestMethod]
public void WhenIAddAndSaveANewCarThenItIsAddedToDB()
{
using var target = new EFCoreExampleContext();
using var concurrentContext = new EFCoreExampleContext();
var expected = new Car() {CarId = 0815, Name = "Isetta", Engine = new EngineType() { Name = "2Takt" }};
target.Cars.Add(expected);
target.SaveChanges();
var actual = concurrentContext.Cars.Single();
Assert.AreEqual(1, concurrentContext.Cars.Count());
Assert.IsNotNull(actual.Engine);
Assert.AreEqual(expected, actual);
}
My Context looks like this:
public class EFCoreExampleContext : DbContext
{
public DbSet<Car> Cars { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase(databaseName: "Add_writes_to_database");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EngineType>(
d =>
{
d.HasKey(e => e.Name);
d.Property(e => e.Name).IsRequired();
});
modelBuilder.Entity<EngineType>(
d =>
{
d.HasKey(e => e.Name);
});
modelBuilder.Entity<Car>(
d =>
{
d.HasKey(e => e.CarId);
d.Property<DateTime>("LastChanged").IsRowVersion().ValueGeneratedOnAddOrUpdate();
d.Property<string>("EngineForeignKey");
d.HasOne(e => e.Engine)
.WithMany()
.HasForeignKey("EngineForeignKey")
.IsRequired();
});
}
}
Any idea what am I doing wrong (or which existing topic answers this question - I even didn't have the right search words to find it).
Thanks!
I think there is no issue with saving. Entity Framework does not do eager loading by default. So you have to explicitly include any navigational properties that should be in result. Try this when you are fetching actual,
using Microsoft.EntityFrameworkCore;
var actual = concurrentContext.Cars.Include(c => c.Engine).Single();
I got this error when try add "AppUserRoles" table by myself to access UserRoles from User, for example:
from u in _userManager.Users.Where(x => x.UserRoles "contain" "some codition here")
And I got this error:
A key cannot be configured on 'AppUserRoles' because it is a derived type. The key must be configured on the root type 'IdentityUserRole'. If you did not intend for 'IdentityUserRole' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.
This is my previous code, It is run ok:
builder.Entity<IdentityUserRole<Guid>>().ToTable("AppUserRoles")
.HasKey(x => new { x.RoleId, x.UserId });
--->And I change to this:
My user class
[Table("AppUsers")]
public class AppUser : IdentityUser<Guid>, IDateTracking, ISwitchable
{
public virtual ICollection<AppUserRoles> UserRoles { get; set; }
}
My role class
[Table("AppRoles")]
public class AppRole : IdentityRole<Guid>
{
public virtual ICollection<AppUserRoles> UserRoles { get; set; }
}
My appUserRole class:
[Table("AppUserRoles")]
public class AppUserRoles : IdentityUserRole<Guid>
{
public virtual AppUser User { get; set; }
public virtual AppRole Role { get; set; }
}
My DbContextClass
public class AppDbContext : IdentityDbContext<AppUser, AppRole, Guid>
{
public AppDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<AppUser> AppUsers { get; set; }
public DbSet<AppRole> AppRoles { get; set; }
public DbSet<AppUserRoles> AppUserRoles { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
#region Identity Config
builder.Entity<IdentityUserClaim<Guid>>().ToTable("AppUserClaims").HasKey(x => x.Id);
builder.Entity<IdentityUserLogin<Guid>>().ToTable("AppUserLogins").HasKey(x => x.UserId);
builder.Entity<AppUserRoles>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
builder.Entity<IdentityUserToken<Guid>>().ToTable("AppUserTokens")
.HasKey(x => new { x.UserId });
#endregion Identity Config
}
public override int SaveChanges()
{
var modified = ChangeTracker.Entries().Where(e => e.State == EntityState.Modified || e.State == EntityState.Added);
foreach (EntityEntry item in modified)
{
var changedOrAddedItem = item.Entity as IDateTracking;
if (changedOrAddedItem != null)
{
if (item.State == EntityState.Added)
{
changedOrAddedItem.DateCreated = DateTime.Now;
}
changedOrAddedItem.DateModified = DateTime.Now;
}
}
return base.SaveChanges();
}
}
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args)
{
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json").Build();
var builder = new DbContextOptionsBuilder<AppDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
return new AppDbContext(builder.Options);
}
}
This is my startup file:
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
o => o.MigrationsAssembly("YayoiApp.Data.EF")),
ServiceLifetime.Scoped);
Please give me some advise, I already have researched to much, but not found a solution. Thanks.
For custom IdentityUserRole<Guid>, you need to change your DbContext like
public class ApplicationDbContext : IdentityDbContext<AppUser
, AppRole
, Guid
, IdentityUserClaim<Guid>
, AppUserRoles
, IdentityUserLogin<Guid>
, IdentityRoleClaim<Guid>
, IdentityUserToken<Guid>>
{
Then register it in Startup.cs
services.AddIdentity<AppUser, AppRole>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
UseCase:
public class HomeController : Controller
{
private readonly UserManager<AppUser> _userManager;
private readonly RoleManager<AppRole> _roleManager;
public HomeController(UserManager<AppUser> userManager
, RoleManager<AppRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
public async Task<IActionResult> Index()
{
var userName = "Tom";
var passWord = "1qaz#WSX";
var appUser = new AppUser
{
UserName = userName
};
await _userManager.CreateAsync(appUser, passWord);
var roleName = "Admin";
await _roleManager.CreateAsync(new AppRole { Name = roleName });
await _userManager.AddToRoleAsync(appUser, roleName);
var roles = appUser.UserRoles;
return View();
}
I have multiple databases, 1 common and n company db. I use code first with one migration for common and one for the companies db.
I have a base context this is inherit to 2 contexts (common, company). I try to use only the base context and remove the specified contexts, so far no problem.
My problem is following, if i try to use linq on context.Set<> then I get an InvalidOperationException "The entity type NOCompany is not part of the model for the current context".
using (NOContext db = new NOContext(connection)) {
var dbset = db.Set<NOCompany>()
.Where(company => (company.Deleted == null) || (company.Deleted == false));
foreach (var item in dbset) {
System.Diagnostics.Debug.WriteLine(item.Matchcode);
}
}
if I use this
using (NOContext db = new NOCommonContext(connection)) {
var dbset = db.Set<NOCompany>()
.Where(company => (company.Deleted == null) || (company.Deleted == false));
foreach (var item in dbset) {
System.Diagnostics.Debug.WriteLine(item.Matchcode);
}
}
then it works fine. Where is the problem?
Following excerpt from the classes
public class NOContext : DbContext, INOContext
{
public NOContext() { }
public NOContext(string connection) : base(connection) { }
#region - override DbContext -
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<EntityBase>()) {
DateTime currentDateTime = DateTime.Now;
var entity = entry.Entity;
if (entry.State == EntityState.Added) {
entity.CreateDate = currentDateTime;
entity.CreateId = NOEngine.SessionInfo.CurrentUserId;
} else if (entry.State == EntityState.Deleted) {
entry.State = EntityState.Modified;
entity.Deleted = true;
entity.DeletedDate = currentDateTime;
entity.DeletedId = NOEngine.SessionInfo.CurrentUserId;
}
entity.ModifiedDate = currentDateTime;
entity.ModifiedId = NOEngine.SessionInfo.CurrentUserId;
}
return base.SaveChanges();
}
#endregion
//database methods
}
Then I have 2 specified context
public class NOCommonContext : NOContext
{
public const string CommonCatalog = "NOCommonDb";
public NOCommonContext() { }
public NOCommonContext(string connection) : base(connection) { }
#region - DbSets -
public virtual DbSet<NOUser> Users { get; set; }
public virtual DbSet<NOCompany> Companies { get; set; }
public virtual DbSet<NOConfig> AppConfiguration { get; set; }
#endregion //DbSets
}
and
public partial class NOAppContext : NOContext
{
public NOAppContext() { }
public NOAppContext(string connection) : base(connection) { }
#region - DbSets -
public virtual DbSet<BPCard> BPCards { get; set; }
public virtual DbSet<BPContact> BPContacts { get; set; }
public virtual DbSet<HRCard> HRCards { get; set; }
#endregion //DbSets
}
Can I get sample of how to test whether event is published from given handler or not.
var cmd = new Catalogue()
{
CatalogueCode = "",
CatalogueType = "",
CustomerSegmentCode = "",
DisplayName = "",
EffectiveDate = null,
Products = null
};
Test.Handler(bus => new CatalogueAddedCommandHandler(bus))
.ExpectPublish<ICatalogue>(e => e.CatalogueCode == cmd.CatalogueCode).OnMessage(cmd);
When I debug the test case it shows exception Interface not found at Test.Handler.
I think you need to call Test.Initialize();
You can find the documentation on the Particular docs website
UPDATE:
using NServiceBus;
using NServiceBus.Testing;
using NUnit.Framework;
[TestFixture]
public class Tests
{
[Test]
public void Run()
{
Test.Initialize(c => c.Conventions().DefiningEventsAs(t => t == typeof (ICatalogue)));
var cmd = new Catalogue()
{
CatalogueCode = "TEST"
};
Test.Handler<CatalogueAddedCommandHandler>()
.ExpectPublish<ICatalogue>(e => e.CatalogueCode == cmd.CatalogueCode)
.OnMessage(cmd);
}
}
public interface ICatalogue
{
string CatalogueCode { get; set; }
}
public class Catalogue
{
public string CatalogueCode { get; set; }
}
public class CatalogueEvent : ICatalogue
{
public string CatalogueCode { get; set; }
}
public class CatalogueAddedCommandHandler : IHandleMessages<Catalogue>
{
public IBus Bus { get; set; }
public void Handle(Catalogue message)
{
Bus.Publish<ICatalogue>(e => e.CatalogueCode = message.CatalogueCode);
}
}
Does this help?
On escalado, throws the exception. It throws with or wihtout Include.
static void Main(string[] args)
{
try
{
using (var context = new CKContext())
{
var servReprosWithIncludes = context.ServicioRepro
.Include(p => p.Categoria)
.ToList();
var escalado = context.EscaladoPrecio
//.Include(p => p.Servicio)
.ToList();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
InvalidOperationException: The value of a property that is part of an object's key does not match the corresponding property value stored in the ObjectContext. This can occur if properties that are part of the key return inconsistent or incorrect values or if DetectChanges is not called after changes are made to a property that is part of the key.
The mapping of EscaladoPrecio:
public class EscaladoPrecioMapping : EntityTypeConfiguration<EscaladoPrecio>
{
public EscaladoPrecioMapping()
{
base.HasKey(p => new { p.Desde, p.Hasta, p.ServicioReproId });
base.HasRequired(p => p.Servicio)
.WithMany()
.HasForeignKey(p => p.ServicioReproId);
base.ToTable("PreciosServicioReprografia");
}
}
The entity ServicioRepro is a part from TPT hierarchy. Looks like:
public class ServicioRepro : Producto
{
public bool IncluirPrecioClick { get; set; }
public bool IncluirPrecioPapel { get; set; }
public bool HayPapel { get; set; }
public bool HayImpresion { get; set; }
public bool PrecioPorVolumen { get; set; }
//public virtual ICollection<EscaladoPrecio> EscaladoPrecio { get; set; }
public virtual CategoriaServicioRepro Categoria { get; set; }
public virtual ServicioReproFacturacionType ServicioReproFacturacionType { get; set; }
}
On this entity you can't see the key, because the base entity Producto have it.
The entity EscaladoPrecio have 3 PK: desde, hasta and Servicio. Servicio is PK and FK.
The entity looks like (methods, overrides and members have been removed to reduce the code):
public class EscaladoPrecio : IComparable<EscaladoPrecio>, IComparable<int>, IComparable, IEntity
{
#region Declarations
private int _desde;
private int _hasta;
private double _precio;
private int _cada;
#endregion Declarations
#region Constructor
public EscaladoPrecio()
: this(1, 1, 0, 0)
{ }
public EscaladoPrecio(int desde, int hasta, double precio)
: this(desde, hasta, precio, 0)
{ }
public EscaladoPrecio(int desde, int hasta, double precio, int cada)
{
_desde = desde;
_hasta = hasta;
_precio = precio;
_cada = cada;
}
#endregion Constructor
#region Properties
public int Desde
{
get
{
return _desde;
}
set
{
_desde = value;
}
}
public int Hasta
{
get
{
return _hasta;
}
set
{
_hasta = value;
}
}
public double Precio
{
get
{
return _precio;
}
set
{
_precio = value;
}
}
public int Cada
{
get
{
return _cada;
}
set
{
_cada = value;
}
}
#endregion Properties
private int _ServicioReproId;
public int ServicioReproId
{
get
{
if (Servicio != null)
{
_ServicioReproId = Servicio.Id;
return Servicio.Id;
}
else
return 0;
}
set
{
_ServicioReproId = value;
}
}
public virtual ServicioRepro Servicio { get; set; }
}
Why throws the exception?
Why are you doing this:
public int ServicioReproId
{
get
{
if (Servicio != null)
{
_ServicioReproId = Servicio.Id;
return Servicio.Id;
}
else
return 0;
}
set
{
_ServicioReproId = value;
}
}
Your part of the key property ServicioReproId is returning 0 here potentially although it has been loaded (and stored in the context) with a value != 0 (probably). I think this part of the exception is refering to this problem: "This can occur if properties that are part of the key return inconsistent or incorrect values."
Better leave it an automatic property:
public int ServicioReproId { get; set; }
try to initialice his virtual property in the constructor of the class EscaladoPrecio()