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.
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.
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.
Controller looks like
public class NodesRestController : ODataController
{
private INodeService _nodeService;
public NodesRestController(INodeService nodeService)
{
_nodeService = nodeService;
}
[EnableQuery()]
public IQueryable<Node> Get()
{
return _nodeService.GetAllNodes();
}
[EnableQuery()]
public Node Get(string id)
{
return _nodeService.GetNodeById(id);
}
}
in MongoDb repository i am returning AsQueryable of the collection.
//..............Rest of initializations
_collection = _dbContext.Database
.GetCollection<TEntity>(typeof(TEntity).Name);
//..........
public IQueryable<TEntity> GetAll()
{
return _collection.AsQueryable();
}
public TEntity Insert(TEntity entity)
{
entity.Id = ObjectId.GenerateNewId().ToString();
_collection.Insert(entity);
return entity;
}
//..............Rest of initializations
MongoDB Document looks like
{
"_id" : "5688d5b1d5ae371c60ffd8ef",
"Name" : "RTR1",
"IP" : "1.2.2.22",
"NodeGroup" : {
"_id" : "5688d5aad5ae371c60ffd8ee",
"Name" : "Group One",
"Username" : null,
"Password" : null
}}
Id were generated using ObjectId.GenerateNewId().ToString() so they are stored as string.
Node and NodeGroup are pure POCOs
public partial class NodeGroup : EntityBase
{
public string Name { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string LoginPrompt { get; set; }
public string PasswordPrompt { get; set; }
public string ReadyPrompt { get; set; }
public string Description { get; set; }
}
public partial class Node : EntityBase
{
public string Name { get; set; }
public string IP { get; set; }
public virtual NodeGroup NodeGroup { get; set; }
}
public abstract class EntityBase
{
//[JsonIgnore]
// [BsonRepresentation(BsonType.ObjectId)]
// [BsonId]
public string Id { get; set; }
}
Problem
oData URIs like
http://localhost:9910/api/NodesRest
http://localhost:9910/api/NodesRest?$expand=NodeGroup
http://localhost:9910/api/NodesRest?$expand=NodeGroup&$filter=Name eq 'RTR1'
works fine.
But when i try to filter on Navigation Property
http://localhost:9910/api/NodesRest?$expand=NodeGroup&$filter=NodeGroup/Name eq 'Group One'
it gives me exception
message: "Unable to determine the serialization information for the expression: ConditionalExpression.",
I used _collection.FindAll(); and it worked.
It worked for me when i used in memory collection without using mongoDB. As well.
It's somethings wrong with the AsQueryable method in c# driver of mongodb.
I have following abstract class:
public abstract class ClauseComponent
{
public int ClauseComponentId { get; set; }
public abstract string[] Determinate(ClimateChart chart);
public abstract List<ClauseComponent> GiveCorrectPath(ClimateChart chart);
public abstract String GetHtmlCode(Boolean isYes);
public virtual void Add(Boolean soort, ClauseComponent component)
{
throw new ApplicationException();
}
public ClauseComponent()
{
}
}
The Clause class inherits from the abstract class:
public class Clause : ClauseComponent
{
public virtual ClauseComponent YesClause { get; set; }
public virtual ClauseComponent NoClause { get; set; }
public String Name { get; private set; }
public virtual Parameter Par1 { get; set; }
public virtual Parameter Par2 { get; set; }
public int Waarde { get; set; }
public String Operator { get; set; }
public Clause()
{
}
public Clause(String name, Parameter par1, String op, int waarde)
{
this.Name = name;
this.Par1 = par1;
this.Operator = op;
this.Waarde = waarde;
}
public Clause(String name, Parameter par1, Parameter par2)
{
this.Name = name;
this.Par1 = par1;
this.Par2 = par2;
}
}
This is the mapper of the abstract class (I dont have a mapper for the subclass):
public ClauseComponentsMapper()
{
ToTable("ClauseComponents");
// Primary key
HasKey(c => c.ClauseComponentId);
// Properties
Property(c => c.ClauseComponentId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
I have this in my DB:
Now I want to give a proper name to the mapping, how can I accomplish this?
I have never done the mapping on abstract classes and subclasses so I'm a little bit in the blue here.
One way is to create properties for the mapping columns, and in the mapping class, map the virtual property using the mapping column property.
E.g.
public class Clause : ClauseComponent
{
public int MyCustomPar1Id{ get; set; }
[ForeignKey("MyCustomPar1Id")]
public virtual Parameter Par1 { get; set; }
}
Or Fluent Api:
modelBuilder.Entity<Clause >().HasRequired(p => p.Par1 ) // Or Optional
.HasForeignKey(p => p.MyCustomPar1Id);
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");