Handling concurrency in CQRS approach - c#

I'm using the CQRS approach in my architecture, as an example, I have a command like:
public class ModifyDepartmentInformationCommand
{
public ModifyDepartmentInformationCommand() { }
public ModifyDepartmentInformationCommand(int departmentId, string departmentName, byte[] version)
{
DepartmentId = departmentId;
DepartmentName = departmentName;
Version = version;
}
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public byte[] Version { get; set; }
}
Its handler looks like:
public class ModifyDepartmentInformationCommandHandler :
IRequestHandler<ModifyDepartmentInformationCommand, ModifyDepartmentInformationCommandResult>
{
private readonly IMgpCommandContext _mgpCommandContext;
public ModifyDepartmentInformationCommandHandler(IMgpCommandContext mgpCommandContext)
{
_mgpCommandContext = mgpCommandContext;
}
public ModifyDepartmentInformationCommandResult Execute(ModifyDepartmentInformationCommand request)
{
new ModifyDepartmentInformationCommandValidator().ValidateAndThrow(request);
var department = _mgpCommandContext.Departments.SingleOrDefault(o => o.Id == request.DepartmentId);
if (department == null) { throw new ApplicationException("DepartmentDoesNotExist", "There is no such department."); }
department.ModifyInformation(request.Version, request.DepartmentName);
_mgpCommandContext.SaveChanges();
return new ModifyDepartmentInformationCommandResult();
}
}
As you see Version is used as the concurrency token. It is passed with the command, and in the command handler passed to the department's domain entity operation ModifyInformation, which sets the version:
public class Department
{
...
public void ModifyInformation(byte[] version, string departmentName)
{
Version = version;
Name = departmentName;
new DepartmentValidator().ValidateAndThrow(this);
}
}
Because the Version property on the Department domain entity is mapped using EF Code First as follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>().ToTable("Department").Property(p => p.Version).IsRowVersion().IsConcurrencyToken(); ;
}
...concurrency is detected correctly from the moment SaveChanges is executed on the context.
That's how it works now... the question I have is related to the fact that I have one concurrency token on department level. So, if I have commands that each modify different properties of a department, the same concurrency token is used.
As a result, if two users query department X, and user 1 uses a command to change the department name and user 2 uses another command to change some other property of department; then the second user that submits the command may still get a concurrency violation even if it's not the same property that was modified.
Is this the right approach, or is there a better way to handle concurrency in commands, like using multiple concurrency tokens for an entity? What approach is generally taken?

You choose right approach. Also, you can split the department configuration into several logical parts, for example: common parameters, security, etc. Each part would be considered in isolation and each part will have its own version.

Related

Why is Entity Framework Core attempting to insert records into one of the tables from many to many relationships and NOT the join table?

Given the following set up where there are many Teams and there are many LeagueSessions. Each Team belongs to zero or more LeagueSessions but only ever one LeagueSession is active. LeagueSessions have many teams, and the teams will be repeated. Many-to-many relationship is established between Teams and LeagueSessions with a join table called TeamsSessions.
Team model looks like this:
public class Team
{
public string Id { get; set; }
public string Name { get; set; }
public League League { get; set; }
public string LeagueID { get; set; }
public bool Selected { get; set; }
public ICollection<Match> Matches { get; set; }
public virtual ICollection<TeamSession> TeamsSessions { get; set; }
}
Team model fluent api configuration:
`
public class TeamConfiguration
{
public TeamConfiguration(EntityTypeBuilder<Team> model)
{
// The data for this model will be generated inside ThePLeagueDataCore.DataBaseInitializer.DatabaseBaseInitializer.cs class
// When generating data for models in here, you have to provide it with an ID, and it became mildly problematic to consistently get
// a unique ID for all the teams. In ThePLeagueDataCore.DataBaseInitializer.DatabaseBaseInitializer.cs we can use dbContext to generate
// unique ids for us for each team.
model.HasOne(team => team.League)
.WithMany(league => league.Teams)
.HasForeignKey(team => team.LeagueID);
}
}
`
Each team belongs to a single League. League model looks like this:
`public class League
{
public string Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public IEnumerable<Team> Teams { get; set; }
public bool Selected { get; set; }
public string SportTypeID { get; set; }
public SportType SportType { get; set; }
public IEnumerable<LeagueSessionSchedule> Sessions { get; set; }
}`
fluent API for the League:
`public LeagueConfiguration(EntityTypeBuilder<League> model)
{
model.HasOne(league => league.SportType)
.WithMany(sportType => sportType.Leagues)
.HasForeignKey(league => league.SportTypeID);
model.HasMany(league => league.Teams)
.WithOne(team => team.League)
.HasForeignKey(team => team.LeagueID);
model.HasData(leagues);
}`
SessionScheduleBase class looks like this:
public class SessionScheduleBase
{
public string LeagueID { get; set; }
public bool ByeWeeks { get; set; }
public long? NumberOfWeeks { get; set; }
public DateTime SessionStart { get; set; }
public DateTime SessionEnd { get; set; }
public ICollection<TeamSession> TeamsSessions { get; set; } = new Collection<TeamSession>();
public ICollection<GameDay> GamesDays { get; set; } = new Collection<GameDay>();
}
Note: LeagueSessionSchedule inherits from SessionScheduleBase
The TeamSession model looks like this:
`public class TeamSession
{
public string Id { get; set; }
public string TeamId { get; set; }
public Team Team { get; set; }
public string LeagueSessionScheduleId { get; set; }
public LeagueSessionSchedule LeagueSessionSchedule { get; set; }
}`
I then configure the relationship with the fluent API like this:
`public TeamSessionConfiguration(EntityTypeBuilder<TeamSession> model)
{
model.HasKey(ts => new { ts.TeamId, ts.LeagueSessionScheduleId });
model.HasOne(ts => ts.Team)
.WithMany(t => t.TeamsSessions)
.HasForeignKey(ts => ts.TeamId);
model.HasOne(ts => ts.LeagueSessionSchedule)
.WithMany(s => s.TeamsSessions)
.HasForeignKey(ts => ts.LeagueSessionScheduleId);
}`
The problem arises whenever I attempt to insert a new LeagueSessionSchedule. The way I am adding a new TeamSession object onto the new LeagueSessionSchedule is like this:
`foreach (TeamSessionViewModel teamSession in newSchedule.TeamsSessions)
{
Team team = await this._teamRepository.GetByIdAsync(teamSession.TeamId, ct);
if(team != null)
{
TeamSession newTeamSession = new TeamSession()
{
Team = team,
LeagueSessionSchedule = leagueSessionSchedule
};
leagueSessionSchedule.TeamsSessions.Add(newTeamSession);
}
}`
Saving the new LeagueSessionSchedule code:
public async Task<LeagueSessionSchedule> AddScheduleAsync(LeagueSessionSchedule newLeagueSessionSchedule, CancellationToken ct = default)
{
this._dbContext.LeagueSessions.Add(newLeagueSessionSchedule);
await this._dbContext.SaveChangesAsync(ct);
return newLeagueSessionSchedule;
}
Saving the new LeagueSessionSchedule object throws an error by Entity Framework Core that it cannot INSERT a duplicate primary key value into the dbo.Teams table. I have no idea why its attempting to add to dbo.Teams table and not into TeamsSessions table.
ERROR:
INSERT INTO [LeagueSessions] ([Id], [Active], [ByeWeeks], [LeagueID], [NumberOfWeeks], [SessionEnd], [SessionStart])
VALUES (#p0, #p1, #p2, #p3, #p4, #p5, #p6);
INSERT INTO [Teams] ([Id], [Discriminator], [LeagueID], [Name], [Selected])
VALUES (#p7, #p8, #p9, #p10, #p11),
(#p12, #p13, #p14, #p15, #p16),
(#p17, #p18, #p19, #p20, #p21),
(#p22, #p23, #p24, #p25, #p26),
(#p27, #p28, #p29, #p30, #p31),
(#p32, #p33, #p34, #p35, #p36),
(#p37, #p38, #p39, #p40, #p41),
(#p42, #p43, #p44, #p45, #p46);
System.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_Teams'. Cannot insert duplicate key in object 'dbo.Teams'. The duplicate key value is (217e2e11-0603-4239-aab5-9e2f1d3ebc2c).
My goal is to create a new LeagueSessionSchedule object. Along with the creation of this object, I also have to create a new TeamSession entry to the join table (or not if join table is not necessary) to then be able to pick any given team and see what session it is currently a part of.
My entire PublishSchedule method is the following:
`
public async Task<bool> PublishSessionsSchedulesAsync(List<LeagueSessionScheduleViewModel> newLeagueSessionsSchedules, CancellationToken ct = default(CancellationToken))
{
List<LeagueSessionSchedule> leagueSessionOperations = new List<LeagueSessionSchedule>();
foreach (LeagueSessionScheduleViewModel newSchedule in newLeagueSessionsSchedules)
{
LeagueSessionSchedule leagueSessionSchedule = new LeagueSessionSchedule()
{
Active = newSchedule.Active,
LeagueID = newSchedule.LeagueID,
ByeWeeks = newSchedule.ByeWeeks,
NumberOfWeeks = newSchedule.NumberOfWeeks,
SessionStart = newSchedule.SessionStart,
SessionEnd = newSchedule.SessionEnd
};
// leagueSessionSchedule = await this._sessionScheduleRepository.AddScheduleAsync(leagueSessionSchedule, ct);
// create game day entry for all configured game days
foreach (GameDayViewModel gameDay in newSchedule.GamesDays)
{
GameDay newGameDay = new GameDay()
{
GamesDay = gameDay.GamesDay
};
// leagueSessionSchedule.GamesDays.Add(newGameDay);
// create game time entry for every game day
foreach (GameTimeViewModel gameTime in gameDay.GamesTimes)
{
GameTime newGameTime = new GameTime()
{
GamesTime = DateTimeOffset.FromUnixTimeSeconds(gameTime.GamesTime).DateTime.ToLocalTime(),
// GameDayId = newGameDay.Id
};
// newGameTime = await this._sessionScheduleRepository.AddGameTimeAsync(newGameTime, ct);
newGameDay.GamesTimes.Add(newGameTime);
}
leagueSessionSchedule.GamesDays.Add(newGameDay);
}
// update teams sessions
foreach (TeamSessionViewModel teamSession in newSchedule.TeamsSessions)
{
// retrieve the team with the corresponding id
Team team = await this._teamRepository.GetByIdAsync(teamSession.TeamId, ct);
if(team != null)
{
TeamSession newTeamSession = new TeamSession()
{
Team = team,
LeagueSessionSchedule = leagueSessionSchedule
};
leagueSessionSchedule.TeamsSessions.Add(newTeamSession);
}
}
// update matches for this session
foreach (MatchViewModel match in newSchedule.Matches)
{
Match newMatch = new Match()
{
DateTime = match.DateTime,
HomeTeamId = match.HomeTeam.Id,
AwayTeamId = match.AwayTeam.Id,
LeagueID = match.LeagueID
};
leagueSessionSchedule.Matches.Add(newMatch);
}
try
{
leagueSessionOperations.Add(await this._sessionScheduleRepository.AddScheduleAsync(leagueSessionSchedule, ct));
}
catch(Exception ex)
{
}
}
// ensure all leagueSessionOperations did not return any null values
return leagueSessionOperations.All(op => op != null);
}
`
This is not a many-to-many relationship.
It is two separate one-to-many relationships, which happen to refer to the same table on one end of the relationship.
While it is true that on the database level, both use cases are represented by three tables, i.e. Foo 1->* FooBar *<-1 Bar, these two cases are treated differently by Entity Framework's automated behavior - and this is very important.
EF only handles the cross table for you if it is a direct many-to-many, e.g.
public class Foo
{
public virtual ICollection<Bar> Bars { get; set; }
}
public class Bar
{
public virtual ICollection<Foo> Foos { get; set; }
}
EF handles the cross table behind the scenes, and you are never made aware of the existence of the cross table (from the code perspective).
Importantly, EF Core does not yet support implicit cross tables! There is currently no way to do this in EF Core, but even if there were, you're not using it anyway, so the answer to your problem remains the same regardless of whether you're using EF or EF Core.
However, you have defined your own cross table. While this is still representative of a many-to-many relationship in database terms, it has ceased to be a many-to-many relationship as far as EF is concerned, and any documentation you find on EF's many-to-many relationships no longer applies to your scenario.
Unattached but indirectly added objects are assumed to be new.
By "indirectly added", I mean you that it was added to the context as part of another entity (which you directly added to the context). In the following example, foo is directly added and bar is indirectly added:
var foo = new Foo();
var bar = new Bar();
foo.Bar = bar;
context.Foos.Add(foo); // directly adding foo
// ... but not bar
context.SaveChanges();
When you add (and commit) a new entity to the context, EF adds it for you. However, EF also looks at any related entities that the first entity contains. During the commit in the above example, EF will look at both the foo and bar entities and will handle them accordingly. EF is smart enough to realize that you want bar to be stored in the database since you put it inside the foo object and you explicitly asked EF to add foo to the database.
It is important to realize that you've told EF that foo should be created (since you called Add(), which implies a new item), but you never told EF what it should do with bar. It's unclear (to EF) what you expect EF to do with this, and thus EF is left guessing at what to do.
If you never explained to EF whether bar already exists or not, Entity Framework defaults to assuming it needs to create this entity in the database.
Saving the new LeagueSessionSchedule object throws an error by Entity Framework Core that it cannot INSERT a duplicate primary key value into the dbo.Teams table. I have no idea why its attempting to add to dbo.Teams table
Knowing what you now know, the error becomes clearer. EF is trying to add this team (which was the bar object in my example) because it has no information on this team object and what its state in the database is.
There are a few solutions here.
1. Use the FK property instead of the navigational property
This is my preferred solution because it leaves no room for error. If the team ID does not yet exist, you get an error. At no point will EF try to create a team, since it doesn't even know the team's data, it only knows the (alleged) ID you're trying to create a relationship with.
Note: I am omitting LeagueSessionSchedule as it is unrelated to the current error - but it's essentially the same behavior for both Team and LeagueSessionSchedule.
TeamSession newTeamSession = new TeamSession()
{
TeamId = team.Id
};
By using the FK property instead of the nav prop, you are informing EF that this is an existing team - and therefore EF no longer tries to (re)create this team.
2. Ensure that the team is tracked by the current context
Note: I am omitting LeagueSessionSchedule as it is unrelated to the current error - but it's essentially the same behavior for both Team and LeagueSessionSchedule.
context.Teams.Attach(team);
TeamSession newTeamSession = new TeamSession()
{
Team = team
};
By attaching the object to the context, you are informing it of its existence. The default state of a newly attached entity is Unchanged, meaning "this already exists in the database and has not been changed - so you don't need to update it when we commit the context".
If you have actually made changes to your team that you want to be updated during commit, you should instead use:
context.Entry(team).State = EntityState.Modified;
Entry() inherently also attaches the entity, and by setting its state to Modified you ensure that the new values will be committed to the database when you call SaveChanges().
Note that I prefer solution 1 over solution 2 because it's foolproof and much less likely to lead to unexpected behavior or runtime exceptions.
String primary keys are undesirable
I'm not going to say that it doesn't work, but strings cannot be autogenerated by Entity Framework, making them undesirable as the type of your entity's PK. You will need to manually set your entity PK values.
Like I said, it's not impossible, but your code shows that you're not explicitly setting PK values:
if(team != null)
{
TeamSession newTeamSession = new TeamSession()
{
Team = team,
LeagueSessionSchedule = leagueSessionSchedule
};
leagueSessionSchedule.TeamsSessions.Add(newTeamSession);
}
If you want your PK's to be automatically generated, use an appropriate type. int and Guid are by far the most commonly used types for this.
Otherwise, you're going to have to start setting your own PK values, because if you don't (and the Id value thus defaults to null), your code is going to fail when you add a second TeamSession object using the above code (even though you're doing everything else correctly), since PK null is already taken by the first entity you added to the table.

Linq query deleting multiple rows instead of one. Very Simple Query

This should be really simple but I think I'm having possible issues with my model. I have been working with linq over a year and I should have this simple remove easily done. Please help! It's removing both records from the database when I only want one deleted
I have a database table with these properties.
Email, EmployeeName, StoreId
jsch#m.com,Joe Schneider,9
jsch#m.com,Joe Schneider,8
I need to delete Joe Schneider with storeId 9
So I run this simple query and remove process.
var temp2 = difference[i];
var PersonToRemove = db.Permissions.SingleOrDefault(s => s.EmployeeName == temp2 && s.StoreId == Persons.StoreId);
if (PersonToRemove.EmployeeName != null)
{
db.Permissions.Remove(PersonToRemove);
db.SaveChanges();
}
I am assuming one is going to say, hey your model is not right and don't put the name as a key, but I can't just be changing the model because other parts of the app are based on this model and would cause huge breaks. Could you give me advise how to edit the linq query to not delete both records?
model
[Table("Permissions")]
public class Permissions
{
[Key]
public String EmployeeName { get; set; }
public string Department { get; set; }
public int? StoreId { get; set; }
public String Email { get; set; }
}
[Table("Permissions")]
public class Permissions
{
[Key]
public String EmployeeName { get; set; }
}
The problem is here you are defining a primary key which has no length constraint on it. (MaxLength). This leads to EF generate a column with NVARCHAR(MAX). As mentioned here VARCHAR(MAX) columns are not allowed to be primary key. So correct definition should be like below
[Table("Permissions")]
public class Permissions
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] <--
[MaxLength(255)] // <---
public String EmployeeName { get; set; }
}
Edit: You need to recreate the database in order to associated tables initialized with correct settings.
Edit 2 : Also you may need a DatabaseGenerated(DatabaseGeneratedOption.None) since its not identity column.
you can set Deleted state on individual entity like so:
var temp2 = difference[i];
var PersonToRemove = db.Permissions.SingleOrDefault(s => s.EmployeeName == temp2 && s.StoreId == Persons.StoreId);
if (PersonToRemove.EmployeeName != null)
{
db.Entry(PersonToRemove).State = EntityState.Deleted; // do this instead
db.SaveChanges();
}
EF should then figure out which entity you wanted to delete
UPD
I am assuming you are using EF6 and DB-first approach. I am also assuming you've got your DB context class set up with default convention model builder. It seems EF's default object tracking based on Key will not work as your key is not unique (this is a bigger problem, but I understand you're already aware of that).
You might try circumvent that convention by adding custom model builder configuration like so:
class MyDbContext : DbContext {
public virtual DbSet<Permissions> Permissions {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Permissions>().HasKey(p => new { p.EmployeeName, p.StoreId});
}
}
since you didn't share your DbContext definition this is just a snippet but hopefully gives you some ideas to explore.
this is the API reference: https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.modelconfiguration.entitytypeconfiguration-1?view=entity-framework-6.2.0

Updating two related Objects using generic repository pattern and Entity Framework

I am using a generic repository and Entity Framework. I can update one of the classes normally, but I'm having trouble updating the relationship between them.
I'm also using lazy loading, AutoMapper and a service layer to isolate the domain.
public class DetalhesDoArquivoViewModel
{
public DetalhesDoArquivoViewModel()
{
Id = Guid.NewGuid();
}
[Key]
public Guid Id { get; set; }
public string FileName { get; set; }
public string Extension { get; set; }
public Guid FormularioId { get; set; }
public virtual FormularioDoUploadViewModel DescricaoDoUpload { get; set; }
}
public class FormularioDoUploadViewModel
{
public FormularioDoUploadViewModel()
{
Id = Guid.NewGuid();
}
[Key]
public Guid Id { get; set; }
[Required(ErrorMessage = "Digite um nome")]
[Display(Name = "Nome")]
[MaxLength(100)]
public string Nome { get; set; }
[Required(ErrorMessage = "Entre com uma descrição")]
[Display(Name = "Descrição")]
[MaxLength(500)]
public string Descricao { get; set; }
public virtual IEnumerable<DetalhesDoArquivoViewModel> DetalhesDoArquivo { get; set; }
}
My Update repository
public virtual TEntity Atualizar(TEntity obj)
{
var entry = Db.Entry(obj);
Dbset.Attach(obj);
entry.State = EntityState.Modified;
SaveChanges();
return obj;
}
My service class:
public class UploadAppServices : BaseService, IUploadServices
{
private readonly IFormularioUploadRepository _formularioUploadRepository;
private readonly IDetalhesDoArquivoRepository _detalhesDoArquivoRepository;
// Update
public FormularioDoUploadViewModel Atualizar(FormularioDoUploadViewModel formularioDoUploadViewModel)
{
var form = Mapper.Map<FormularioUpload>(formularioDoUploadViewModel);
_formularioUploadRepository.Atualizar(form);
Commit();
return formularioDoUploadViewModel;
}
//getById
public FormularioDoUploadViewModel ObterPorId(Guid id)
{
return Mapper.Map<FormularioDoUploadViewModel>(_formularioUploadRepository.ObterPorId(id));
}
}
My controller:
public class FormularioDoUploadController : BaseController
{
private ApplicationDbContext db = new ApplicationDbContext();
private IFormularioUploadRepository _formularioUploadRepository;
private IUploadServices _uploadServices;
public ActionResult Edit(Guid id)
{
var formularioDoUploadViewModel = _uploadServices.ObterPorId(id);
if (formularioDoUploadViewModel == null)
{
return HttpNotFound();
}
return View(formularioDoUploadViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FormularioDoUploadViewModel formularioDoUploadViewModel)
{
if (ModelState.IsValid)
{
for (int i = 0; i < Request.Files.Count; i++)
{
var file = Request.Files[i];
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
DetalhesDoArquivoViewModel detalhesDoArquivo = new DetalhesDoArquivoViewModel()
{
FileName = fileName,
Extension = Path.GetExtension(fileName),
FormularioId = formularioDoUploadViewModel.Id,
};
var path = Path.Combine(Server.MapPath("~/App_Data/Upload/"), detalhesDoArquivo.Id + detalhesDoArquivo.Extension);
file.SaveAs(path);
}
// Update
_uploadServices.Atualizar(formularioDoUploadViewModel);
return RedirectToAction("Index");
}
}
return View(formularioDoUploadViewModel);
}
Automapper is great for mapping entity to view-model, but I would avoid using it to map from a view-model to entity. This may seem convenient, but you are effectively unconditionally trusting the data received from the client and overwriting your database data. This means you have to send 100% of your entity domain model to the client, revealing more about your domain structure than you need to, and then accept that expanded domain model which can contain alterations that your client application does not intend to make. (intercepting the post to the server in the browser debugger and altering values in the object posted back to the server)
Submit actions should be coded to:
Validate that the current session user has permission to modify the record(s) identified by the submit request.
Limit the update to specific values provided in the request.
Validate those specific values.
Disconnect the user session and notify administrators if any of the above is violated.
In some cases, such as adding a new entity, the payload will effectively be a complete entity and potentially some related details. This still needs to be validated against the known data state. In other cases where you provide an action that updates an entity, the model posted back should merely contain the ID of the entity being updated, and the specific values the client is allowed to update. (not the entire, modified entity)
By passing entities, or view models that map directly to entities for a method intended to update some aspects of the entity, I can:
Re-assign that entity to someone else.
Use the request to attempt to assign another random entity to myself.
Negate or otherwise change any and all data recorded in that entity.
Do not trust anything received from the client.
This issue also presents a concurrent access issue where your system is adopting a "last in wins" scenario. Between the time you provided the entity/view model and the time you submit the view model back to the server, that entity data may have changed. By mapping the data into a new entity class, attaching, marking modified, and saving, you overwrite the data without any consideration as to whether the data was stale.
To avoid the issue you are seeing, and the security/stale issues, you should load the entity from the context on the Update post call, validate the authorization for the current user, check the row version # or timestamp to ensure the record isn't stale, validate your updated details, then, once you're absolutely sure that the data in your view model presents no risk to your entity, you can use automapper's .Map(source, detination) to copy the values across. If you need to update related entities against related view models, then as long as you .Include() those related entities when you retrieve the entity from the context, then the .Map() call should handle the related data.

C# Entity Framework (Code first), Implementing CRUD Operations on Model

I just wondering if you can store a function in a model (CRUD transactions)
that will look something like this:
My Existing code:
public class tbluser
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
[Required(ErrorMessage = "Username is required")]
public string username { get; set; }
[Required(ErrorMessage = "Password is required")]
public string password { get; set; }
public static List<tbluser> list()
{
using (var db = new sample())
{
var user = db.tbluser.ToList();
return user;
}
}
}
What i want:
public class tbluser:DbContext
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
[Required(ErrorMessage = "Username is required")]
public string username { get; set; }
[Required(ErrorMessage = "Password is required")]
public string password { get; set; }
public static List<tbluser> list()
{
return this.toList();
}
}
I just want to ask also if that method of implementing Entity Framework is ok.
Here is a quick example of how you might setup a simple Code First implementation to get started.
First, define your User model. The Key attribute on an integer type automatically configures the identity property for you. Then, you may want an index on username if you plan to do frequent lookups by username (to get user details or to validate a password).
public class User
{
[Key] // Becomes identity by default
public int Id { get; set; }
[Index("IX_User_Username", IsUnique = true)]
public string Username { get; set; }
public string Password { get; set; }
}
Then, you can define
public class AppDataContext : DbContext
{
public AppDataContext() : base("name=DBConnection") { }
public DbSet<User> Users { get; set; }
}
You will just need to be sure there is a connection string in your config file to match the name passed there.
<connectionStrings>
<add name="DBConnection" providerName="System.Data.SqlClient"
connectionString="Data Source=instancePath;Initial Catalog=dbName;Integrated Security=true;MultipleActiveResultSets=True" />
</connectionStrings>
This would now allow you to create repos such as this:
public class UserRepo : IDisposable
{
public Lazy<AppDataContext> _db = new Lazy<AppDataContext>(() => new AppDataContext());
public IQueryable<User> Get() => _db.Value.Users.AsQueryable();
public IList<User> GetAll() => _db.Value.Users.ToList();
public void Dispose()
{
if (_db.IsValueCreated)
_db.Value.Dispose();
}
}
So then you can either use the repo or the context directly.
// Use the repo
using (var userRepo = new UserRepo())
{
var allUsers = userRepo.GetAll();
var user = userRepo.Get().FirstOrDefault(m => m.Username == "myUsername");
}
// Or just use the data context
using (var db = new AppDataContext())
{
var allUsers = db.Users.ToList(); // Get all users
var user = db.Users.FirstOrDefault(m => m.Username == "myUsername");
}
For more information, here are some useful links with great details:
Simple Example
Data Annotations
Initializer Config
Migrations
Code like this is going to be heavily problematic.
In the first example you are tightly coupling an instance of a DbContext to an entity. Calling tblUser.list() will return a list of User entities, but these will now be outside of the scope of a DbContext. (Due to the using() block closure) This means that any lazy load calls to retrieve related entities will fail and you cannot persist any changes to the entities until they are re-attached to another DbContext. This gets very messy, very fast.
In the second example you would be extending a DbContext, meaning each "entity" is effectively scoping a DbContext use to populate instances of itself. You can't just "static" wrap the method because that wouldn't have visibility to the non-static DbSets inherited from DbContext.
This would be horrible in terms of performance, and from a code perspective would look plain weird:
I.e.
using (var user = new tbluser)
{
var users = user.list(); // not static.
// .. Do stuff..
}
To make it static would be problematic because a DbContext would need to be static-scoped inside tbluser
public class tbluser
{
private static MyContext _context = new MyContext();
// ...
public static List<tbluser> list()
{
return _context.tblusers.ToList();
}
}
And this may still have issues, such as how the static instance is disposed, before it was remotely functional but I certainly cannot recommend an approach like this.
Instead, use the DbContext as it is intended. Look at IoC containers like Unity or Autofac to manage the lifetime scope for for the DbContext and inject an instance as a dependency into classes that need it, or at a minimum wrap it in a using() {} block and treat it like an repository with it's DbSets.
There are lots of examples of using the DbContext effectively, using Repositories and Unit of Work patterns with dependency injection. Master these before attempting to spin up something unique. Future developers looking at your code will thank you. :)
There is one famous principle called "Separation of Concerns" that will get very angry if you do this. My advice is to keep the code simple, meaningful and loosely coupled.

ASP.NET MVC 4, EF5, Unique property in model - best practice?

ASP.NET MVC 4, EF5, Code First, SQL Server 2012 Express
What is best practice to enforce a unique value in a model? I have a places class that has a 'url' property that should be unique for every place.
public class Place
{
[ScaffoldColumn(false)]
public virtual int PlaceID { get; set; }
[DisplayName("Date Added")]
public virtual DateTime DateAdded { get; set; }
[Required(ErrorMessage = "Place Name is required")]
[StringLength(100)]
public virtual string Name { get; set; }
public virtual string URL { get; set; }
};
Why isn't there just a [Unique] data annotation you can place on it?
I have seen 1 or 2 discussions on this, but no talk of best practice. Using Code First can you somehow tell the database to set a unique constraint on the field in the database?
What is easiest way - and what is best practice?
As crazy as it might sound the best practice nowadays is to not use built-in validation and instead use FluentValidation. Then the code will be very easy to read and super-maintainable since validation will be managed on separate class meaning less spaghetti code.
Pseudo-example of what you are trying to achieve.
[Validator(typeof(PlaceValidator))]
class Place
{
public int Id { get; set; }
public DateTime DateAdded { get; set; }
public string Name { get; set; }
public string Url { get; set; }
}
public class PlaceValidator : AbstractValidator<Place>
{
public PlaceValidator()
{
RuleFor(x => x.Name).NotEmpty().WithMessage("Place Name is required").Length(0, 100);
RuleFor(x => x.Url).Must(BeUniqueUrl).WithMessage("Url already exists");
}
private bool BeUniqueUrl(string url)
{
return new DataContext().Places.FirstOrDefault(x => x.Url == url) == null
}
}
This link might help:
https://github.com/fatihBulbul/UniqueAttribute
[Table("TestModels")]
public class TestModel
{
[Key]
public int Id { get; set; }
[Display(Name = "Some", Description = "desc")]
[Unique(ErrorMessage = "This already exist !!")]
public string SomeThing { get; set; }
}
The only way is to update your migration once you generate it, assuming you are using them, so that it enforces a unique constraint on the column.
public override void Up() {
// create table
CreateTable("dbo.MyTable", ...;
Sql("ALTER TABLE MyTable ADD CONSTRAINT U_MyUniqueColumn UNIQUE(MyUniqueColumn)");
}
public override void Down() {
Sql("ALTER TABLE MyTable DROP CONSTRAINT U_MyUniqueColumn");
}
The hard bit, though, is enforcing the constraint at the code level before you get to the database. For that you might need a repository that contains the complete list of unique values and makes sure that new entities don't violate that through a factory method.
// Repository for illustration only
public class Repo {
SortedList<string, Entity1> uniqueKey1 = ...; // assuming a unique string column
public Entity1 NewEntity1(string keyValue) {
if (uniqueKey1.ContainsKey(keyValue) throw new ArgumentException ... ;
return new Entity1 { MyUniqueKeyValue = keyValue };
}
}
References:
Repository - Fowler (the original source of Repository)
Repostory - MSDN
Tutorial: Repository in MVC (www.asp.net)
Singleton in C# - SO
Footnote:
There are a lot of requests for [Unique] in code first, but it looks like it isn't even making version 6: http://entityframework.codeplex.com/wikipage?title=Roadmap
You could try voting for it here: http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-i-e-candidate-key-support
You may do this checking in the code level before saving the data to the Database tables.
You can try using the Remote data annotation on your viewmodel to do an asynchronous validation to make the UI more responsive.
public class CreatePlaceVM
{
[Required]
public string PlaceName { set;get;}
[Required]
[Remote("IsExist", "Place", ErrorMessage = "URL exist!")
public virtual string URL { get; set; }
}
Make sure you have an IsExists action method in your Placecontroller which accepts a URL paramtere and check it againist your table and return true or false.
This msdn link has a sample program to show how to implement Remote attribute to do instant validation.
Also, If you are using a Stored procedure (For some reason), you can do an EXISTS check there before the INSERT query.
I solved the general problem of enabling constructor injection in your Validation flow, integrating into the normal DataAnnotations mechanism without resorting to frameworks in this answer, enabling one to write:
class MyModel
{
...
[Required, StringLength(42)]
[ValidatorService(typeof(MyDiDependentValidator), ErrorMessage = "It's simply unacceptable")]
public string MyProperty { get; set; }
....
}
public class MyDiDependentValidator : Validator<MyModel>
{
readonly IUnitOfWork _iLoveWrappingStuff;
public MyDiDependentValidator(IUnitOfWork iLoveWrappingStuff)
{
_iLoveWrappingStuff = iLoveWrappingStuff;
}
protected override bool IsValid(MyModel instance, object value)
{
var attempted = (string)value;
return _iLoveWrappingStuff.SaysCanHazCheez(instance, attempted);
}
}
With some helper classes (look over there), you wire it up e.g. in ASP.NET MVC like so in the Global.asax :-
DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
typeof(ValidatorServiceAttribute),
(metadata, context, attribute) =>
new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));
Faced similar issue in my ASP.NET Razor Page Project. Creating custom UniqueDataAttribute didn't work, because on Edit, it would throw an error if you're not changing unique field.
I needed unique Book Name. This is how I resolved:
I added unique constraint to the field in database via EF Core migrations. Added following in ApplicationDbContext class and then ran migration.
Code:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Book>()
.HasIndex(u => u.Name)
.IsUnique();
}
Next, created helper/extension method as follows.
Code:
// Validate uniqueness of Name field in database.
// If validation is done on existing record, pass the id of the record.
// Else, if validating new record Name, then id is set to dummy key integer -1
public static bool UniqueNameInDb(this string data, ApplicationDbContext db, int id = -1)
{
var duplicateData = from o in db.Book
where o.Name == data && o.Id != id
select o;
if(duplicateData.Any())
{
return false;
}
return true;
}
}
Then used it in Create and Edit page model in OnPost() method as follows.
Create model:
public async Task<IActionResult> OnPost()
{
if(ModelState.IsValid)
{
if (!Book.Name.UniqueNameInDb(_db)) //<--Uniqueness validation
{
ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
return Page();
}
await _db.Book.AddAsync(Book);
await _db.SaveChangesAsync();
return RedirectToPage("Index");
}
else
{
return Page();
}
}
Edit Model:
public async Task<IActionResult> OnPost()
{
if(ModelState.IsValid)
{
var bookFromDb = await _db.Book.FindAsync(Book.Id);
if (!Book.Name.UniqueNameInDb(_db, Book.Id)) //<--Uniqueness validation
{
ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
return Page();
}
bookFromDb.Name = Book.Name;
bookFromDb.Author = Book.Author;
await _db.SaveChangesAsync();
return RedirectToPage("Index");
}
return Page();
}
PS: Your Razor view should've Model validation set on in the form to capture and display the error.
i.e,
<div class="text-danger" asp-validation-summary="ModelOnly"></div>
and below validation against the field.
<span asp-validation-for="Book.Name" class="text-danger"></span>
well it's simple but idk if this is efficient or not. Just check before adding a new user whether the email already exists or not.
if (!db.Users.Any(x => x.Email == data.Email))
// your code for adding
else
// send a viewbag to the view
// ViewBag.error = "Email Already Exist";
To achieve Unique for multiple Columns
modelBuilder.Entity<DataClass>()
.HasIndex(u => new
{
u.col1,
u.col2
})
.IsUnique();

Categories

Resources