I am moving my first steps towards Domain Driven Design using Entity Framework Core. I have a User entity that, in a simplified version, has only Id and ProfilePhoto. However, I want to store profile photos in a different table, that is why I created an Owned Type containing the profile photo and configured in this way:
User:
public class User
{
private int id;
public int Id => this.id;
//private UserProfilePhoto userProfilePhoto;
public virtual UserProfilePhoto UserProfilePhoto { get; set; }
private User()
{
}
public static User Create(byte[] profilePhoto)
{
var user = new User();
user.UserProfilePhoto = new UserProfilePhoto(profilePhoto);
return user;
}
public void SetProfilePhoto(byte[] profilePhoto)
{
this.UserProfilePhoto = new UserProfilePhoto(profilePhoto);
}
}
UserProfilePhoto:
public class UserProfilePhoto
{
public byte[] ProfilePhoto { get; private set; }
public UserProfilePhoto(byte[] profilePhoto)
{
this.ProfilePhoto = profilePhoto;
}
}
DbContext configuration:
public class ModelContext : DbContext
{
public ModelContext(DbContextOptions<ModelContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
OnUserModelCreating(modelBuilder);
}
protected void OnUserModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasKey(u => u.Id);
modelBuilder.Entity<User>()
.Property(u => u.Id)
.HasField("id");
modelBuilder.Entity<User>()
.OwnsOne(u => u.UserProfilePhoto, builder =>
{
builder.ToTable("UserProfilePhoto");
builder.Property(u => u.ProfilePhoto)
.IsRequired();
});
}
}
I chose to use an Owned type because I want the profile photo to be accessible only from the user entity. with a one-to-one mapping, I could still access the UserProfilePhoto table using context.Set<UserProfilePhoto>() for example and, for what I read about DDD aggregates, this could mean skipping User business logic.
So, I migrated and the database model is just like I expected it to be: the UserProfilePhoto table with a primary and foreign key to User.Id.
Obviously in my queries I do not want to load the entire User entity every time, so I enabled Lazy Loading, unsuccessfully. This is the code I tried in a unit test:
protected ModelContext GetModelContext(DbContextOptionsBuilder<ModelContext> builder)
{
builder
.UseLoggerFactory(loggerFactory)
.UseLazyLoadingProxies()
.EnableDetailedErrors();
var ctx = new ModelContext(builder.Options);
ctx.Database.EnsureCreated();
return ctx;
}
[TestMethod]
public async Task TestMethod1()
{
var builder = new DbContextOptionsBuilder<ModelContext>()
.UseSqlServer(...);
var ctx = this.GetModelContext(builder);
var user = User.Create(new byte[] { });
try
{
await ctx.Users.AddAsync(user);
await ctx.SaveChangesAsync();
var users = ctx.Users;
foreach (var u in users)
{
Console.WriteLine(u.Id);
}
}
finally
{
ctx.Users.Remove(user);
await ctx.SaveChangesAsync();
ctx.Database.EnsureDeleted();
}
}
And here the SQL generated:
SELECT [u].[Id], [u0].[UserId], [u0].[ProfilePhoto]
FROM [Users] AS [u]
LEFT JOIN [UserProfilePhoto] AS [u0] ON [u].[Id] = [u0].[UserId]
I do not exactly know if it works, but injecting an ILazyLoader is not an solution for me, on the other hand, it feels like dirtying the model.
My doubt is that Owned types do not bind to the principal entity through actual navigation properties, so creating proxy for them is not supported.
What is wrong with my approach? Is it DDD? And if so, how can I lazily load owned entities?
I found an issue on Github related to this, although it does not answer my question.
Edit
my goal is to prevent the access to the UserProfilePhoto table from EF api (See comments). If I managed to do this, then protecting my UserProfilePhoto class and encapsulating it in the User class would be easy, something like this:
User
...
protected virtual UserProfilePhoto UserProfilePhoto { get; set; }
public void SetProfilePhoto(byte[] profilePhoto)
{
this.UserProfilePhoto.SetProfilePhoto(profilePhoto);
}
public byte[] GetProfilePhoto()
{
return this.UserProfilePhoto.ProfilePhoto;
}
...
I tried this code with a one-to-one mapping and works perfectly, even with lazy loading. How could I do this with only Owned Types? are there other ways?
EF Core loads owned types automatically when the owner gets loaded (from Owned Entity Types: Querying owned types)
When querying the owner the owned types will be included by default. It is not necessary to use the Include method, even if the owned types are stored in a separate table.
Therefore using owned types does not fulfill your requirement of being loaded only on demand.
(You can tinker with Metadata.PrincipalToDependent.SetIsEagerLoaded(false) etc., but this is very much unsupported, unlikely to work in all cases and could break any time.)
Options without using owned types (in order of recommendation)
Override DbContext.Set<>(), DbContext.Find() etc. and throw if called inappropriately
Implement a traditional custom Unit-of-Work and Repository pattern, that gives you full control over the API exposed (trades flexibility for control)
Add an expression visitor early to the query pipeline (register IQueryTranslationPreprocessorFactory and derive from RelationalQueryTranslationPreprocessorFactory), that throws if a DbSet<UserProfilePhoto> is used anywhere in the query
Provide your own IDbSetSource (and InternalDbSet) implementation (both internal) and throw if called inappropriately
Overriding DbContext methods
Generally, just overriding DbContext.Set<>(), DbContext.Find() etc. should be the simplest solution. You could decorate the types that you don't want to be queried directly with a custom attribute and then simply just check, that TEntity etc. has not have been decorated with this custom attribute.
For easier maintainability, all the overridden methods can be moved to a base class, that can also perform some runtime check to ensure, that all methods in question have been overridden (of course those checks could also be done by a unit test).
Here is a sample demonstrating this approach:
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class DontRootQueryMeAttribute : Attribute
{
}
public class User
{
public int Id { get; private set; }
public virtual UserProfilePhoto UserProfilePhoto { get; set; }
public static User Create(byte[] profilePhoto)
{
var user = new User
{
UserProfilePhoto = new UserProfilePhoto(profilePhoto)
};
return user;
}
}
[DontRootQueryMeAttribute]
public class UserProfilePhoto
{
public int Id { get; set; }
public byte[] ProfilePhoto { get; private set; }
public UserProfilePhoto(byte[] profilePhoto)
{
ProfilePhoto = profilePhoto;
}
}
public abstract class ModelContextBase : DbContext
{
private static readonly string[] OverriddenMethodNames =
{
nameof(DbContext.Set),
nameof(DbContext.Query),
nameof(DbContext.Find),
nameof(DbContext.FindAsync),
};
static ModelContextBase()
{
var type = typeof(ModelContextBase);
var overriddenMethods = type
.GetRuntimeMethods()
.Where(
m => m.IsPublic &&
!m.IsStatic &&
OverriddenMethodNames.Contains(m.Name) &&
m.GetRuntimeBaseDefinition() != null)
.Select(m => m.GetRuntimeBaseDefinition())
.ToArray();
var missingOverrides = type.BaseType
.GetRuntimeMethods()
.Where(
m => m.IsPublic &&
!m.IsStatic &&
OverriddenMethodNames.Contains(m.Name) &&
!overriddenMethods.Contains(m))
.ToArray();
if (missingOverrides.Length > 0)
{
throw new InvalidOperationException(
$"The '{nameof(ModelContextBase)}' class is missing overrides for {string.Join(", ", missingOverrides.Select(m => m.Name))}.");
}
}
private void EnsureRootQueryAllowed<TEntity>()
=> EnsureRootQueryAllowed(typeof(TEntity));
private void EnsureRootQueryAllowed(Type type)
{
var rootQueriesAllowed = type.GetCustomAttribute(typeof(DontRootQueryMeAttribute)) == null;
if (!rootQueriesAllowed)
throw new InvalidOperationException($"Directly querying for '{type.Name}' is prohibited.");
}
public override DbSet<TEntity> Set<TEntity>()
{
EnsureRootQueryAllowed<TEntity>();
return base.Set<TEntity>();
}
public override DbQuery<TQuery> Query<TQuery>()
{
EnsureRootQueryAllowed<TQuery>();
return base.Query<TQuery>();
}
public override object Find(Type entityType, params object[] keyValues)
{
EnsureRootQueryAllowed(entityType);
return base.Find(entityType, keyValues);
}
public override ValueTask<object> FindAsync(Type entityType, params object[] keyValues)
{
EnsureRootQueryAllowed(entityType);
return base.FindAsync(entityType, keyValues);
}
public override ValueTask<object> FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken)
{
EnsureRootQueryAllowed(entityType);
return base.FindAsync(entityType, keyValues, cancellationToken);
}
public override TEntity Find<TEntity>(params object[] keyValues)
{
EnsureRootQueryAllowed<TEntity>();
return base.Find<TEntity>(keyValues);
}
public override ValueTask<TEntity> FindAsync<TEntity>(params object[] keyValues)
{
EnsureRootQueryAllowed<TEntity>();
return base.FindAsync<TEntity>(keyValues);
}
public override ValueTask<TEntity> FindAsync<TEntity>(object[] keyValues, CancellationToken cancellationToken)
{
EnsureRootQueryAllowed<TEntity>();
return base.FindAsync<TEntity>(keyValues, cancellationToken);
}
// Add other overrides as needed...
}
public class ModelContext : ModelContextBase
{
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63887500_01")
.UseLoggerFactory(LoggerFactory.Create(b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
OnUserModelCreating(modelBuilder);
}
protected void OnUserModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>(
entity =>
{
entity.HasOne(e => e.UserProfilePhoto)
.WithOne()
.HasForeignKey<UserProfilePhoto>(e => e.Id);
});
}
}
internal static class Program
{
private static void Main()
{
var accessingSetThrows = false;
using (var ctx = new ModelContext())
{
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
var user = User.Create(new byte[] { });
ctx.Users.Add(user);
ctx.SaveChanges();
// Make sure, that UserProfilePhoto cannot be queried directly.
try
{
ctx.Set<UserProfilePhoto>()
.ToList();
}
catch (InvalidOperationException)
{
accessingSetThrows = true;
}
Debug.Assert(accessingSetThrows);
}
// No eager loading by default with owned type here.
using (var ctx = new ModelContext())
{
var users = ctx.Users.ToList();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto == null);
}
// Explicitly load profile photo.
using (var ctx = new ModelContext())
{
var users = ctx.Users.ToList();
ctx.Entry(users[0]).Reference(u => u.UserProfilePhoto).Load();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto != null);
}
}
}
}
Providing an IQueryTranslationPreprocessorFactory implementation
An expression visitor can be used to solve the issue by using an IQueryTranslationPreprocessorFactory implementation to search the query for a specific expression, that is only added when the new InternalQuery() extension method is called and throwing, if it is missing and a non-root entity is being queried. In practice, this should be good enough to make sure, that nobody in the team queries non-root objects by accident.
(You could also add an internal class instance as a constant parameter to the method call expression, that is then evaluated later in the expression visitor to ensure, that the caller really had internal access to the InternalQuery() methods. But this is just icing on the cake and unnecessary in practice, since developers could use reflection to bypass any access restrictions anyway. So I wouldn't bother to implement this.)
Here it the implementation (using a custom interface instead of a custom attribute to mark entities that should not be queried directly):
using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
#region Models
public class User
{
public int Id { get; private set; }
public virtual UserProfilePhoto UserProfilePhoto { get; set; }
public static User Create(byte[] profilePhoto)
{
var user = new User
{
UserProfilePhoto = new UserProfilePhoto(profilePhoto)
};
return user;
}
}
public class UserProfilePhoto : INonRootQueryable
{
public int Id { get; set; }
public byte[] ProfilePhoto { get; private set; }
public UserProfilePhoto(byte[] profilePhoto)
{
ProfilePhoto = profilePhoto;
}
}
#endregion
#region Custom implementations
public interface INonRootQueryable
{
}
public class CustomQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
{
private readonly QueryTranslationPreprocessorDependencies _dependencies;
private readonly RelationalQueryTranslationPreprocessorDependencies _relationalDependencies;
public CustomQueryTranslationPreprocessorFactory(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
_dependencies = dependencies;
_relationalDependencies = relationalDependencies;
}
public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
=> new CustomQueryTranslationPreprocessor(
_dependencies,
_relationalDependencies,
queryCompilationContext);
}
public class CustomQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
{
public CustomQueryTranslationPreprocessor(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
}
public override Expression Process(Expression query)
{
query = new ThrowOnNoneRootQueryableViolationExpressionVisitor().Visit(query);
return base.Process(query);
}
}
public class ThrowOnNoneRootQueryableViolationExpressionVisitor : ExpressionVisitor
{
private bool _isInternalQuery;
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.GetGenericMethodDefinition() == CustomQueryableExtensions.InternalQueryMethodInfo)
{
_isInternalQuery = true;
return node.Arguments[0];
}
return base.VisitMethodCall(node);
}
protected override Expression VisitConstant(ConstantExpression node)
{
var expression = base.VisitConstant(node);
// Throws if SomeEntity in a DbSet<SomeEntity> implements INonRootQueryable and the query was not chained
// to the `InternalQuery()` extension method.
return !_isInternalQuery &&
node.Type.IsGenericType &&
node.Type.GetGenericTypeDefinition() == typeof(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<>) &&
node.Type.GenericTypeArguments.Length == 1 &&
typeof(INonRootQueryable).IsAssignableFrom(node.Type.GenericTypeArguments[0])
? throw new InvalidOperationException($"Directly querying for '{node.Type.Name}' is prohibited.")
: expression;
}
}
internal static class CustomQueryableExtensions
{
internal static readonly MethodInfo InternalQueryMethodInfo
= typeof(CustomQueryableExtensions)
.GetTypeInfo()
.GetDeclaredMethods(nameof(InternalQuery))
.Single(m => m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType.Namespace == $"{nameof(System)}.{nameof(System.Linq)}" &&
m.GetParameters()[0].ParameterType.Name.StartsWith(nameof(IQueryable)) &&
m.GetParameters()[0].ParameterType.GenericTypeArguments.Length == 1);
internal static IQueryable<TSource> InternalQuery<TSource>(this IQueryable<TSource> source)
=> source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
InternalQueryMethodInfo.MakeGenericMethod(typeof(TSource)),
source.Expression));
internal static IQueryable<TProperty> InternalQuery<TEntity, TProperty>(this ReferenceEntry<TEntity, TProperty> source)
where TEntity : class
where TProperty : class
=> source.Query()
.InternalQuery();
}
#endregion
public class ModelContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Register the custom type IQueryTranslationPreprocessorFactory.
// Since this is a console program, we need to create our own ServiceCollection
// for this.
// In an ASP.NET Core application, the AddSingleton call can just be added to
// the general service configuration method.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.AddSingleton<IQueryTranslationPreprocessorFactory, CustomQueryTranslationPreprocessorFactory>()
.AddScoped(
s => LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.BuildServiceProvider();
optionsBuilder
.UseInternalServiceProvider(serviceProvider) // <-- use our ServiceProvider
.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63887500_05")
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
OnUserModelCreating(modelBuilder);
}
protected void OnUserModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>(
entity =>
{
entity.HasOne(e => e.UserProfilePhoto)
.WithOne()
.HasForeignKey<UserProfilePhoto>(e => e.Id);
});
}
}
internal static class Program
{
private static void Main()
{
var accessingSetThrows = false;
using (var ctx = new ModelContext())
{
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
var user = User.Create(new byte[] { });
ctx.Users.Add(user);
ctx.SaveChanges();
}
// Make sure, that UserProfilePhoto cannot be queried directly by default.
using (var ctx = new ModelContext())
{
try
{
ctx.Set<UserProfilePhoto>()
.ToList();
}
catch (InvalidOperationException)
{
accessingSetThrows = true;
}
Debug.Assert(accessingSetThrows);
}
// Make sure, that UserProfilePhoto can be queried directly, when using the `InternalQuery()` extension
// method.
using (var ctx = new ModelContext())
{
var userProfilePhotos = ctx.Set<UserProfilePhoto>()
.InternalQuery()
.ToList();
Debug.Assert(userProfilePhotos.Count == 1);
}
// No eager loading of referenced types by default.
using (var ctx = new ModelContext())
{
var users = ctx.Users.ToList();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto == null);
}
// Eager loading of referenced types is allowed, when using the `InternalQuery()` extension method.
using (var ctx = new ModelContext())
{
var users = ctx.Users
.Include(u => u.UserProfilePhoto)
.InternalQuery()
.ToList();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto != null);
}
// Explicitly load profile photo, when using the `InternalQuery()` extension method.
using (var ctx = new ModelContext())
{
var users = ctx.Users.ToList();
ctx.Entry(users[0])
.Reference(u => u.UserProfilePhoto)
.InternalQuery()
.Load();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto != null);
}
}
}
}
Should bytes really be part of the domain? Do you actually run any business logic on those bytes in the user profile context? Is there really a use case where you'd want to access the bytes from within the User AR?
If not then perhaps it makes more sense decoupling the bytes storage from the photo's metadata and introduce a ProfilePhoto VO with a storageUrl/storageId property to locate the bytes.
Don't forget that your domain model should be designed for commands, not queries & the presentation layer.
Granted, now you can't easily have ACID properties when storing the bytes & the AR's data in the DB, but it's usually easy to cope with that with a cleanup process.
If you don't need profile photo's metadata in User to enforce business rules then you may also consider making ProfilePhoto it's own AR.
Finally, I think trying to prevent ORM misuse is unnecessary. The ORM should be seen as a low-level API which shouldn't ever be used directly to change AR states. I think it's safe to assume developers will have enough rigour to respect that rule just like they should respect the overall system's architecture. If they don't you have bigger problems. If it was as easy as adding a private modifier to a member then sure, but it seems to be needing a lot of efforts so I'd just go the pragmatic way...
I found a temporary solution:
modelBuilder.Entity<User>()
.OwnsOne(u => u.UserProfilePhoto, builder =>
{
builder.Metadata.IsOwnership = false;
builder.Metadata.IsRequired = false;
builder.Metadata.PrincipalToDependent.SetIsEagerLoaded(false);
builder.ToTable("UserProfilePhoto");
builder.Property(u => u.ProfilePhoto)
.IsRequired();
});
I do not like it and I guess EF allows you to configure that in other, more clear, ways. I am not accepting this answer, hoping someone else could point me in the right direction.
EDIT: proxy works this way but when a User is deleted, the association with the UserProfilePhoto is severed:
The association between entities 'User' and 'UserProfilePhoto' with
the key value '{UserId: 1}' has been severed but the relationship is
either marked as 'Required' or is implicitly required because the
foreign key is not nullable. If the dependent/child entity should be
deleted when a required relationship is severed, then setup the
relationship to use cascade deletes.
I even tried to specify through metadata the DeleteBehaviour.Cascade option but it probably breaks an internal constraint.
Moreover, it is now accessible via DbContext.Set<UserProfilephoto>(), which is not what I want.
I am using code first approach to connect with database and tables but due to some issue enable/add migration command is not creating my tables so I created tables manually. Th application build successfully that means I assume the objDbContext get my table. The name of Table is Task in database.
Below is my code
eDbContext objDbContext = new eDbContext ();
public List<TaskDetail> GetTasks(long eventId)
{
List<TaskDetail> listTask = new List<TaskDetail>();
try {
listTask = (from task in objDbContext.Tasks
where task.EventId==eventId
select new TaskDetail
{
Id = task.Id,
Title = task.Title,
Description = task.Description,
StartDate = task.StartDate,
EndDate = task.EndDate
}
).ToList();
}
catch(Exception ex) {
throw ex;
}
return listTask;
}
Below is database context
public class eDbContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
}
If you have similar problem (plural table names) for other entities, then you should remove PluralizingTableNameConvention (by default EF generates plural table names from entity type names). Add this code to your DbContext class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
If other tables have plural names, then you should just fix mapping for Task entity as #Valkyriee suggested.
Your DbContext Class should look like this:
public class eDbContext : DbContext
{
public IebContext()
: base("name=ConnectionStringName")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<eDbContext, Migrations.Configuration>("CatalogName"));
}
public DbSet<Task> Tasks{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new TaskMap());
}
}
For your Migration you can create a new class like:
internal sealed class Configuration : DbMigrationsConfiguration<eDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
//know this might loss data while its true.
AutomaticMigrationDataLossAllowed = true;
ContextKey = "Path to your DbContext Class";
}
protected override void Seed(eDbContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
Now using this approach you can create your tables with EF code-first and change them later on. Note that I've added a Map Class for Tasks which means i am using fluent api for Mapping my entity:
public class TaskMap : EntityTypeConfiguration<Task>
{
public TaskMap ()
{
ToTable("Tasks");
HasKey(x => x.Id);
}
}
This is my abstract class:
namespace MusicStoreApp.BLL.Master
{
public abstract class Master<T>
{
MusicStoreEntities db = new MusicStoreEntities();
public void Add(T item)
{
db.T.Add(item);
db.SaveChanges();
}
}
}
This is my target output in the other classes:
public class AlbumRepository : Master<Album>
{
public void Add(Album item)
{
db.Albums.Add(item);
db.SaveChanges();
}
}
public class ArtistRepository : Master<Artist>
{
public void Add(Artist item)
{
db.Artists.Add(item);
db.SaveChanges();
}
}
What i am tring to do here, that i should create a reusable interface-like class. So, i can just type the name of the T reference and it will create the rest of the codes for me.
The way your sample is setup can't work because T needs to point to two different classes (the specific instance and the DbSet that contains that class). Instead, try this:
namespace MusicStoreApp.BLL.Master
{
public abstract class Master<T>
{
MusicStoreEntities db = new MusicStoreEntities();
public void Add(T item)
{
db.Entry(item).State = System.Data.Entity.EntityState.Added;
db.SaveChanges();
}
}
}
You don't need this T anonymous type. Just do something like this:
public abstract class Master
{
public abstract void Add(Master item);
}
Then you can just inherit the Master like this:
public class Album : Master
public override void Add(Album item)
{
db.Albums.Add(item);
db.SaveChanges();
}
}
If you want to use a repository for the add just remove the add function from master and make interface and inherit from it:
public interface IMasterRepository
{
public void Add(Master item);
}
public class AlbumRepository : IMasterRepository
public override void Add(Album item)
{
db.Albums.Add(item);
db.SaveChanges();
}
}
But don't mix the entity classes with the repositories.
You are mixing abstract with generic class. The former contains something that requires to be implemented by the inheritors while the later provides common implementation that differs by some type(s) of the objects involved. From your explanation (and since your "abstract" class does not contain any abstract method), looks like you need a generic class. Something like this
public class Master<T>
{
MusicStoreEntities db = new MusicStoreEntities();
public void Add(T item)
{
db.Set<T>().Add(item);
db.SaveChanges();
}
}
public class AlbumRepository : Master<Album> { }
public class ArtistRepository : Master<Artist> { }
Note that you don't even need the concrete classes (if that's all they are supposed to do).
You can do so by using reflection.
get property name
string PropertyName = T.GetType().Name + "s";
retrive the entity property
var property = db.GetType().Properties.Where(x => x.Name.CompareTo(PropertyName) == 0).FirstOrDefault();
then work with it directly
Thank you for all of your effort to answer :).
I found my answer, I hope it will help you too:
public abstract class RepositoryBase<T>:IRepository<T> where T:class
{
public void Add(T item)
{
db.Set<T>().Add(item);
db.SaveChanges();
}
public void Update(int id,T item)
{
db.Entry(db.Set<T>().Find(id)).CurrentValues.SetValues(item);
db.SaveChanges();
}
public void Delete(T item)
{
db.Set<T>().Remove(item);
db.SaveChanges();
}
public List<T> SelectAll()
{
return db.Set<T>().ToList();
}
public T SelectByID(int id)
{
return db.Set<T>().Find(id);
}
}
I have ready my model on code first EF and I try it on sql express and it works. But I have a problem translating it to a sql server: I don't have the permissions to recreate a database I can only add tables to an empty database.
I already see this answer but when I'm trying to replicate it I have some troubles with the context part:
public class DropCreateDatabaseTables : IDatabaseInitializer<Context> {
#region IDatabaseInitializer<Context> Members
public void InitializeDatabase(Context context)
I already put the reference to System.Data.Entity but that don't work and the Context class not is the referenced on System.Runtime.Remoting.Contexts
There is something wrong in the code? Or is a better solution with the last tools of EF?
EDIT:
Finally was:
DbContext:
public class PeopleContext: DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Adress> Adresses{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Add Entity type configuration classes
modelBuilder.Configurations.Add(new PersonConfiguration());
modelBuilder.Configurations.Add(new AdressConfiguration());
}
}
Initializer:
public class DropCreateDatabaseTables : IDatabaseInitializer<PeopleContext>
{
public void InitializeDatabase(PeopleContextContext)
{
bool dbExists;
using (new TransactionScope(TransactionScopeOption.Suppress))
{
dbExists = Context.Database.Exists();
}
if (dbExists)
{
// remove all tables
Context.Database.ExecuteSqlCommand("EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'");
Context.Database.ExecuteSqlCommand("EXEC sp_MSforeachtable #command1 = \"DROP TABLE ?\"");
// create all tables
var dbCreationScript = ((IObjectContextAdapter)Context).ObjectContext.CreateDatabaseScript();
Context.Database.ExecuteSqlCommand(dbCreationScript);
Context.SaveChanges();
}
else
{
throw new ApplicationException("No database instance");
}
}
}
Call:
class Program
{
static void Main(string[] args)
{
var person= new Person
{
Identifier= "John Doe"
};
Database.SetInitializer(new DropCreateDatabaseTables());
using (var context = new PeopleContext())
{
context.People.Add(person);
context.SaveChanges();
}
}
}
Thanks Lukas Kabrt!
The Context class in the example should be your DbContext class i.e. the class where you specify your DbSet<>s.
Example:
DbContext class
public class DataContext : DbContext {
public DbSet<Customer> Customers { get; set; }
}
DatabaseInitializer
public class DropCreateDatabaseTables : IDatabaseInitializer<DataContext> {
...
}
I have an entity A that used as navigation property for another entity B. I would like to control the Insertion for entity A. Thats meen that whenever I would insert A I have to perform other checks and update other entities.
But when I Insert entities of type B it automatically inserts the connected A entities without the extra checks and updates I need.
How can I solve this?
UPDATE
I decided to use this answer as suggested. But in the OnBeforeInsert() I might add a new entities to the context which their OnBeforeInsert() won't be called since in the time var changedEntities = ChangeTracker.Entries(); was called the new entitied wasn't exist yet.
How can I solve this?
EF has very few extension points. So it is sometimes very difficult to customize.
This answer is an extension of my previous answer
public abstract class Entity
{
public virtual void OnBeforeInsert(){}
public virtual void OnBeforeUpdate(){}
}
public class Category : Entity
{
public string Name { get; set; }
public string UrlName{ get; set; }
public override void OnBeforeInsert()
{
//ur logic
}
}
Then in your DbContext class subscribe to ObjectStateManagerChanged event of ObjectStateManager.
public class MyContext : DbContext
{
public override int SaveChanges()
{
//intercept entity changes
UnderlyingObjectContext.ObjectStateManager.ObjectStateManagerChanged
+= OnObjectStateManagerChanged;
var changedEntities = ChangeTracker.Entries();
foreach (var changedEntity in changedEntities)
{
if (changedEntity.Entity is Entity)
{
var entity = (Entity)changedEntity.Entity;
switch (changedEntity.State)
{
case EntityState.Added:
entity.OnBeforeInsert();
break;
case EntityState.Modified:
entity.OnBeforeUpdate();
break;
}
}
}
return base.SaveChanges();
}
ObjectContext UnderlyingObjectContext
{
get
{
return ((IObjectContextAdapter)this).ObjectContext;
}
}
void OnObjectStateManagerChanged(object sender, CollectionChangeEventArgs e)
{
if (e.Action == CollectionChangeAction.Add)
{
//not all added entities are new
if (UnderlyingObjectContext.ObjectStateManager
.GetObjectStateEntry(e.Element).State == EntityState.Added)
{
if (e.Element is Entity)
{
((Entity)e.Element).OnBeforeInsert();
}
}
}
}
}
If you are using EF 4.0 you will need to customize this accordingly.