Save many to many relation via view model in entity framework 6 - entity-framework

I have two entities (TaskStep and Role) with many to many relation, I use a view model (named StepViewModel) to display taskstep and it's allowed Roles, StepViewModel is :
public class StepViewModel
{
public StepViewModel()
{
}
public TaskStep TaskStep { get; set; }
public virtual ICollection<AllowedRole> AllowedRole { get; set; }
}
and I use this method to display taskstep:
public static StepViewModel GetTaskStepData(int stepId)
{
using (var context = new WFContext())
{
return context.TaskSteps.Where(ts => ts.ID == stepId).Include(ts => ts.Roles)
.Select(ts => new StepViewModel()
{
AllowedRole = context.Roles.Select(r => new AllowedRole()
{
Role = r,
Allowed = ts.Roles.Select(x => x.ID).Contains(r.ID)
}).ToList(),
TaskStep = ts
})
.SingleOrDefault();
}
}
but when I save the taskstep, it's roles changes don't save. My save method is :
public static void SaveTaskStep(StepViewModel stepViewModel)
{
using (var context = new WorkFlowContext())
{
//TaskStep taskStep = context.TaskSteps.Where(ts => ts.ID == stepViewModel.TaskStep.ID).Include(ts => ts.Roles).SingleOrDefault();
TaskStep taskStep = stepViewModel.TaskStep;
List<int> aRolesIDs = stepViewModel.AllowedRole.Where(ar => ar.Allowed == true).Select(ar => ar.Role.ID).ToList();
List<Role> roles = context.Roles.Where(r => aRolesIDs.Contains(r.ID)).Include(r => r.AllowedTaskSteps).ToList();
taskStep.Roles.Clear();
foreach (Role role in roles)
{
if (context.Entry(role).State == EntityState.Detached)
context.Roles.Attach(role);
taskStep.Roles.Add(role);
}
if (taskStep.ID == 0)
context.TaskSteps.Add(taskStep);
else
context.Entry(taskStep).State = EntityState.Modified;
context.SaveChanges();
}
}
if I get taskstep from db with include roles (like first -comment- line in save method), it work's well. but in this way, I have to make all taskstep field's changes manually. In addition when I assign taskstep to stepviewmodel,I use "include" but it's still doesn't work.what can I do to save stepViewModel roles?

context.TaskSteps.Attach(taskStep) should work in your case
SaveTaskStep method:
using (var context = new WorkFlowContext())
{
TaskStep taskStep = stepViewModel.TaskStep;
if (taskStep.ID == 0)
context.TaskSteps.Add(taskStep);
else
context.TaskSteps.Attach(taskStep); //from this point EF start change tracking
//I just copy-pasted this part your code assuming it's correct
List<int> aRolesIDs = stepViewModel.AllowedRole.Where(ar => ar.Allowed == true).Select(ar => ar.Role.ID).ToList();
List<Role> roles = context.Roles.Where(r => aRolesIDs.Contains(r.ID)).Include(r => r.AllowedTaskSteps).ToList();
taskStep.Roles.Clear();
foreach (Role role in roles)
{
if (context.Entry(role).State == EntityState.Detached)
context.Roles.Attach(role);
taskStep.Roles.Add(role);
}
context.SaveChanges();
} // end of using (var context

Related

Analyse the tables involved in an IQueryable

I have a requirement where I need to look at all of the tables involved in a query about to be run by EF Core via an IQueryable
Has anyone ever been able to do this?
Lets use an example
var cars = await (from car in DbContext.Cars
from salesperson in DbContext.SalesPersons.Where(x=>x.Id == car.SalesPersonId)
.Select(x=>x.Car));
Lets now say that there is a CountryId column in the salespersons table and the Cars table
I need to detect that the IQueryable above is using Cars and SalesPerson
Then I will add to the IQueryable so it becomes
var cars = await (from car in DbContext.Cars
from salesperson in DbContext.SalesPersons.Where(x=>x.Id == car.SalesPersonId).Select(x=>x.Car)
.Where(car.CountryId = 1).Where(salesPerson.CountryId = 1);
So we are basically adding a filter at runtime automatically
Cheers
Paul
It is not needed to analyze LINQ query for your task. For filtering by tenant there is Global Query Filters. After configuring EF Core should apply filter for every entity which has defined Query Filter.
public class TenantContext : DbContext
{
public int? TenantId { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
... // entities configuration
modelBuilder.Entity<Some>().HasQueryFilter(e => TenantId == null || TenantId == e.TenantId)
}
}
All that you need before querying data - set TenantId for context
context.TeantId = 2;
context.Some.ToList() // table will be filtered by TenantId == 2
I have prepared small sample how to apply tenant filter for all entities in one function call:
public class TenantContext : DbContext
{
public int? TenantId { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
... // entities configuration
ApplyTenantQueryFilters(modelBuilder, "TenantId", () => TenantId);
}
private void ApplyTenantQueryFilters<TProp>(ModelBuilder builder, string tenantPropName, Expression<Func<TProp>> tenantPropExpr)
{
foreach (var entityType in builder.Model.GetEntityTypes())
{
var tenantProp = entityType.GetProperties().FirstOrDefault(p => p.Name == tenantPropName);
if (tenantProp == null)
continue;
var entityParam = Expression.Parameter(entityType.ClrType, "e");
var contextTenantPropAccess = tenantPropExpr.Body;
var propertyExpression = GetPropertyExpression(entityParam, tenantProp);
if (propertyExpression.Type != contextTenantPropAccess.Type)
propertyExpression = Expression.Convert(propertyExpression, contextTenantPropAccess.Type);
// ctx.TenantId == null || ctx.TenantId == e.TenantId
var filterBody = (Expression)Expression.OrElse(
Expression.Equal(contextTenantPropAccess, Expression.Default(contextTenantPropAccess.Type)),
Expression.Equal(contextTenantPropAccess,
propertyExpression));
var filterLambda = entityType.GetQueryFilter();
// we have to combine filters
if (filterLambda != null)
{
filterBody = ReplacingExpressionVisitor.Replace(entityParam, filterLambda.Parameters[0], filterBody);
filterBody = Expression.AndAlso(filterLambda.Body, filterBody);
filterLambda = Expression.Lambda(filterBody, filterLambda.Parameters);
}
else
{
filterLambda = Expression.Lambda(filterBody, entityParam);
}
entityType.SetQueryFilter(filterLambda);
}
}
private static Expression GetPropertyExpression(Expression objExpression, IProperty property)
{
Expression propExpression;
if (property.PropertyInfo == null)
{
// 'property' is Shadow property, so call via EF.Property(e, "name")
propExpression = Expression.Call(typeof(EF), nameof(EF.Property), new[] { property.ClrType },
objExpression, Expression.Constant(property.Name));
}
else
{
propExpression = Expression.MakeMemberAccess(objExpression, property.PropertyInfo);
}
return propExpression;
}
}

What should my repository return on a http post when the posted id (foreign key) is wrong

Given the user sends a valid token to an api endpoint via fiddler/postman, he could post a resource (pupil) for a related resource (schoolclass).
When the schoolclass id
does not exist yet in the database
does exist already in the database but this schoolclass Id belongs to another user.
does exist in the database and belongs to the passed userId
Then
What would you change in the Controller and Repository class to make it work for all 3 cases using a REST api + repository pattern.
Controller:
[HttpPost("~/api/pupils")]
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
return Ok(pupil.Id);
}
Repository:
public async Task CreatePupil(Pupil pupil, int schoolclassCodeId, string userId)
{
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode != null)
{
schoolclassCode.Pupils.Add(pupil);
await context.SaveChangesAsync();
}
}
NOTE
At the moment the last of the 3 use cases is implemented!
From REST prospective you need to return 400 or 404 depending on your design.
If your route need to be like /classes/{id}/users/{id}/pupil I thing you need to use 404 in case user or class is wrong.
In case of separate route (as I can see in your question) I think this should be 400 code as request URL is pointing to valid resource but payload is invalid.
In both cases I think the batter error handling strategy here is to write some set of custom exceptions (like EntityNotFondException, EntityInvalidException, BusinessLogicException) and throw them from repository in case something is wrong. Then you can create some global action filter or OWIN middleware to catch those exceptions and translate them to correct response status codes with appropriate messages
Example:
public class NotFoundException : Exception
{
public NotFoundException(Type entityType)
: base($"Entity {entityType.Name} was not found")
{
}
}
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
public ApiExceptionFilterAttribute()
{
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var exception = actionExecutedContext.Exception;
if (exception == null)
return;
if (exception is HttpResponseException)
return;
var entityNotFoundException = exception as NotFoundException;
if (entityNotFoundException != null)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, entityNotFoundException.Message);
return;
}
}
}
Usage:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if(schoolclassCode == null)
throw new NotFoundException(typeof(Schoolclass));
You can throw validation exceptions in the same way. E.g:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId);
if(schoolclassCode == null)
throw new InvalidModelStateException("Schoolclass was not found.")
if(schoolclassCode.UserId != userId)
throw new InvalidModelStateException("Schoolclass is owned by different user.")
... etc.
I always use Result classes for returning state from a service class (wouldn't implement that in Repository as it shouldn't contain business logic):
public class QueryResult
{
private static readonly QueryResult success = new QueryResult { Succeeded = true };
private readonly List<QueryError> errors = new List<QueryError>();
public static QueryResult Success { get { return success; } }
public bool Succeeded { get; protected set; }
public IEnumerable<QueryError> Errors { get { return errors; } }
public static QueryResult Failed(params QueryError[] errors)
{
var result = new QueryResult { Succeeded = false };
if (errors != null)
{
result.errors.AddRange(errors);
}
return result;
}
}
public class QueryResult<T> : QueryResult where T : class
{
public T Result { get; protected set; }
public static QueryResult<T> Suceeded(T result)
{
if (result == null)
throw new ArgumentNullException(nameof(result));
var queryResult = new QueryResult<T>
{
Succeeded = true,
Result = result
};
return queryResult;
}
}
public class QueryError
{
public string ErrorId { get; set; }
public string ErrorMessage { get; set; }
}
And use it like
var schoolclassCode = await context.Schoolclasses
.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode == null)
return QueryResult.Failed(new QueryError
{
ErrorId = 1,
ErrorMessage = "Invalid User Id"
});
Edit:
Just as an addition and rule of thumb
Services which operate on one or multiple entities and perform user input validation should return Result classes
Domain Models (which you don't seem to use, since you use a repository and Repository + Rich Domains doesn't work out well in real life applications) should throw exception (i.e. InvalidOperationException or ArgumentException, ArgumentNullException). Doing Result-types her will pollute the model and mix the separation of responsibility (Domain Model will suddenly also do validation instead only guarding against invalid state)
Using XxxResult type classes gives you an easy way to transport one or multiple errors back to the user, where an exception should act as an guard against your domain model getting into invalid state.
Edit 2
In response to the comments:
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
var result = await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok(pupil.Id);
}
Edit 3
Example with 3 parameters for let's say /api/schoolclasses/1/students/2/lessons/2 (Update an existing lesson to the student with the id 2 for the school class with id 1).
// on SchoolClasses Controller
[HttpPost("{schoolClassId:int}/students/{studentId:int}/lessons/{lessonId:int}")]
public async Task<IActionResult> Post([FromBody]Lessons lessonDto)
{
// rough input validation, do first to avoid db hits
if(!ModelState.IsValid)
return BadRequest(ModelState);
// best put logic into service classes i.e. SchoolClassService
var result = schoolClassService.UpdateLessonFor(schoolClassId, studentId, lessonDto)
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok();
}
Content of UpdateLessonsFor
List<ErrorMessage> errors = new List<ErrorMessage>();
// with .Include to include both student and all of his lessons
// does student exist?
// Hits db once and gets both, student and all lessons in a single query
var student = _context.SchoolClasses
.Include(sc => sc.Students)
.ThenInclude(s => s.Lessons)
.Where(sc => sc.SchoolClassId == schoolClassId)
.SelectMany(sc => sc.Students)
FirstOrDefault(s => s.StudentId == studentId);
if(student==null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 1, ErrorMessage = "Student or School Class not found" } );
// Doesn't hit the database, since lessons have been loaded with the above call
var lesson = student.Lessons.Any(l => l.LessonId = lessonId))
if(lesson == null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 2, ErrorMessage = "Lesson not found. " } );
// modify it
lesson.SomeValue = dto.SomeValue;
try
{
} catch(Exception ex) {
return QueryResult.Failed(new ErrorMessage { ErrorId = 3, ErrorMessage = "Couldn't update the lesson. Try again and if the error appears again, contact the administrator." } );
} finally {
return QueryResult.Suceeded;
// or if you also want to return a result
return QueryResult.Suceeded(lesson);
}
Also from the comments of the other answer: Don't put logic into your repository, that's what services are for when you use anemic domain (models have no logic, all in services) or have thin service layer and put most logic into domain service. But that's out of the scope.

When the user logs in how can I get that particular users details?

I am making a project where there are multiple users and each user can have any number of courses. When a user logs in I want to display only the courses for that particular user. The tables and its records are-
User
UserId
UserName
Password
Course
CourseId
CourseName
UserCourse
Id
UserId
CourseId
In home controller I am writing the code as -
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(User u)
{
if (ModelState.IsValid)
{
using (Entities dc = new Entities())
{
var v = dc.Users.Where(a => a.UserName.Equals(u.UserName) && a.Password.Equals(u.Password)).FirstOrDefault();
if (v != null)
{
Session["LoggedUserID"] = v.UserId.ToString();
Session["LoggedUserName"] = v.UserName.ToString();
return RedirectToAction("Index","Course");
}
}
}
return View(u);
}
Then I created a new controller with template MVC controller with read/write actions and views, using Entity Framework named UserController.
In UserController I have written code for getting particular users course details as-
public class UserController : Controller
{
private Entities db = new Entities();
public ActionResult Index()
{
int userId = (int)Session["LoggedUserID"];
var user = db.Users.SingleOrDefault(u=>u.UserId==userId);
if (user != null)
return View(user); // this
return View();
}
}
I am getting all the users courses after logging in when I want only that users details. What should I do? Please do help.
I finally worked it out. I had to pass the userId as well as the course for that user into the view. My latest working code is shown below. Hope it helps others.
HomeController:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(User u)
{
if (ModelState.IsValid)
{
using (DetailsEntities dc = new DetailsEntities())
{
var v = dc.Users.Where(a => a.UserName.Equals(u.UserName) && a.Password.Equals(u.Password)).FirstOrDefault();
if (v != null)
{
Session["User"] = v;
return RedirectToAction("Index", "Course");
}
}
}
return View(u);
}
CourseController:
public class CourseController : Controller
{
private Entities db = new Entities();
public ActionResult Index()
{
User user = (User)Session["User"];
var usr = db.Users.Find(user.UserId);
if (Session["User"] != null)
{
var course = db.UserCourses.Where(u => u.UserId == user.UserId);
if (usr != null)
return View(course);
}
return View(usr);
}
}

Using DbContext in MS unit test

I am not sure how to fit EF into my business logic tests. Let me give an example of how it works at runtime (no testing, regular application run):
Context.Set<T>.Add(instance);
When I add the entity using the above generic method, an instance is added to context, and EF fixes all the navigation properties behind the scenes. For example, if exists [instance.Parent] property, and [parent.Instances] collection property (1-to-many relationship), EF will automatically add the instance to parent.Instances collection behind the scenes.
My code depends on the [parent.Instances] collection, and if it is empty, it will fail. When I am writing unit tests using MS testing framework, how can I reuse the power of EF, so it can still do its behind-the-scenes job, but uaing the memory as data storage, and not the actual database? I am not really interested whether EF successfully added, modified or deleted something in the database, I am just interested in getting the EF magic on the in-memory sets.
I've been doing this with a mock DbContext and mock DbSet that I've created. They store test data in memory and allow you to do most of the standard things you can do on a DbSet.
Your code that acquires the DbContext initially will have to be changed so that it acquires a MockDbContext when it is running under unit test. You can determine if you are running under MSTest with the following code:
public static bool IsInUnitTest
{
get
{
return AppDomain.CurrentDomain.GetAssemblies()
.Any(assembly =>
assembly.FullName.StartsWith(
"Microsoft.VisualStudio.QualityTools.UnitTestFramework"));
}
}
Here is the code for MockDbContext:
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication5
{
// ProductionDbContext would be DbContext class
// generated by Entity Framework
public class MockDbContext: ProductionDbContext
{
public MockDbContext()
{
LoadFakeData();
}
// Entities (for which we'll provide MockDbSet implementation
// and test data)
public override DbSet<Account> Accounts { get; set; }
public override DbSet<AccountGenLink> AccountGenLinks { get; set; }
public override DbSet<AccountPermit> AccountPermits { get; set; }
public override DbSet<AcctDocGenLink> AcctDocGenLinks { get; set; }
// DbContext method overrides
private int InternalSaveChanges()
{
// Just return 0 in the mock
return 0;
}
public override int SaveChanges()
{
return InternalSaveChanges();
}
public override Task<int> SaveChangesAsync()
{
return Task.FromResult(InternalSaveChanges());
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
// Just ignore the cancellation token in the mock
return SaveChangesAsync();
}
private void LoadFakeData()
{
// Tables
Accounts = new MockDbSet<Account>(this);
Accounts.AddRange(new List<Account>
{
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "CD",
acctnumber = "1", pending = false, BankOfficer1 = string.Empty,
BankOfficer2 = null, Branch = 0, type = "18", drm_rate_code = "18",
officer_code = string.Empty, open_date = new DateTime(2010, 6, 8),
maturity_date = new DateTime(2010, 11, 8), HostAcctActive = true,
EffectiveAcctStatus = "A"
},
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "DD",
acctnumber = "00001234", pending = false, BankOfficer1 = "BCK",
BankOfficer2 = string.Empty, Branch = 0, type = "05", drm_rate_code = "00",
officer_code = "DJT", open_date = new DateTime(1998, 9, 14),
maturity_date = null, HostAcctActive = true,
EffectiveAcctStatus = "A"
},
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "LN", acctnumber = "1",
pending = false, BankOfficer1 = "LMP", BankOfficer2 = string.Empty,
Branch = 0, type = "7", drm_rate_code = null, officer_code = string.Empty,
open_date = new DateTime(2001, 10, 24),
maturity_date = new DateTime(2008, 5, 2), HostAcctActive = true,
EffectiveAcctStatus = "A"
}
});
AccountGenLinks = new MockDbSet<AccountGenLink>(this);
AccountGenLinks.AddRange(new List<AccountGenLink>
{
// Add your test data here if needed
});
AccountPermits = new MockDbSet<AccountPermit>(this);
AccountPermits.AddRange(new List<AccountPermit>
{
// Add your test data here if needed
});
AcctDocLinks = new MockDbSet<AcctDocLink>(this);
AcctDocLinks.AddRange(new List<AcctDocLink>
{
new AcctDocLink { ID = 1, SSN_EIN = "123456789", CODE = "A", accttype = "DD",
acctnumber = "00001234", DocID = 50, DocType = 5 },
new AcctDocLink { ID = 25, SSN_EIN = "123456789", CODE = "6", accttype = "CD",
acctnumber = "1", DocID = 6750, DocType = 5 }
});
}
}
}
And here is the code for MockDbSet:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication5
{
public sealed class MockDbSet<TEntity> : DbSet<TEntity>, IQueryable,
IEnumerable<TEntity>, IDbAsyncEnumerable<TEntity> where TEntity : class
{
public MockDbSet(MockDbContext context)
{
// Get entity set for entity
// Used when we figure out whether to generate
// IDENTITY values
EntitySet = ((IObjectContextAdapter) context).ObjectContext
.MetadataWorkspace
.GetItems<EntityContainer>(DataSpace.SSpace).First()
.BaseEntitySets
.FirstOrDefault(item => item.Name == typeof(TEntity).Name);
Data = new ObservableCollection<TEntity>();
Query = Data.AsQueryable();
}
private ObservableCollection<TEntity> Data { get; set; }
Type IQueryable.ElementType
{
get { return Query.ElementType; }
}
private EntitySetBase EntitySet { get; set; }
Expression IQueryable.Expression
{
get { return Query.Expression; }
}
IEnumerator IEnumerable.GetEnumerator()
{
return Data.GetEnumerator();
}
public override ObservableCollection<TEntity> Local
{
get { return Data; }
}
IQueryProvider IQueryable.Provider
{
get { return new MockDbAsyncQueryProvider<TEntity>(Query.Provider); }
}
private IQueryable Query { get; set; }
public override TEntity Add(TEntity entity)
{
GenerateIdentityColumnValues(entity);
Data.Add(entity);
return entity;
}
public override IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities)
{
foreach (var entity in entities)
Add(entity);
return entities;
}
public override TEntity Attach(TEntity entity)
{
return Add(entity);
}
public override TEntity Create()
{
return Activator.CreateInstance<TEntity>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override TEntity Find(params object[] keyValues)
{
throw new NotSupportedException();
}
public override Task<TEntity> FindAsync(params object[] keyValues)
{
return FindAsync(CancellationToken.None, keyValues);
}
public override Task<TEntity> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
{
throw new NotSupportedException();
}
private void GenerateIdentityColumnValues(TEntity entity)
{
// The purpose of this method, which is called when adding a row,
// is to ensure that Identity column values are properly initialized
// before performing the add. If we were making a "real" Entity Framework
// Add() call, this task would be handled by the data provider and the
// value(s) would then be propagated back into the entity. In the case
// of this mock, there is nothing that will do that, so we have to make
// this at-least token effort to ensure the columns are properly initialized.
// In SQL Server, an Identity column can be of one of the following
// data types: tinyint, smallint, int, bigint, decimal (with a scale of 0),
// or numeric (with a scale of 0); This method handles the integer types
// (the others are typically not used).
foreach (var member in EntitySet.ElementType.Members.ToList())
{
if (member.IsStoreGeneratedIdentity)
{
// OK, we've got a live one; do our thing.
//
// Note that we'll get the current value of the column and,
// if it is nonzero, we'll leave it alone. We do this because
// the test data in our mock DbContext provides values for the
// Identity columns and many of those values are foreign keys
// in other entities (where we also provide test data). We don't
// want to disturb any existing relationships defined in the test data.
Type columnDataType = null;
foreach (var metadataProperty in member.TypeUsage.EdmType.MetadataProperties.ToList())
{
if (metadataProperty.Name != "PrimitiveTypeKind")
continue;
switch ((PrimitiveTypeKind)metadataProperty.Value)
{
case PrimitiveTypeKind.SByte:
columnDataType = typeof(SByte);
break;
case PrimitiveTypeKind.Int16:
columnDataType = typeof(Int16);
break;
case PrimitiveTypeKind.Int32:
columnDataType = typeof(Int32);
break;
case PrimitiveTypeKind.Int64:
columnDataType = typeof(Int64);
break;
default:
throw new InvalidOperationException();
}
var identityColumnGetter = entity.GetType().GetProperty(member.Name).GetGetMethod();
var identityColumnSetter = entity.GetType().GetProperty(member.Name).GetSetMethod();
Int64 specifiedColumnValue = 0;
switch (columnDataType.Name)
{
case "SByte":
specifiedColumnValue = (SByte)identityColumnGetter.Invoke(entity, null);
break;
case "Int16":
specifiedColumnValue = (Int16)identityColumnGetter.Invoke(entity, null);
break;
case "Int32":
specifiedColumnValue = (Int32)identityColumnGetter.Invoke(entity, null);
break;
case "Int64":
specifiedColumnValue = (Int64)identityColumnGetter.Invoke(entity, null);
break;
}
if (specifiedColumnValue != 0)
break;
Int64 maxExistingColumnValue = 0;
switch (columnDataType.Name)
{
case "SByte":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (SByte)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (SByte)(++maxExistingColumnValue) });
break;
case "Int16":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int16)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int16)(++maxExistingColumnValue) });
break;
case "Int32":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int32)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int32)(++maxExistingColumnValue) });
break;
case "Int64":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int64)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int64)(++maxExistingColumnValue) });
break;
}
}
}
}
}
IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.GetAsyncEnumerator()
{
return new MockDbAsyncEnumerator<TEntity>(Data.GetEnumerator());
}
IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
return Data.GetEnumerator();
}
public override TEntity Remove(TEntity entity)
{
Data.Remove(entity);
return entity;
}
public override IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities)
{
foreach (var entity in entities)
Remove(entity);
return entities;
}
public override DbSqlQuery<TEntity> SqlQuery(string sql, params object[] parameters)
{
throw new NotSupportedException();
}
}
internal class MockDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
internal MockDbAsyncQueryProvider(IQueryProvider queryProvider)
{
QueryProvider = queryProvider;
}
private IQueryProvider QueryProvider { get; set; }
public IQueryable CreateQuery(Expression expression)
{
return new MockDbAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new MockDbAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return QueryProvider.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return QueryProvider.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class MockDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public MockDbAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{
}
public MockDbAsyncEnumerable(Expression expression)
: base(expression)
{
}
IQueryProvider IQueryable.Provider
{
get { return new MockDbAsyncQueryProvider<T>(this); }
}
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new MockDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
}
internal class MockDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
public MockDbAsyncEnumerator(IEnumerator<T> enumerator)
{
Enumerator = enumerator;
}
public void Dispose()
{
Enumerator.Dispose();
}
public T Current
{
get { return Enumerator.Current; }
}
object IDbAsyncEnumerator.Current
{
get { return Current; }
}
private IEnumerator<T> Enumerator { get; set; }
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Enumerator.MoveNext());
}
}
}
If you are using the EntityFramework-Reverse-POCO-Code-First-Generator from Simon Hughes, with its generated FakeContext, Jeff Prince's approach is still possible with some tweaking. The little difference here is we are using the partial class support and implementing the InitializePartial() methods in the FakeContext and FakeDbSet. The bigger difference is that the Reverse POCO FakeContext does not inherit from a DbContext, so we can't easily get the MetadataWorkspace to know which columns are identities. The answer is to create a 'real' context with a bogus connection string and use that to get the EntitySetBase for the FakeDbSet. This should be pasted inside the proper namespace of a new source file, renaming the context, and you shouldn't need to do anything further in the rest of your project.
/// <summary>
/// This code will set Identity columns to be unique. It behaves differently from the real context in that the
/// identities are generated on add, not save. This is inspired by https://stackoverflow.com/a/31795273/1185620 and
/// modified for use with the FakeDbSet and FakeContext that can be generated by EntityFramework-Reverse-POCO-Code-
/// First-Generator from Simon Hughes.
///
/// Aside from changing the name of the FakeContext and the type used to in its InitializePartial() as
/// the 'realContext' this file can be pasted into another namespace for a completely unrelated context. If you
/// have additional implementation for the InitializePartial methods in the FakeContext or FakeDbSet, change the
/// name to InitializePartial2 and they will be called after InitializePartial is called here. Please don't add
/// code unrelated to the above purpose to this file - make another file to further extend the partial class.
/// </summary>
partial class FakeFooBarBazContext
{
/// <summary> Initialization of FakeContext to handle setting an identity for columns marked as
/// <c>IsStoreGeneratedIdentity</c> when an item is Added to the DbSet. If this signature
/// conflicts with another partial class, change that signature to implement
/// <see cref="InitializePartial2"/>, as that will be called when this is complete. </summary>
partial void InitializePartial()
{
// Here we need to get a 'real' ObjectContext so we can get the metadata for determining
// identity columns. Since FakeContext doesn't inherit from DbContext, create
// the real one with a bogus connection string.
using (var realContext = new FooBarBazContext("Server=."))
{
var objectContext = (realContext as IObjectContextAdapter).ObjectContext;
// Reflect over the public properties that return DbSet<> and get it. If it is
// of type FakeDbSet<>, call InitializeWithContext() on it.
var fakeDbSetGenericType = typeof(FakeDbSet<>);
var dbSetGenericType = typeof(DbSet<>);
var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
if (!prop.PropertyType.IsGenericType || prop.GetMethod == null)
continue;
if (prop.PropertyType.GetGenericTypeDefinition() != dbSetGenericType)
continue;
var dbSetObj = prop.GetMethod.Invoke(this, null);
var dbSetObjType = dbSetObj?.GetType();
if (dbSetObjType?.GetGenericTypeDefinition() != fakeDbSetGenericType)
continue;
var initMethod = dbSetObjType.GetMethod(nameof(FakeDbSet<object>.InitializeWithContext),
BindingFlags.NonPublic | BindingFlags.Instance,
null, new[] {typeof(ObjectContext)}, new ParameterModifier[] { });
initMethod.Invoke(dbSetObj, new object[] {objectContext});
}
}
InitializePartial2();
}
partial void InitializePartial2();
}
partial class FakeDbSet<TEntity>
{
private EntitySetBase EntitySet { get; set; }
/// <summary> Initialization of FakeDbSet to handle setting an identity for columns marked as
/// <c>IsStoreGeneratedIdentity</c> when an item is Added to the DbSet. If this signature
/// conflicts with another partial class, change that signature to implement
/// <see cref="InitializePartial2"/>, as that will be called when this is complete. </summary>
partial void InitializePartial()
{
// The only way we know something was added to the DbSet from this partial class
// is to hook the CollectionChanged event.
_data.CollectionChanged += DataOnCollectionChanged;
InitializePartial2();
}
internal void InitializeWithContext(ObjectContext objectContext)
{
// Get entity set for entity. Used when we figure out whether to generate IDENTITY values
EntitySet = objectContext
.MetadataWorkspace
.GetItems<EntityContainer>(DataSpace.SSpace).First()
.BaseEntitySets
.FirstOrDefault(item => item.Name == typeof(TEntity).Name);
}
private void DataOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Add)
return;
foreach (TEntity entity in e.NewItems)
GenerateIdentityColumnValues(entity);
}
/// <summary> The purpose of this method, which is called after a row is added, is to ensure that Identity column values are
/// properly initialized. If this was a real Entity Framework, this task would be handled by the data provider
/// when SaveChanges[Async]() is called and the value(s) would then be propagated back into the entity.
/// In the case of FakeDbSet, there is nothing that will do that, so we have to make this at-least token effort
/// to ensure the columns are properly initialized, even if it is done at the incorrect time.
/// </summary>
private void GenerateIdentityColumnValues(TEntity entity)
{
foreach (var member in EntitySet.ElementType.Members)
{
if (!member.IsStoreGeneratedIdentity)
continue;
foreach (var metadataProperty in member.TypeUsage.EdmType.MetadataProperties)
{
if (metadataProperty.Name != "PrimitiveTypeKind")
continue;
var entityProperty = entity.GetType().GetProperty(member.Name);
var identityColumnGetter = entityProperty.GetGetMethod();
// Note that we'll get the current value of the column and,
// if it is nonzero, we'll leave it alone. We do this because
// the test data in our mock DbContext provides values for the
// Identity columns and many of those values are foreign keys
// in other entities (where we also provide test data). We don't
// want to disturb any existing relationships defined in the test data.
bool isDefaultForType;
var columnType = (PrimitiveTypeKind)metadataProperty.Value;
switch (columnType)
{
case PrimitiveTypeKind.SByte:
isDefaultForType = default(SByte) == (SByte)identityColumnGetter.Invoke(entity, null);
break;
case PrimitiveTypeKind.Int16:
isDefaultForType = default(Int16) == (Int16)identityColumnGetter.Invoke(entity, null);
break;
case PrimitiveTypeKind.Int32:
isDefaultForType = default(Int32) == (Int32)identityColumnGetter.Invoke(entity, null);
break;
case PrimitiveTypeKind.Int64:
isDefaultForType = default(Int64) == (Int64)identityColumnGetter.Invoke(entity, null);
break;
case PrimitiveTypeKind.Decimal:
isDefaultForType = default(Decimal) == (Decimal)identityColumnGetter.Invoke(entity, null);
break;
default:
// In SQL Server, an Identity column can be of one of the following data types:
// tinyint (SqlByte, byte), smallint (SqlInt16, Int16), int (SqlInt32, Int32),
// bigint (SqlInt64, Int64), decimal (with a scale of 0) (SqlDecimal, Decimal),
// or numeric (with a scale of 0) (SqlDecimal, Decimal). Those are handled above.
// 'If we don't know, we throw'
throw new InvalidOperationException($"Unsupported Identity Column Type {columnType}");
}
// From this point on, we can return from the method, as only one identity column is
// possible per table and we found it.
if (!isDefaultForType)
return;
var identityColumnSetter = entityProperty.GetSetMethod();
lock (Local)
{
switch (columnType)
{
case PrimitiveTypeKind.SByte:
{
SByte maxExistingColumnValue = 0;
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (SByte) identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] {(SByte) (++maxExistingColumnValue)});
return;
}
case PrimitiveTypeKind.Int16:
{
Int16 maxExistingColumnValue = 0;
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int16) identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] {(Int16) (++maxExistingColumnValue)});
return;
}
case PrimitiveTypeKind.Int32:
{
Int32 maxExistingColumnValue = 0;
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int32) identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] {(Int32) (++maxExistingColumnValue)});
return;
}
case PrimitiveTypeKind.Int64:
{
Int64 maxExistingColumnValue = 0;
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int64) identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] {(Int64) (++maxExistingColumnValue)});
return;
}
case PrimitiveTypeKind.Decimal:
{
Decimal maxExistingColumnValue = 0;
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Decimal) identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] {(Decimal) (++maxExistingColumnValue)});
return;
}
}
}
}
}
}
partial void InitializePartial2();
}

Updating a UserProfile and its Roles using Entity Framework

I'm trying to find a way that would allow me to update a UserProfile entity along with a list of Roles that the user is assigned to. I've written the code below, but it doesn't work.
public void UpdateUserProfile(UserProfile userProfile)
{
context.Entry(userProfile).State = EntityState.Added;
var databaseRoleIds = context.Roles
.Where(r => r.UserProfiles
.Any(u => u.UserId == userProfile.UserId))
.Select(r => r.RoleId).ToList();
var clientRoleIds = userProfile.Roles.Select(r => r.RoleId).ToList();
var removedRoleIds = databaseRoleIds.Except(clientRoleIds).ToList();
var addedRoleIds = clientRoleIds.Except(databaseRoleIds).ToList();
var unchangedRoleIds = removedRoleIds.Union(addedRoleIds).ToList();
foreach (var roleId in unchangedRoleIds)
{
var role = context.Roles.Find(roleId);
context.Entry(role).State = EntityState.Unchanged;
}
foreach (var roleId in removedRoleIds)
{
userProfile.RemoveRole(context.Roles.Find(roleId));
}
foreach (var roleId in addedRoleIds)
{
userProfile.AddRole(context.Roles.Find(roleId));
}
context.Entry(userProfile).State = EntityState.Modified;
}
Here is the unitOfWork
namespace MvcWebsite.WorkUnits
{
public class WorkUnit : IWorkUnit, IDisposable
{
private MvcContext context = new MvcContext();
private RoleRepository roleRepository;
private UserProfileRepository userProfileRepository;
public IRoleRepository RoleRepository
{
get
{
if (this.roleRepository == null)
{
roleRepository = new RoleRepository(context);
}
return roleRepository;
}
}
public IUserProfileRepository UserProfileRepository
{
get
{
if (this.userProfileRepository == null)
{
userProfileRepository = new UserProfileRepository(context);
}
return userProfileRepository;
}
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
... and here is the HttpPost Edit method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserProfileEditViewModel model)
{
try
{
if (ModelState.IsValid)
{
var clientUserProfile = new UserProfile();
clientUserProfile.UserId = model.UserId;
clientUserProfile.UserName = model.UserName;
clientUserProfile.FirstName = model.FirstName;
clientUserProfile.LastName = model.LastName;
clientUserProfile.Email = model.Email;
clientUserProfile.RowVersion = model.RowVersion;
clientUserProfile.Roles = new List<Role>();
foreach(var role in model.Roles)
{
if (role.Assigned)
{
clientUserProfile.Roles.Add(new Role
{
RoleId = role.RoleId,
RoleName = role.RoleName,
RowVersion = role.RowVersion,
});
}
}
unitOfWork.UserProfileRepository.UpdateUserProfile(clientUserProfile);
unitOfWork.Save();
return RedirectToAction("Details", new { id = clientUserProfile.UserId });
}
}
catch (DataException ex)
{
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
return View(model);
}
Does anyone have any idea why this isn't working? Or could suggest a tutorial somewhere that actually works. Any help, as always, is greatly appreciated.
Instead of creating a new UserProfile in your controller, get the UserProfile from the repository, modify its fields, then send it back to UpdateUserProfile and call Save.
Finally found that I had it totally wrong in the first place. I wasn't changing the relationships at all. I've included the code below, which allows me to attach the parent entity as modified and then mark the relationships as added and deleted as required
public void UpdateUserProfile(UserProfile userProfile)
{
context.Entry(userProfile).State = EntityState.Modified;
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
foreach (var role in userProfile.Roles)
{
context.Entry(role).State = EntityState.Unchanged;
}
var databaseRoleIds = context.Roles
.Where(r => r.UserProfiles
.Any(u => u.UserId == userProfile.UserId))
.Select(r => r.RoleId)
.ToList();
var clientRoleIds = userProfile.Roles.Select(r => r.RoleId).ToList();
var removedRoleIds = databaseRoleIds.Except(clientRoleIds).ToList();
var addedRoleIds = clientRoleIds.Except(databaseRoleIds).ToList();
foreach (var roleId in removedRoleIds)
{
var role = context.Roles.Find(roleId);
objectContext
.ObjectStateManager
.ChangeRelationshipState(userProfile, role, u => u.Roles, EntityState.Deleted);
}
foreach (var roleId in addedRoleIds)
{
var role = context.Roles.Find(roleId);
objectContext
.ObjectStateManager
.ChangeRelationshipState(userProfile, role, u => u.Roles, EntityState.Added);
}
}