How to generalise access to DbSet<TEntity> members of a DbContext? - c#

I have a DbContext with several of the following type of members:
public DbSet<JobLevel> JobLevels { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Race> Races { get; set; }
public DbSet<Language> Languages { get; set; }
public DbSet<Title> Titles { get; set; }
All these are where T: IdNamePairBase, which has Id and Name members only. I am trying desperately to find a common interface with which to access any of these members, to generalise the following MVC3 controller code into one controller:
public ActionResult Edit(DropDownListModel model, Guid)
{
var dbSet = _dbContext.Countries;
var newItems = model.Items.Where(i => i.IsNew && !i.IsDeleted).Select(i => new { i.Name });
foreach (var item in newItems)
{
if (!string.IsNullOrWhiteSpace(item.Name))
{
var undead = ((IEnumerable<IdNamePairBase>)dbSet).FirstOrDefault(p => p.Name.ToLower() == item.Name.ToLower());
if (undead != null)
{
// Assign new value to update to the new char. case if present.
undead.Name = item.Name;
undead.IsDeleted = false;
_dbContext.SaveChanges();
continue;
}
var newPair = new Country { Name = item.Name };
dbSet.Add(newPair);
_dbContext.SaveChanges();
}
}
return RedirectToAction("Edit", new {listName = model.ListName});
}
How could I go about resolving my problem that right now I need one controller for each of the DbContext members, like the one above is dedicated to DbSet<Country> Countries?
PARTIAL SOLUTION: Along lines similar to GertArnold's answer below, before I knew about the _dbContext.Set<T> all he highlights, I implemented this method on my context class to get sets of a specific type:
public IEnumerable<DbSet<T>> GetDbSetsByType<T>() where T : class
{
//var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance;
var props = GetType().GetProperties()
.Where(p => p.PropertyType.IsGenericType && p.PropertyType.Name.StartsWith("DbSet"))
.Where(p => p.PropertyType.GetGenericArguments().All(t => t == typeof(T)));
return props.Select(p => (DbSet<T>)p.GetValue(this, null));
}

Some generalization is possible by using
var dbSet = _dbContext.Set<T>
and putting most of your method in a method with a generics type parameter.
However, there should be a switch somewhere to decide which type should be specified and which type to create, because I think the type is supplied as a property of the model (is it?). So it probably won't really look elegant, but probably be a lot shorter, with DRY-er code.

To add on Gert Arnold's answer, I want to note that there is another method overload on the dbContext that returns a general DbSet from a type object:
var dbSet = dbContext.Set(typeof(T))
If you want to add blind an object, then create the object using the set.Create() method, or if you already have an object created with the "new" keyowrd, you can convert it by using (similar to this answer)
var entity = dbSet.Create();
dbSet.Add(entity);
DbEntityEntry entry = context.Entry(entity);
entry.CurrentValues.SetValues(yourObject);

I've been looking for an answer to this question and I've found that it is easy to do using the Managed Extensibility Framework. There is a quicker way at the bottom of this post, however MEF allows for a much more scalable approach.
MEF allows you to build dynamic access plugins from disparate Assemblies; however it can be used to quickly populate Collections within a single assembly application.In essence, we'll be using it as a safe way of reflecting our assembly back into the class. In order to make this fully functional, I'm also going to implement the Strategy Pattern to the Entity Framework Model.
Add a reference to your project, pointing to System.ComponentModel.Composition. This will give access to the MEF library.
Now, we need to implement the Strategy Pattern. If you don't have an Interfaces folder, create one, and add IEntity.cs, as below.
IEntity.cs
namespace Your.Project.Interfaces
{
/// <summary>
/// Represents an entity used with Entity Framework Code First.
/// </summary>
public interface IEntity
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
int Id { get; set; }
}
}
Now, each of you concrete entities need to implement this Interface:
public class MyEntity : IEntity
{
#region Implementation of IEntity
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public int Id { get; set; }
#endregion
// Other POCO properties...
}
I find that it is best practice, not to create individual interfaces for each entity, unless you're working in a high testing environment. Pragmatically, interfaces should only be used where that level of abstraction is needed; mainly when more than one concrete class will inherit, or when working with an over-enthusiastic Inversion of Control engine. If you have interfaces for everything in your production model, your architecture more than likely, has major flaws. Anyway, enough of the rambling.
Now that we have all of our entities "strategised", we can use MEF to collate them and populate a collection within your context.
Within your context, add a new property:
/// <summary>
/// Gets a dynamically populated list of DbSets within the context.
/// </summary>
/// <value>
/// A dynamically populated list of DbSets within the context.
/// </value>
[ImportMany(typeof(DbSet<IEntity>))]
public IEnumerable<DbSet<IEntity>> Sets { get; private set; }
The [ImportMany(typeof(DbSet<IEntity>))] here, allows MEF to populate the collection.
Next, add the corresponding Export attribute to each DbSet within the context:
[Export(typeof(DbSet<IEntity>))]
public DbSet<MyEntity> MyEntities { get; set; }
Each of the Imported and Exported properties is known as a "part". The final piece to the puzzle is to compose those parts. Add the following to your context's constructor:
// Instantiate the Sets list.
Sets = new List<DbSet<IEntity>>();
// Create a new Types catalogue, to hold the exported parts.
var catalogue = new TypeCatalog(typeof (DbSet<IEntity>));
// Create a new Composition Container, to match all the importable and imported parts.
var container = new CompositionContainer(catalogue);
// Compose the exported and imported parts for this class.
container.ComposeParts(this);
Now, with any luck, you should have a dynamically populated list of DbSets, within your context.
I have used this method to allow easy truncating of all tables via an extension method.
/// <summary>
/// Provides extension methods for DbSet objects.
/// </summary>
public static class DbSetEx
{
/// <summary>
/// Truncates the specified set.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="set">The set.</param>
/// <returns>The truncated set.</returns>
public static DbSet<TEntity> Truncate<TEntity>(this DbSet<TEntity> set)
where TEntity : class, IEntity
{
set.ToList().ForEach(p => set.Remove(p));
return set;
}
}
I have added a method to the context to truncate the whole database.
/// <summary>
/// Truncates the database.
/// </summary>
public void TruncateDatabase()
{
Sets.ToList().ForEach(s => s.Truncate());
SaveChanges();
}
EDIT (Overhaul):
The solution above has now been depreciated. Some tweeking as had to be done to get this to work now. To make this work, you need to import the DbSets into a temporary collection of DbSet of type "object", then cast this collection to DbSet of your required interface type. For basic purposes, the IEntity interface will suffice.
#region Dynamic Table List
/// <summary>
/// Gets a dynamically populated list of DbSets within the context.
/// </summary>
/// <value>
/// A dynamically populated list of DbSets within the context.
/// </value>
public List<DbSet<IEntity>> Tables { get; private set; }
/// <summary>
/// Gets a dynamically populated list of DbSets within the context.
/// </summary>
/// <value>
/// A dynamically populated list of DbSets within the context.
/// </value>
[ImportMany("Sets", typeof (DbSet<object>), AllowRecomposition = true)]
private List<object> TableObjects { get; set; }
/// <summary>
/// Composes the sets list.
/// </summary>
/// <remarks>
/// To make this work, you need to import the DbSets into a temporary collection of
/// DbSet of type "object", then cast this collection to DbSet of your required
/// interface type. For basic purposes, the IEntity interface will suffice.
/// </remarks>
private void ComposeSetsList()
{
// Instantiate the list of tables.
Tables = new List<DbSet<IEntity>>();
// Instantiate the MEF Import collection.
TableObjects = new List<object>();
// Create a new Types catalogue, to hold the exported parts.
var catalogue = new TypeCatalog(typeof (DbSet<object>));
// Create a new Composition Container, to match all the importable and imported parts.
var container = new CompositionContainer(catalogue);
// Compose the exported and imported parts for this class.
container.ComposeParts(this);
// Safe cast each DbSet<object> to the public list as DbSet<IEntity>.
TableObjects.ForEach(p => Tables.Add(p as DbSet<IEntity>));
}
#endregion
Next, run the CompileSetsList() facade from the constructor (with best practices for Web shown):
public MvcApplicationContext()
{
// Enable verification of transactions for ExecuteSQL functions.
Configuration.EnsureTransactionsForFunctionsAndCommands = true;
// Disable lazy loading.
Configuration.LazyLoadingEnabled = false;
// Enable tracing of SQL queries.
Database.Log = msg => Trace.WriteLine(msg);
// Use MEF to compile a list of all sets within the context.
ComposeSetsList();
}
Then, just decorate your DbSet<>s like this:
/// <summary>
/// Gets or sets the job levels.
/// </summary>
/// <value>
/// The job levels.
/// </value>
[Export("Sets", typeof(DbSet<object>))]
public DbSet<JobLevel> JobLevels { get; set; }
Now it will work properly.

Related

EF Core OnDelete Not Available

I have an issue in our system where a process is attempting to replace a record and it causes a FK Exception and fails. We are using Microsoft.EntityFrameworkCore 2.2.2 in a .Net Standard 2.0 library. Most of the code was ported from an existing project that was not .Net Core.
I am trying to add cascade delete to the following model:
modelBuilder.Entity<TermEntity>()
.ToTable("Term", SchemaName)
.HasKey(k => new { k.TermId, k.TenantId });
by adding the following:
modelBuilder.Entity<TermEntity>()
.ToTable("Term", SchemaName)
.HasKey(k => new { k.TermId, k.TenantId });
.OnDelete(DeleteBehavior.Cascade);
but I then get an error C1061 'KeyBuilder' does not contain a definition for OnDelete which makes sense as that is what is returned from HasKey and the OnDelete is on ReferenceCollectionBuilder. None of the methods used above return one of those and all of them (yes I have tried moving it around) give a similar error. The signature of the method this is in looks like:
protected override void OnModelCreating(ModelBuilder modelBuilder)
When I look up some EF Core tutorials I don't see the methods I am using and instead I see HasOne, WithMany etc usually with OnDelete at the end of the chain. I also thought I read that Cascade was the default so not sure why I have the issue in the first place if that is the case.
Can someone point me in the right direction to getting this fixed please.
The TermEntity looks like:
public class TermEntity
{
/// <summary>
/// Gets or sets the term identifier.
/// </summary>
/// <value>
/// The term identifier.
/// </value>
public short TermId { get; set; }
/// <summary>
/// Gets or sets the Tenant identifier.
/// </summary>
/// <value>
/// The Tenant range identifier.
/// </value>
public short TenantId { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public int Value { get; set; }
}
NB: I was asked to provide the class I was using in the example and realised that I had posted the wrong one. TermEntity is the class I am having problems with.

Prevent EF Model creation in secondary apps

So I have a drop and recreate setup in my application's core project which is consumed by all of my apps in this solution. My model changes significantly enough to where I need this drop and recreate the database tables each time I recycle my app pool (app is in rapid development right now.) The problem is that if 2+ projects are kicked off at the same time, the drop and recreate steps on each other and many times there are missing tables.
I need a way to prevent, at runtime, the creation of the model based on a condition. I tried:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (condition)
return;
//...
base.OnModelCreating(modelBuilder);
}
but this is not working... the tables are still being created. How can I do this?
Not quite sure about your scenario.
For the development purposes, you can use different schema for your tables.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("applicationX");
}
If this is not possible, you can suffix/prefix table names for your entities.
If you need shared tables (data) by both application, you can compose all the database in any application sharing the same model building code.
Then, of course, you must somehow create "critical section" for your database recreation code (https://github.com/aspnet/EntityFramework/issues/3042), using Mutex or/and combined it e.g. with your own lock synchronization object (table) in your database.
If you need to construct a model (database) dynamically, e.g., use the dependancy injection to build model dynamically at single DB context:
/// <summary>
/// Formalization of entity framework DbContext model creation <see cref="DbContext.OnModelCreating"/>
/// </summary>
/// <remarks>
/// The reason for this explicit interface of the <see cref="DbContext.OnModelCreating"/> is to make compositions of several model builders into one
/// </remarks>
public interface IEntityFrameworkModelBuilder
{
void BuildModel(ModelBuilder modelBuilder);
}
public abstract class DbContextBase : DbContext
{
protected DbContextBase(DbContextOptions options, IEntityFrameworkModelBuilder modelBuilder) : base(options)
{
_modelBuilder = modelBuilder;
}
public virtual IEntityFrameworkModelBuilder ModelBuilder
{
get { return _modelBuilder ?? (_modelBuilder = new DefaultSchemaEntityFrameworkModelBuilder("dbo")); }
protected set { _modelBuilder = value; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
ModelBuilder.BuildModel(modelBuilder);
}
}
Of course, then you can create e.g. composit pattern class for your IEntityFrameworkModelBuilder:
/// <summary>
/// The composition of <see cref="IEntityFrameworkModelBuilder"/> used to construct <see cref="DbContext"/> model creation from several modules (tests).
/// </summary>
/// <seealso cref="DbContextOnModelCreatingAdaptingModelBuilder"/>
public class EntityFrameworkModelBuilderComposition : IEntityFrameworkModelBuilder, IList<IEntityFrameworkModelBuilder>
{
private readonly IList<IEntityFrameworkModelBuilder> _builders;
public EntityFrameworkModelBuilderComposition() : this(new IEntityFrameworkModelBuilder[0])
{}
public EntityFrameworkModelBuilderComposition(params IEntityFrameworkModelBuilder[] builders)
{
_builders = new List<IEntityFrameworkModelBuilder>(builders);
}
/// <summary>
/// Constructor to take list of builders as underlaying list so it is initialized with these builders and sharing the same reference.
/// </summary>
/// <param name="builders"></param>
public EntityFrameworkModelBuilderComposition(IList<IEntityFrameworkModelBuilder> builders)
{
_builders = builders ?? new List<IEntityFrameworkModelBuilder>();
}
public virtual void BuildModel(ModelBuilder modelBuilder)
{
foreach (var b in _builders)
{
b.BuildModel(modelBuilder);
}
}
}
Just adding, what you could find helpful sooner or later:
/// <summary>
/// The <see cref="IEntityFrameworkModelBuilder"/> implementation of a building model on the base of <see cref="DbContext"/> instance calling its <see cref="DbContext.OnModelCreating"/> protected method.
/// </summary>
/// <seealso cref="DbContextOnModelCreatingAdaptingModelBuilder"/>
/// <seealso cref="EntityFrameworkModelBuilderComposition"/>
public class DbContextOnModelCreatingAdaptingModelBuilder : IEntityFrameworkModelBuilder
{
private readonly Func<DbContext> _dbContextFactoryMethod;
public DbContextOnModelCreatingAdaptingModelBuilder(Func<DbContext> dbContextFactoryMethod)
{
_dbContextFactoryMethod = dbContextFactoryMethod;
}
public void BuildModel(ModelBuilder modelBuilder)
{
using (var dbContext = _dbContextFactoryMethod())
{
MethodInfo onModelCreating = dbContext.GetType().GetMethod("OnModelCreating", BindingFlags.NonPublic | BindingFlags.Instance);
onModelCreating.Invoke(dbContext, new object[] {modelBuilder});
}
}
}
Of course, all the mentioned points can/must be combined to get the desired behaviour. But it's not clear, how you evaluate the model differences, what
My model changes significantly enough to where I need this drop and recreate
means exactly regarding
the drop and recreate steps on each other and many times there are missing tables
Sadly, according to Microsoft, there does not seem to be a way to do it: https://github.com/aspnet/EntityFramework/issues/6346

Entity Framework get user from contex in saveChanges

i have two projects in my solution, UI as mvc and class project for entitiy model code first. I have severall entities in my model but now I need to extend them by new audit fields where I need to save who changed entity.
I added new interface
public interface IAuditable
{
/// <summary>Gets or sets the name.</summary>
/// <value>The name.</value>
DateTime CreatedDate { get; set; }
/// <summary>Gets or sets the name.</summary>
/// <value>The name.</value>
string CreatedBy { get; set; }
/// <summary>Gets or sets the name.</summary>
/// <value>The name.</value>
DateTime UpdatedDate { get; set; }
/// <summary>Gets or sets the name.</summary>
/// <value>The name.</value>
string UpdatedBy { get; set; }
}
and try to extend SaveChanges in this way
foreach (var auditableEntity in ChangeTracker.Entries<IAuditable>())
{
if (auditableEntity.State == EntityState.Added ||
auditableEntity.State == EntityState.Modified)
{
// implementation may change based on the useage scenario, this
// sample is for forma authentication.
string currentUser = ;
// modify updated date and updated by column for
// adds of updates.
auditableEntity.Entity.UpdatedDate = DateTime.Now;
auditableEntity.Entity.UpdatedBy =
// pupulate created date and created by columns for
// newly added record.
if (auditableEntity.State == EntityState.Added)
{
auditableEntity.Entity.CreatedDate = DateTime.Now;
auditableEntity.Entity.CreatedBy = currentUser;
}
else
{
auditableEntity.Property(p => p.CreatedDate).IsModified = false;
auditableEntity.Property(p => p.CreatedBy).IsModified = false;
}
}
but how do I get the userName here ? I can't use any httpContex getUser becuase this is class project. Any ideas?
this is my contex
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDbContext
so I thought to extend ApplicationUser by another field like LogedUserName, and fill it when user is loging, but how do I get this field in my SaveChanges method ?
If you are sure that this class library will be always used in ASP.NET pipeline you actually can access HttpContext.
You need a reference to System.Web in your class library and then:
using System.Web;
[...]
public void SaveChanges()
{
var username = HttpContext.Current.User.Identity.Name;
}
In this case HttpContext is a static class, not a property.
Ofcourse this will fail badly if this class is ever used outside ASP.NET pipeline (for example in WPF application, console app etc). Also it doesn't seem clean to do it this way. But it's probably the fastest way which requires minimal existing code change.
Another way would be to pass either username or whole identity to either class responsible for saving changes or directly to SaveChanges method.
One implementation could look like this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDbContext
{
private IPrincipal _currentUser;
public ApplicationDbContext(IPrincipal currentUser)
{
_currentUser = currentUser;
}
}
and then in Controller (if you use context directly in MVC controllers):
using(var db = new ApplicationDbContext(User))
{
[...]
}
where User is controller's property holding current user.

How do I retrieve a single record on Microsoft CRM using c# if I don't know the GUID?

We have a vendor solution here where it uses Microsoft Dynamics CRM as base. The application includes this custom entity which has the following attributes (truncated to only show the relevant bits)
relationship entity (works like a list of values. like values for populating a dropdown list)
relationshipid (guid datatype. primary key)
description (string and sample values would include "staff" or "customer" or "visitor" and etc)
I want to retrieve a single record from the entity. something like:
select * from relationship where description like "staff%"
I know there's a Retrieve function but I need the guid to use that. I want to emulate the SQL above without having to possibly use QueryExpression. I want to get an object of type Entity instead of EntityCollection which is what a QueryExpression will give me.
thanks a lot :)
The SDK doesn't provide a method to return a single entity unless you have its Id. But you can write an extension method to help yourself out.
Here is what I use:
/// <summary>
/// Gets the first entity that matches the query expression. Null is returned if none are found.
/// </summary>
/// <typeparam name="T">The Entity Type.</typeparam>
/// <param name="service">The service.</param>
/// <param name="qe">The query expression.</param>
/// <returns></returns>
public static T GetFirstOrDefault<T>(this IOrganizationService service, QueryExpression qe) where T : Entity
{
qe.First();
return service.RetrieveMultiple(qe).ToEntityList<T>().FirstOrDefault();
}
/// <summary>
/// Converts the entity collection into a list, casting each entity.
/// </summary>
/// <typeparam name="T">The type of Entity</typeparam>
/// <param name="col">The collection to convert</param>
/// <returns></returns>
public static List<T> ToEntityList<T>(this EntityCollection col) where T : Entity
{
return col.Entities.Select(e => e.ToEntity<T>()).ToList();
}
The GetFirstOrDefault ensures that the QE is only going to retrieve one entity. The ToEntityList Casts each entity to the early bound type being returned.
So the call would look like:
var contact = service.GetFirstOrDefault<Contact>(qe);
just check if the EntityCollection contains one element, if true returns the single element
EntityCollection results = service...
if (results.Entities.Count == 1) {
return results.Entities[0];
}
To add to the other answers, even though you will still get an EntityCollection in return to a query expression, you can specify a top count to only retrieve 1 record as an alternative.
QueryExpression qeOpportunity = new QueryExpression();
qeOpportunity.EntityName = "opportunity";
qeOpportunity.ColumnSet = new ColumnSet(new string[] { "sp_shippingmethod_lookup", "description" });
qeOpportunity.TopCount = 1;
// Add other Conditions here with qeOpportunity.Criteria.AddCondition()....
EntityCollection ecOpportunity = service.RetrieveMultiple(qeOpportunity);
if (ecOpportunity.Entities.Count > 0)
{
string description = ecOpportunity.Entities[0].GetAttributeValue<string>("description");
EntityReference myShippingMethod = ecOpportunity.Entities[0].GetAttributeValue<EntityReference>("sp_shippingmethod_lookup");
}

Where to put Created date and Created by in DDD?

I use Entity Framework and want to use DDD principles. However, there are some information regarding the entities that is on the borderline between what is logging/persistence information and what is information about the domain objects.
I my situation these are put in an abstract base class that all entities inherit from:
public abstract class BaseEntity: IBaseEntity
{
/// <summary>
/// The unique identifier
/// </summary>
public int Id { get; set; }
/// <summary>
/// The user that created this instance
/// </summary>
public User CreatedBy { get; set; }
/// <summary>
/// The date and time the object was created
/// </summary>
public DateTime CreatedDate { get; set; }
/// <summary>
/// Which user was the last one to change this object
/// </summary>
public User LastChangedBy { get; set; }
/// <summary>
/// When was the object last changed
/// </summary>
public DateTime LastChangedDate { get; set; }
/// <summary>
/// This is the status of the entity. See EntityStatus documentation for more information.
/// </summary>
public EntityStatus EntityStatus { get; set; }
/// <summary>
/// Sets the default value for a new object
/// </summary>
protected BaseEntity()
{
CreatedDate = DateTime.Now;
EntityStatus = EntityStatus.Active;
LastChangedDate = DateTime.Now;
}
}
Now a Domain Object can't be instantiated without providing the date and time. However, I feel it is the wrong place to put it. I can argue for both really. Maybe it should not be mixed with the domain at all?
Since I'm using EF Code First it makes sense to put it there, or else I would need to create new classes that inherit from the base class in the DAL also, duplicating code and needing to map to both domain objects and MVC models which does seem more messy than the approach above.
The question(s):
Is it Ok to use DateTime.Now in the Domain model at all? Where do you put this kind of information using DDD and EF Code First? Should User to be set in the domain object or require it in the Business Layer?
Update
I think jgauffin har the right answer here - but it is really quite a fundamental change. However, on my search for an alternate solution I almost had it solved with this. I used the ChangeTracker.Entries to find ut if an entity is added or modified and set the fields accordingly. This is done in my UnitOfWork Save() method.
The problem is loading navigation properties, like User (DateTime is set correctly). It might be since the user is a property on the abstract base class the entity inherits from. I also don't like putting strings in there, however it might solve some simple scenarios for someone, so I post the solution here:
public void SaveChanges(User changedBy)
{
foreach (var entry in _context.ChangeTracker.Entries<BaseEntity>())
{
if (entry.State == EntityState.Added)
{
entry.Entity.CreatedDate = DateTime.Now;
entry.Entity.LastChangedDate = DateTime.Now;
entry.Entity.CreatedBy = changedBy;
entry.Entity.LastChangedBy = changedBy;
}
if (entry.State == EntityState.Modified)
{
entry.Entity.CreatedDate = entry.OriginalValues.GetValue<DateTime("CreatedDate");
entry.Entity.CreatedBy = entry.OriginalValues.GetValue<User>("CreatedBy");
entry.Entity.LastChangedDate = DateTime.Now;
entry.Entity.LastChangedBy = changedBy;
}
}
_context.SaveChanges();
}
Is it Ok to use DateTime.Now in the Domain model at all?
Yes.
Where do you put this kind of information using DDD and EF Code First? Should User to be set in the domain object or require it in the Business Layer?
Well. First of all: A DDD model is always in a valid state. That's impossible with public setters. In DDD you work with the models using methods since the methods can make sure that all required information has been specified and is valid.
For instance, if you can mark an item as completed it's likely that the UpdatedAt date should be changed too. If you let the calling code make sure of that it's likely that it will be forgotten somewhere. Instead you should have something like:
public class MyDomainModel
{
public void MarkAsCompleted(User completedBy)
{
if (completedBy == null) throw new ArgumentNullException("completedBy");
State = MyState.Completed;
UpdatedAt = DateTime.Now;
CompletedAt = DateTime.Now;
CompletedBy = completedBy;
}
}
Read my blog post about that approach: http://blog.gauffin.org/2012/06/protect-your-data/
Update
How to make shure that noone changes the "CreatedBy" and "CreatedDate" later on
I usually have two constructors for the models which also fits the DB. one protected one which can be used by my persistance layer and one which requires the mandatory fields. Put the createdby in that constructor and set the createdate in it:
public class YourModel
{
public YourModel(User createdBy)
{
CreatedDate = DateTime.Now;
CreatedBy = createdby;
}
// for persistance
protected YourModel()
{}
}
Then have private setters for those fields.
I get a lot of R# warning "Virtual member call in constructor", I've read about it before and it is not supposed to be a good practice.
That's usually not a problem. Read here: Virtual member call in a constructor
Is it Ok to use DateTime.Now in the Domain model at all?
It isn't terrible, but the problem is that you will end up having to duplicate code and it will more difficult to achieve consistency.
Where do you put this kind of information using DDD and EF Code First?
You are correct to assert that this type of information doesn't belong in your domain. It is typically called an audit log or trail. There are a few ways to implement auditing with EF. Take a look at AuditDbContext - Entity Framework Auditing Context for instance, or just search around for EF auditing implementations. The idea is that before EF persists changes to an entity, it raises an event which you can listen to and assign the required audit values.
Should User to be set in the domain object or require it in the
Business Layer?
It is best to handle this at the infrastructure/repository level with an auditing implementation as stated above. This is the final stop before data is persisted and thus is the perfect place to take care of this.

Categories

Resources