I'm following this example :
Entities
[Table("Authors")]
public class Author {
[Key]
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
[Table("Books")]
public class Book {
[Key]
public int ID { get; set; }
public string Title { get; set; }
public int Author_ID { get; set; }
[ForeignKey("Author_ID")]
public virtual Author Author { get; set; }
}
DbContext
public class MyDbContext : DbContext
{
public virtual DbSet<Author> Authors { get; set; }
public virtual DbSet<Book> Books { get; set; }
public MyDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}
Generic repository
public interface IRepository<T> where T : class
{
IQueryable<T> Entities { get; }
void Remove(T entity);
void Add(T entity);
}
public class GenericRepository<T> : IRepository<T> where T : class
{
private readonly MyDbContext _dbContext;
private IDbSet<T> _dbSet => _dbContext.Set<T>();
public IQueryable<T> Entities => _dbSet;
public GenericRepository(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public void Remove(T entity)
{
_dbSet.Remove(entity);
}
public void Add(T entity)
{
_dbSet.Add(entity);
}
}
UnitOfWork
public interface IUnitOfWork
{
IRepository<Author> AuthorRepository { get; }
IRepository<Book> BookRepository { get; }
/// <summary>
/// Commits all changes
/// </summary>
void Commit();
/// <summary>
/// Discards all changes that has not been commited
/// </summary>
void RejectChanges();
void Dispose();
}
public class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _dbContext;
#region Repositories
public IRepository<Author> AuthorRepository =>
new GenericRepository<Author>(_dbContext);
public IRepository<Book> BookRepository =>
new GenericRepository<Book>(_dbContext);
#endregion
public UnitOfWork(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public void Commit()
{
_dbContext.SaveChanges();
}
public void Dispose()
{
_dbContext.Dispose();
}
public void RejectChanges()
{
foreach (var entry in _dbContext.ChangeTracker.Entries()
.Where(e => e.State != EntityState.Unchanged))
{
switch (entry.State)
{
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Modified:
case EntityState.Deleted:
entry.Reload();
break;
}
}
}
}
Question
What if I need a custom Add function?
If I add a Code property in my Book class :
[Table("Books")]
public class Book {
[Key]
public int ID { get; set; }
public string Title { get; set; }
public int Author_ID { get; set; }
public string Code { get; set; } //I'm adding a Code property here
[ForeignKey("Author_ID")]
public virtual Author Author { get; set; }
}
and I want to autofill the Code property before insert the Book object in DB.
I guess I need to create a "custom" BookRepository which inherit the GenericRepository and override the Add function in order to have something like that :
public void Add(Book entity)
{
entity.Code = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds+entity.Title.Replace(" ","");
_dbSet.Add(entity);
}
I'm not familiar with design pattern / inheritance / interface concepts. Is it possible to do something like that?
The logic that you are trying to add belongs the Model and not the Repository, repository has nothing to do with it. Repositories responsible is to access database and run queries against it.
I have written an article how you plan Thin repository and unit of work using EF
I'll insert the link below
https://www.codeproject.com/Articles/1157241/Very-Thin-Database-Layer-using-UnitOfWork-Pattern
Hope this helps
Related
I am using a GenericRepository pattern when accessing database using EF. I've been using this pattern for years now but it's the first time I have had this issue.
Everywhere in my program, I am able to access the expressions of other repositories implementing the generic one. However, in a particular service I am unable to access the Expression. Here is the code:
GenericRepository.cs
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
private readonly DbContext _context;
public DbSet<TEntity> DbSet() => _context.Set<TEntity>();
public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> func, CancellationToken cancellationToken) =>
await DbSet().FirstOrDefaultAsync(func, cancellationToken).ConfigureAwait(false);
}
IGenericRepository
public interface IGenericRepository<TEntity> where TEntity : class
{
Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> func, CancellationToken cancellationToken);
}
AccountRepository (Where it works)
public class AccountRepository : GenericRepository<Account>, IAccountRepository
{
public AccountRepository(DiscountedDbContext context) : base(context)
{ }
}
public interface IAccountRepository : IGenericRepository<Account>
{
}
...
public AccountManagementService(IAccountRepository accountRepository)
{
Account account = await accountRepository.FirstOrDefault(x => x.id == 1);
}
This is how it should look, I.E. showing properties:
And the implementation for the LocationRepository that does not work
public class LocationRecordRepository : GenericRepository<LocationRecord>, ILocationRecordRepository
{
public LocationRecordRepository(DiscountedDbContext context) : base(context)
{
}
}
public interface ILocationRecordRepository : IGenericRepository<LocationRecord>
{
}
...
public GeoLocationService(ILocationRepository locationRepository)
{
// ERROR IS HERE: Properties does not show up for the location repository
LocationRecord current = await locationRepository.FirstOrDefaultAsync(x => x.);
}
This is all that appears when trying to access the model through expressions:
Here are the models:
public class Account
{
public int Id { get; set; }
public string? Email { get; set; }
public string? Name { get; set; }
public string? Surname { get; set; }
public string? Password { get; set; }
public Guid StoreCode { get; set; }
}
public class LocationRecord
{
public int Id { get; set; }
public DateTime? DateRecorded { get; set; }
public string Country { get; set; } = null!;
public string District { get; set; } = null!;
public string Town { get; set; } = null!;
public string PostCode { get; set; } = null!;
public int Count { get; set; } = 0;
}
The problem was simple. I had a Migration called LocationRepository, and it referenced the wrong one.
I have an entity called CarOwner. Every car owner has a property ServiceAppUser AutoService. When i add a new CarOwner i use Create method in CarOwnerService
In the SSMS every thing looks fine.
But when i hit the Details method and try to get a CarOwner by GetById, in the CarOwnerService I found a CarOwner with AutoService == null, and a collection of Cars == null
public class CarOwner : BaseModel<int>
{
public CarOwner()
{
this.Cars = new HashSet<Car>();
}
public string Name { get; set; }
public string Bulstat { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string MRP { get; set; }
public string Email { get; set; }
public string Telephone { get; set; }
public virtual ICollection<Car> Cars { get; set; }
public decimal Obligation { get; set; }
public string ServiceAppUserId { get; set; }
public virtual ServiceAppUser AutoService { get; set; }
}
public class CarOwnerService : ICarOwnerService
{
private IRepository<CarOwner> carOwnerRepository;
private IMapper mapper;
public CarOwnerService(IRepository<CarOwner> carOwnerRepository, IMapper mapper)
{
this.carOwnerRepository = carOwnerRepository;
this.mapper = mapper;
}
public async Task<int> Create(CarOwnerCreateViewModel model, ServiceAppUser user)
{
var carOwner = mapper.Map<CarOwner>(model);
carOwner.AutoService = user;
await this.carOwnerRepository.AddAsync(carOwner);
await this.carOwnerRepository.SaveChangesAsync();
var id = carOwner.Id;
return id;
}
public CarOwner GetById(int id)
{
var allCarOwners = this.carOwnerRepository.All();
var carOwner = allCarOwners.FirstOrDefault(o => o.Id == id);
return carOwner;
}
public IEnumerable<CarOwnerViewModel> GetAll()
{
var all = this.carOwnerRepository.All().ToList().Select(co => mapper.Map<CarOwnerViewModel>(co));
return all;
}
}
I'm not sure, but it sounds like you might be using Entity Framework Core or another ORM, because you mentioned SSMS.
Without seeing your IRepository implementation it is difficult to be certain, but generally the problem you describe is because you didn't tell the ORM to include the other parts of your object graph.
For example
dbContext.CarOwners
.Include(c => c.Cars)
.Include(c => c.AutoService)
.Select(c => c);
Would return all of your objects, with Cars and AutoService filled in.
WARNING Be careful to only Include exactly what you need for any call, or you will end up loading way too much data and slowing down your application.
this is my IRepository, and yes i am using EF Core
public interface IRepository<TEntity>
where TEntity : class
{
IQueryable<TEntity> All();
bool Contains(TEntity entity);
Task AddAsync(TEntity entity);
void Delete(TEntity entity);
Task<int> SaveChangesAsync();
}
And this is my implementaion of IRepository:
public class DbRepository<TEntity> : IRepository<TEntity>, IDisposable
where TEntity : class
{
private readonly ServiceAppContext context;
private DbSet<TEntity> dbSet;
public DbRepository(ServiceAppContext context)
{
this.context = context;
this.dbSet = this.context.Set<TEntity>();
}
public Task AddAsync(TEntity entity)
{
return this.dbSet.AddAsync(entity);
}
public IQueryable<TEntity> All()
{
return this.dbSet;
}
public void Delete(TEntity entity)
{
this.dbSet.Remove(entity);
}
public Task<int> SaveChangesAsync()
{
return this.context.SaveChangesAsync();
}
public void Dispose()
{
this.context.Dispose();
}
public bool Contains(TEntity entity)
{
return this.dbSet.Contains(entity);
}
}
Below is the my code. I want to get the value of Test.Details & Test.Events
public partial class Test : BaseTypes.ValidationEntityBase
{
public bool Active { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Test()
{
Events = new HashSet<Event>();
}
[Key]
[StringLength(20)]
public string ID { get; set; }
[Required()]
[StringLength(50)]
public string Details { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
[XLTargetName(null)]
public virtual ICollection<Event> Events { get; set; }
public override void AddToModel(System.Data.Entity.DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Test>().HasMany(e => e.Events).WithRequired(e => e.Test).WillCascadeOnDelete(false);
}
}
public partial class Events : BaseTypes.ValidationEntityBase
{
public Events()
{
Active = true; //DEFAULT TO ACTIVE
}
[Key()]
public string EventID { get; set; }
[Required()]
[StringLength(20)]
public string ID { get; set; }
[Required()]
[StringLength(50)]
public string EventDetails { get; set; }
[XLTargetName(null)]
[JsonIgnore]
[ScriptIgnore]
public virtual Test Test { get; set; }
public override void AddToModel(System.Data.Entity.DbModelBuilder modelBuilder)
{
}
}
public abstract class ValidationEntityBase : IValidationEntity
{
public ValidationEntityBase()
{
Valid = true;
}
public virtual void Run(CtxTest context, IValidationEntity entity)
{
}
}
public interface IValidationEntity
{
void Run(CtxTest context, IValidationEntity entity);
}
Here is my Business Object
public void RunRules(string typeCode)
{
var inputRules = Rule.FindRules(this.GetContext(), typeCode);
foreach (var rule in inputRules)
{
rule.Run<Test>(this.GetContext(), this.Test, "Sample", typeCode);
}
}
My Rule Class:
public void Run<T>(CtxTest context, T entity, string sample, string typeCode) where T : class, IValidationEntity
{
var validation = this.GetExecutionType();
var execution = (IValidationEntity)Activator.CreateInstance(validation, sample);
execution.Run(context, entity);
}
Whenever running rule then It will come to the below class and I'm getting all the basetypes(Test class) value
public class Person : ValidationEntityBase
{
public Person(string msgTypeCode)
{
MESSAGE_TYPECODE = msgTypeCode;
}
public override void Run(Context.CtxTest context, IValidationEntity entity)
{
}
}
How to print the value of Test.Details & Test.Events from IValidationEntity entity in run method, please help
This seems to be the simplest answer:
public interface IValidationEntity
{
void Run(CtxTest context, IValidationEntity entity);
string Details { get; }
}
It will require implementing explicitly to allow your classes to have different implementation names (i.e. string EventDetails) while still conforming to the IValidationEntity interface.
I have 4 layer in my application UI,DomainClass,Model(DBCntext),Repository.
In repository i have an abstract class like this :
public abstract class GenericRepository<C, T> :
IGenericRepository<T>
where T : class
where C : DbContext, new()
{
private C _entities = new C();
public C Context
{
get { return _entities; }
set { _entities = value; }
}
public virtual IQueryable<T> GetAll()
{
IQueryable<T> query = _entities.Set<T>();
return query;
}
public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = _entities.Set<T>().Where(predicate);
return query;
}
public virtual void Add(T entity)
{
_entities.Set<T>().Add(entity);
}
public virtual void Delete(T entity)
{
_entities.Set<T>().Remove(entity);
}
public virtual void Edit(T entity)
{
_entities.Entry(entity).State = System.Data.Entity.EntityState.Modified;
}
public virtual void Save()
{
_entities.SaveChanges();
}
}
All my entities inheritance from this class like this :
namespace Repository
{
public class StationRepository : GenericRepository<ShirazRailWay.ShirazRailwayEntities, DomainClass.Station>
{
}
}
I UI i called this repositories. as you can see here :
stationrepository objnew=new stationrepository();
obnew.getall();
In UI layer i have an connection string in app.config as you can see here :
<connectionStrings>
<add name="ShirazRailwayEntities" connectionString="metadata=res://*/RailWay.csdl|res://*/RailWay.ssdl|res://*/RailWay.msl;provider=System.Data.SqlClient;provider connection string="data source=****;initial catalog=DB-Metro;user id=sa;password=****;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
But i want to give an option to my users that with this option they can set their connection string by themselves.So i created a form in UI layer that when the users trying to log in it asks them the connection string .My problem is How can pass this connection string to my dbcontext?
In my model layer(dbcontext) i have this :
public partial class ShirazRailwayEntities : DbContext
{
public ShirazRailwayEntities()
: base("name=ShirazRailwayEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Advertisement> Advertisements { get; set; }
public DbSet<Line> Lines { get; set; }
public DbSet<Log> Logs { get; set; }
public DbSet<Path> Paths { get; set; }
public DbSet<Sensor> Sensors { get; set; }
public DbSet<Station> Stations { get; set; }
public DbSet<Train> Trains { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<TimeTable> TimeTables { get; set; }
public DbSet<ConfigFont> ConfigFonts { get; set; }
public DbSet<ArrivalTime> ArrivalTimes { get; set; }
public DbSet<ConfigColor> ConfigColors { get; set; }
}
Add another constructor that takes your connection string:
public partial class ShirazRailwayEntities : DbContext
{
public ShirazRailwayEntities()
: base(name: "ShirazRailwayEntities")
{
}
public ShirazRailwayEntities(string connectionName)
: base(name: connectionName)
{
}
}
var context = new ShirazRailwayEntities("whatever connection name you want");
I'm using Table per Hierarchy (TPH).
For example we have a base class for all entities:
public abstract class Entity
{
public virtual int Id { get; set; }
public virtual bool IsTransient()
{
return Id == default(int);
}
}
And base class for several entitites:
public abstract class Event:Entity
{
[MaxLength(50)]
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[Required]
[MaxLength(100)]
public string ShortDescription { get; set; }
[Required]
public DateTime PublishDate { get; set; }
public int Duration { get; set; }
}
public class Film:Event
{
public string Director { get; set; }
public string ActorList { get; set; }
public override string ToString()
{
return Name;
}
}
public class Concert:Event
{
public string Genre { get; set; }
public override string ToString()
{
return Name;
}
}
My context:
public class MyContext:DbContext
{
public MyContext():base(ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString)
{
}
public DbSet<Event> Events { get; set; }
public virtual void Commit()
{
base.SaveChanges();
}
}
This is base repository:
public class GenericRepository : IRepository
{
//...
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().AsEnumerable();
}
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
var entityName = GetEntityName<TEntity>();
return ((IObjectContextAdapter)DbContext).ObjectContext.CreateQuery<TEntity>(entityName);
}
private string GetEntityName<TEntity>() where TEntity : class
{
string entitySetName = ((IObjectContextAdapter)DbContext).ObjectContext
.MetadataWorkspace
.GetEntityContainer(((IObjectContextAdapter)DbContext).ObjectContext.DefaultContainerName, DataSpace.CSpace)
.BaseEntitySets.First(bes => bes.ElementType.Name == typeof(TEntity).Name).Name;
return string.Format("{0}.{1}", ((IObjectContextAdapter)DbContext).ObjectContext.DefaultContainerName, entitySetName);
}
}
Next, create context and repository:
var context = new MyContext();
EventRepository repository = new EventRepository(context);
var films = repository.GetAll<Film>();
But I get exception (in the GetEntityName method): the sequence does not have elements.
I think it because there are no Film table in the DB. How to solve this problem?
I don't see the need of GetEntityName in the repository you are showing. For GetQuery you can use the DbContext API directly and don't need to access the underlying ObjectContext or MetadataWorkspace:
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
return DbContext.Set<TEntity>();
}
This returns a DbSet<TEntity> (which is an IQueryable<TEntity>). I am not 100% sure if that also works if TEntity is derived but the MSDN documentation about DbSet<TEntity> says: "The type can be derived type as well as base type." So, I would hope that the Set<TEntity>() method is allowed for derived types as well.