I have been tasked with adding audit columns to a specific table.
public interface IAuditedEntity
{
DateTime CreatedAt { get; }
DateTime LastUpdatedAt { get; }
}
public class MyEntity: IAuditedEntity
{
private readonly DateTime _utcNow = DateTime.UtcNow;
public DateTime CreatedAt { get => _utcNow; private set { } }
public DateTime LastUpdatedAt { get => _utcNow; private set { } }
}
I was thinking of filling the values by overriding SaveChangesAsync using ChangeTracker.
One of my co-workers suggests these values should be testable and I am not sure how that can be done with this implementation.
I have also never seen a testable auto-generated field and am wondering what would be the best approach for such (or does this requirement makes sense at all).
Both CreatedBy and LastUpdatedBy should be exactly, and precisely, defined in your test cases. Hence, I believe both can be asserted in the test cases.
Similarly, unless you have some weird timing requirements it should be fairly easy to put constraints on the CreatedAt and LastUpdatedAt fields too.
I would further suggest that if you are on EFCore, then, use in-memory database for easier readback and asserts here.
var now = DateTime.Now;
repository.Insert(entity);
// At this point, I think both the entity itself and if you can read it back from the store should have acceptable values or the test fails.
Assert.IsTrue(entity.CreatedBy == "me");
Assert.IsTrue(entity.LastUpdatedBy == "me");
Assert.IsTrue((now - entity.CreatedAt).TotalMilliseconds <= tolerance);
Assert.IsTrue((now - entity.LastUpdatedAt).TotalMilliseconds <= tolerance);
var newEntity = repository.Get(entity.Id);
// same tests here.
// Repeat similarly for update, and ensure created attributes don't change.
I guess it depends a bit what you mean by auto-generated. I'm not sure how you could test them if they were auto-generated by the database, but if you populate the values in the SaveChangesAsync method, then you could define an interface for the task, and then mock it up in your tests. Something like
public interface IAuditSource
{
public DateTime Now { get; }
public string LoggedOnUser { get; }
}
public class AuditSource : IAuditSource
{
public DateTime Now => DateTime.Now;
public string LoggedOnUser => // however you authenticate
}
public class MyDbContext : DbContext
{
private IAuditSource _auditSource;
public MyDbContext(IAuditSource auditSource): base() {}
public override int SaveChangesAsync()
{
// If Changed Entities are IAuditedEntity
// Set values using _auditSource
}
}
public void MyTestMethod()
{
var auditSource = new Mock<IAuditSource>()
auditSource.Setup(x => x.Now).Returns(new DateTime(2020, 01, 28))
var context = new MyDbContext(auditSource.Object)
}
while no answer satisfies my requirements atm,
as a temporary solution, i have decided to go with the following
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>(entity =>
{
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("GetUtcDate()")
.ValueGeneratedOnAdd();
entity.Property(e => e.LastUpdatedAt)
.HasDefaultValueSql("GetUtcDate()")
.ValueGeneratedOnAddOrUpdate();
});
base.OnModelCreating(modelBuilder);
}
since values are not manually fed into the entity, there is no need to test it.
if a better answer comes along, i would considering changing it.
Related
I started using AutoFixture for building my test suite, and I'm pretty convinced that this is what I should be using
to make my tests clear, however, there are a couple of things which I simply don't know how to implement this.
First, let me try you to explain the concept.
I do have class which represents a "Company" entity.
public sealed class Compnay
{
public string Name { get; set; }
public DateTime FoundingDate { get; set; }
public List<Person> Persons { get; set; }
}
And I do have a class of "Person" entities, which represents the persons which are working in a specific company.
public sealed class Person
{
public DateTime DateOfBirth { get; set; }
public DateTime DateOfMarriage { get; set; }
}
Now, I do have an interface to abstract away the current Date/Time.
public interface IDateTimeProvider
{
DateTime Now { get; }
}
And I have a function that queries the companies where there are persons working that are born in the current year.
IEnumerable<Company> Get()
{
return this.DB.Companies.Include(x => x.Persons)
.Where(x => x.DateOfBirth.Year == this.dateTimeProvider.Now)
.Select(x => new {
// ... Implementation ...
});
}
Now, in my unit test, I would like to verify that the entities which are returned are correct.
So I need AutoFixture to generate a random date (because I need to have random dates, so ensure that my code does work
with different Date/Time(s)).
But the problem is that the rest of my test needs to have access to this date because, in order to built my assertion, I
need to calculate which persons are going to be returned (which is dependent) on the current Date/Time.
One option would be to freeze the Date/Time(s) which are created by AutoFixture, but than suddenly, all Date/Times, even the founding date of a company would be this date, which is something which I don't want, since my query might be dependent on that also.
How can I tackle this problem?
Might be important to know that I'm using the "AutoData" attribute to avoid having Fixture configuration inside my tests.
I think that instead of injecting the IDateTimeProvider in your repository, you should instead pass the filtering arguments as parameters into your Get() method. This way you could just pick a date out of the entities generated by AutoFixture and you don't need to freeze values at all.
/* repository */
public IEnumerable<Company> Get(DateTime foundingDate)
{
return this.context.Companies.Include(x => x.Persons)
.Where(x => x.FoundingDate == foundingDate)
.Select(x => x);
}
/* test method */
[Theory, PersistenceData]
public void Foo(
List<Company> companies, [Frozen]MyContext context,
CompaniesRepository repository)
{
context.Companies.AddRange(companies);
context.SaveChanges();
var actual = repository.Get(companies[2].FoundingDate);
Assert.Equal(new[] { companies[2] }, actual);
}
In this example I am using PersistenceData that is a auto data attribute created with EntityFrameworkCore.AutoFixture.
It is just an illustratory example, I understand the relations in this example do not make sense perse, but it plots relations in a way I need the solution. So please do not comment about that.
I am searching for a solution in which I can ignore saving a navigational property;
public class ClassRoom {
public Guid Id { get; set; }
public Guid? ClassRoomInformationId { get; set; }
public virtual ClassRoomInformation { get; set; }
public virtual Collection<Student> Students { get; set;
}
public class Student {
public Guid Id { get; set; }
public Guid? ClassRoomId { get; set; }
public Guid? StudentInformationId { get; set; }
public virtual StudentInformation { get; set; }
}
public class StudentEntityConfiguration : IEntityTypeConfiguration<Student> {
public void Configure(EntityTypeBuilder<Student> builder) {
builder.ToTable("Student");
builder.HasKey(s => s.Id);
builder.HasOne(s => s.StudentInformation)
.WithOne()
.HasForeignKey<Student>(s => s.StudentInformationId);
}
}
public class ClassRoomEntityConfiguration : IEntityTypeConfiguration<ClassRoom> {
public void Configure(EntityTypeBuilder<ClassRoom> builder) {
builder.ToTable("ClassRoom");
builder.HasKey(c => c.Id);
builder.HasOne(c => c.ClassRoomInformation)
.WithOne()
.HasForeignKey<ClassRoom>(c => c.ClassRoomInformationId);
builder.HasMany(c => c.Students)
.WithOne()
.HasForeignKey(c => c.ClassRoomInformation);
}
}
To clearify my question (Using EF 2.2); I want to update the student through it's own StudentRepository. And when I save a classroom through the ClassRoomRepository and the student might change in any way, I do not want that change to be persisted (even though it is included to be able to 'view' the data).
I have tried to add the following to the ClassRoomEntityConfiguration:
//BeforeSaveBehavior neither works
builder.Property(c => c.Students).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
However this gives the following exception:
... Cannot be used as a property on ... because it is configured as a navigation.'
Another thing I tried is setting the componentmodel readonly attribute on the Students list in the ClassRoom. This seems to be ignored as well.
I call this the goldilocks problem. You have a hierarchy of objects (Customer, Order, OrderDetails) and you only want to save at "just the right level" of the object-graph.
A work around is to load the object......change only thing things that you care about, then save it.
In the below, I am NOT saving the inputItem.
I am using inputItem to set a small subset of the values of the foundEntity.
public async Task<MyThing> UpdateAsync(MyThing inputItem, CancellationToken token)
{
int saveChangesAsyncValue = 0;
MyThing foundEntity = await this.entityDbContext.MyThings.FirstOrDefaultAsync(item => item.MySurrogateKey == inputItem.MySurrogateKey, token);
if (null != foundEntity)
{
/* alter JUST the things i want to update */
foundEntity.MyStringPropertyOne = inputItem.MyStringPropertyOne;
foundEntity.MyStringPropertyTwo = inputItem.MyStringPropertyTwo;
this.entityDbContext.Entry(foundEntity).State = EntityState.Modified;
saveChangesAsyncValue = await this.entityDbContext.SaveChangesAsync(token);
/* an exception here would suggest another process changed the "context" but did not commit the changes (usually by SaveChanges() or SaveChangesAsync() */
if (1 != saveChangesAsyncValue)
{
throw new ArgumentOutOfRangeException(string.Format("The expected count was off. Did something else change the dbcontext and not save it? {0}", saveChangesAsyncValue), (Exception)null);
}
}
else
{
ArgumentOutOfRangeException argEx = new ArgumentOutOfRangeException(string.Format(" SAD FACE {0} ", entity.MyThingKey), (Exception)null);
this.logger.LogError(argEx);
throw argEx;
}
return foundEntity;
}
SIDE NOTE:
2.2 is no longer supported (see link below). Dot Net Core 2.2 End of Lifetime is listed as "December 23, 2019"
You should upgrade to 3.1 or downgrade to 2.1. (downgrading is counter intuitive I know).
See
https://dotnet.microsoft.com/platform/support/policy/dotnet-core
I use entity framework code first to work with my database.
I have several tables with different names but same structure, and this tables dynamically appears in database. How could I map EntityFramework to one of that tables at run-time and use data from just like I work this over entities of DbContext?
What I've done to make it work:
For example, my class what describes structure of dynamically created table is SetElement.
Here is my context:
public class DataContext : DbContext
{
public DataContext()
: base("RepositoryConnectionString") { }
string setElementsTableId; // the name of table that need to be dynamicly mapped to
// Enforce model recreating
public DataContext(string setElementsTableId)
: this()
{
this.setElementsTableId = setElementsTableId;
}
/* some other entities */
public DbSet<Entities.SetElement> SetElements { get; set; } // dynamicly mapped entity
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
/* come configurations */
if (!string.IsNullOrEmpty(setElementsTableId))
{
modelBuilder.Entity<Entities.SetElement>().Map(x => x.ToTable(setElementsTableId)); // map SetElements property to dynamicly created table
}
}
}
How I use this:
public static void AddSetElements(ICollection<SetElement> setElements, string tableId)
{
using (ctx = new DataContext(tableId)) // configere DataContext to map tableId table for entity SetElements
try
{
var num = ctx.SetElements.Count();
ctx.SetElements.AddRange(setElements);
ctx.SaveChanges();
}
catch (Exception e)
{
}
}
I have also some methods to get, udtate and remove data from dynamicly created tables that are same to AddSetElements.
All works just as I wish but only if AddSetElements runs first, because at the first datacontext creating DbContext.OnModelCreating runs and configure all mappings. But next instance creation doesn't call DbContext.OnModelCreating.
So, my question is: how to call DbContext.OnModelCreating everytime of creating an instance of DataContext then I use DataContext(string setElementsTableId) to create it?
I know, my question is similar to 'dynamic table mapping in EF' but I found nothing in the results.
By the way. If you know another way to solve my problem, you are welcome.
There is a built-in feature which may address your issue : `IDbModelCacheKey ; the implementation of which is to be registered in your configuration.
The point is to generate a different key for your different contexts.
I would go for something like :
First, the configuration
public class EntityFrameworkConfiguration: DbConfiguration
{
public EntityFrameworkConfiguration()
{
this.SetModelCacheKey(ctx => new EntityModelCacheKey((ctx.GetType().FullName + ctx.Database.Connection.ConnectionString).GetHashCode()));
}
}
Then the implementation of the IDbModelCacheKey
public class EntityModelCacheKey : IDbModelCacheKey
{
private readonly int _hashCode;
public EntityModelCacheKey(int hashCode)
{
_hashCode = hashCode;
}
public override bool Equals(object other)
{
if (other == null) return false;
return other.GetHashCode() == _hashCode;
}
public override int GetHashCode()
{
return _hashCode;
}
}
Finally, your DataContext
public class DataContext : DbContext
{
string setElementsTableId;
// use the setElementsTableId as extended property of the
// connection string to generate a custom key
public DataContext(string setElementsTableId)
: base(ConfigurationManager.ConnectionStrings["RepositoryConnectionString"]
+ "; Extended Properties=\"setElementsTableId=" + setElementsTableId + "\"")
{
this.setElementsTableId = setElementsTableId;
}
public DbSet<Entities.SetElement> SetElements { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (!string.IsNullOrEmpty(setElementsTableId))
{
modelBuilder.Entity<Entities.SetElement>().Map(x => x.ToTable(setElementsTableId));
}
}
}
I hope this will be of some help
Look like nobody knows answer...
Otherwise, one man told me that my question is meaningless because of storage data in several tables will not give any achievement. More better to add indexes to database, partitioning table or something else. In other words this is Database Management System problem. But if some one knows answer I'll be very pleasured to hear something about EF hack.
This is my situation, very much simplified.
My classes;
public class ClassBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class ClassMiddle1 : ClassBase
{
}
public class ClassMiddle2 : ClassBase
{
public Guid Token { get; set; }
}
public class ClassA : ClassMiddle1
{
public string UserId { get; set; }
public string Username { get; set; }
}
public class ClassB : ClassMiddle2
{
public string Username { get; set; }
}
And my OnModelCreating;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassBase>()
.Map(m => {
m.Properties(p => new { p.Id});
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassMiddle1>()
.Map<ClassMiddle1>(m =>
{
m.Properties(p => new { });
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassMiddle2>()
.Map<ClassMiddle2>(m =>
{
m.Properties(p => new { p.Token });
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassA>()
.Map<ClassA>(m =>
{
m.Properties(p => new
{
p.UserId,
p.Username
});
m.ToTable("TableA");
});
modelBuilder.Entity<ClassB>()
.Map<ClassB>(m =>
{
m.Properties(p => new
{
p.Username
});
m.ToTable("TableB");
}).Property(p => p.Username).HasColumnName("User");
}
This works fine but the Discriminator column is by default Discriminator, NVARCHAR(128). I read that it is possible to define this column myself using something like below.
m.Requires("ClassType").HasValue(1);
I turned my possibilities inside out but all times running into a dead end. Anyone having a suggestion how to do it?
I will end with another question. As our hierarchy pretty much are as above but even more derivated classes like C, D, E, F and so on to... say P. We found out that EF are making this incredibly big database query (~150K). Anyone else ran into this scenario?
I am hoping with changing Discriminator to at least minimize this. By that I say we have a very neat class hierarchy but an ugly query set.
Late answer how the actual solution went. Only writing it down here because the documentation around this was not that easy to find.
My solution ended up like below...
modelBuilder.Entity<ClassBase>()
.Map(m => {
...
m.Requires("Discriminator").HasValue(1)
});
Regarding your "incredibly big database query": There are indeed performance and query generation issues with TPT inheritance mapping. There still doesn't seem to be a fix for those problems, only this vague announcement (August 2010):
The good news is that we are working
on these issues so that EF no longer
generates unnecessary SQL. The bad
news is that it will take some time
before the fix is delivered in a
future release.
(Quote from the linked article above.)
Okay, I know I have to be doing something wrong here because the performance times I'm getting are so different its shocking. I've been considering using the code first option of entity in an existing project of mine so I've been trying to do some performance test just to see how it compares. I'm using MSpec to run the tests against a remote development database.
Here are my tests:
public class query_a_database_for_a_network_entry_with_linq : ipmanagement_object {
protected static NetINFO.IPM_NetworkMaster result;
Because of = () => {
var db = new NetINFODataContext();
result = db.IPM_NetworkMasters.SingleOrDefault(c => c.NetworkID == 170553);
};
It should_return_an_ipm_networkmaster_object = () => {
result.ShouldBeOfType(typeof(NetINFO.IPM_NetworkMaster));
};
It should_return_a_net_ou_object_with_a_networkid_of_4663 = () => {
result.IPM_OUIDMaps.First().NET_OU.NET_OUID.ShouldEqual(4663);
};
}
public class query_a_database_for_a_network_entry_with_entity_code_first : ipmanagement_object {
protected static NetInfo.Core.Models.CTP.IPM_NetworkMaster result;
Because of = () => {
NetInfo.Core.Models.CTP.NetInfoDb db = new NetInfo.Core.Models.CTP.NetInfoDb();
result = db.IPM_NetworkMasters.SingleOrDefault(c => c.NetworkID == 170553);
};
It should_return_an_ipm_networkmaster_object = () => {
result.ShouldBeOfType(typeof(NetInfo.Core.Models.CTP.IPM_NetworkMaster));
};
It should_return_a_net_ou_object_with_a_networkid_of_4663 = () => {
result.NET_OUs.First().NET_OUID.ShouldEqual(4663);
};
}
As you can see from the datacontext with linq-to-sql I can't access object directly that have a many to many relationship. I have to use the intermediate lookup table. Which is one of the things I like about Entity framework. However when I run these test the linq test never takes longer than 4 seconds to complete (database is remote). Where the entity test takes almost 8 seconds every time. Not for sure why there is such a huge difference?? Here is excerpts of my POCO classes and my dbcontext:
DbContext:
public class NetInfoDb : DbContext {
public NetInfoDb() : base("NetINFOConnectionString") { }
public DbSet<IPM_NetworkMaster> IPM_NetworkMasters { get; set; }
public DbSet<IPM_NetworkType> IPM_NetworkTypes { get; set; }
public DbSet<NET_OU> NET_OUs { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) {
modelBuilder.Entity<IPM_NetworkMaster>()
.HasMany(a => a.NET_OUs)
.WithMany(b => b.IPM_NetworkMasters)
.Map(m => {
m.MapRightKey(a => a.NET_OUID, "NET_OUID");
m.MapLeftKey(b => b.NetworkID, "NetworkID");
m.ToTable("IPM_OUIDMap");
});
}
}
IPM_NetworkMaster:
public class IPM_NetworkMaster {
public int NetworkID { get; set; }
<snip>
public virtual ICollection<NET_OU> NET_OUs { get; set; }
}
NET_OU:
public class NET_OU {
public int NET_OUID { get; set; }
<snip>
public virtual ICollection<IPM_NetworkMaster> IPM_NetworkMasters { get; set; }
}
As everyone has mentioned, you need to profile your queries. Assuming you are using SQL Server, you can just spool up SQL Server Profiler and compare the queries and execution plans.
As with any performance issue, you must measure first. With your scenario, you have to do more. You have to measure twice with each technology and make sure you are comparing apples to apples. If you can rule out the sql being generated you will then have to measure the application code, to possibly rule any bottlenecks there.
I suspect it will be the generated queries though.