TIA
Full Disclosure - I am new to ASP.NET web development and I am still learning some of the framework and it's interactions. I will try to guard my statements here and identify ones that I am not sure I am using correctly.
I am working with an open source package that I downloaded called BeYourMarket where I have a payment controller that has a service. I am not sure exactly what this means, but I believe it has something to do with Unity? This service seems to control order related items on the site. It is connected to a table in the database that is being used in the entire website. I believe that Entity Framework is providing the conduit for this.
Here is code snippets of what I am trying to duplicate instead of it managing Orders this duplication will manage Token Orders. The ellipse's are my way of showing that there is code in between.
I suspect there is something I fundamentally do not understand here.
Below is the controller which you can see I duplicated the OrderService
PaymentController.cs
{
[Authorize]
public class PaymentController : Controller
{
...
private readonly IOrderService _orderService;
private readonly IOrderServiceToken _orderServiceToken;
...
}
public PaymentController(
...
IOrderService orderService,
IOrderServiceToken orderServiceToken,)
{
...
_orderService = orderService;
_orderServiceToken = orderServiceToken;
...
}
}
Below are the files/classes I created a duplicate from
BeYourMarket.Service\OrderService.cs
public interface IOrderService : IService<Order>
{
}
public class OrderService : Service<Order>, IOrderService
{
public OrderService(IRepositoryAsync<Order> repository)
: base(repository)
{
}
}
BeYourMarket.Models\Models\Order.cs
{
public partial class Order : Repository.Pattern.Ef6.Entity
{
public Order()
{
this.ListingReviews = new List<ListingReview>();
}
...
public int DataBaseVariable { get; set; }
...
}
}
BeYourMarket.Model\Models\Mapping\OrderMap.cs
public class OrderMap : EntityTypeConfiguration<Order>
{
public OrderMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Properties
...
this.Property(t => t.PaymentPlugin)
.HasMaxLength(250);
// Table & Column Mappings
this.ToTable("Orders");
...
this.Property(t => t.ID).HasColumnName("ID");
...
}
}
}
It might be worth noting that below my DbSet never gets ran. Perhaps I need to do some sort of another Initial setup that was ran when I first launched this package. I have no understanding how to run this 'setup' again.
BeYourMarket.Model\Models\BeYourMarketContext.cs
public BeYourMarket()
: base("Name=DefaultConnection")
{
}
...
public DbSet<Order> Orders { get; set; }
public DbSet<OrderToken> OrderToken { get; set; }
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new OrderMap());
modelBuilder.Configurations.Add(new OrderTokenMap());
}
BeYourMarket.Service\DataCacheService.cs
public class DataCacheService
{
...
private IOrderService OrderService
{
get { return _container.Resolve<IOrderService>(); }
}
private IOrderServiceToken OrderServiceToken
{
get { return _container.Resolve<IOrderServiceToken>(); }
}
...
}
Attached here is an image of the error I get. If I replace the <OrderToken> class in my OrderSErviceToken.cs with any other already created database for instance the <Order> everything works fine. It has something to do with me manually creating all this and missing some link or registration.
enter image description here
Thanks for reading if you were able to stick through it this long.
Chris
The problem was that I overlooked a line I needed to duplicate inside the following file:
-Note the line that I forgot is the line with asterisks.
UnityConfig.cs
.RegisterType<IRepositoryAsync<Order>, Repository<Order>>()
**.RegisterType<IRepositoryAsync<OrderToken>, Repository<OrderToken>>()**
...
.RegisterType<IOrderService, OrderService>()
.RegisterType<IOrderServiceToken, OrderServiceToken>()
Related
I am trying to move one of our projects from efcore5 to efcore6. We have a type that's derived from Dictionary as follows.
public class LinkRelations : Dictionary<string, LinkRelation>
where LinkRelation is simply;
public class LinkRelation
{
public string Href { get; set; }
}
Our ef entity Notification has LinkRelations as;
public class Notification
{
public LinkRelations Links { get; private set; }
// other stuff
}
We configure the Notification entity for EF as follows;
public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
{
public void Configure(EntityTypeBuilder<Notification> builder)
{
builder.Property(n => n.Links).HasConversion(new JsonValueConverter<LinkRelations>());
}
}
public class JsonValueConverter<T> : ValueConverter<T, string>
{
public JsonValueConverter() : base(
v => JsonConvert.SerializeObject(v),
s => JsonConvert.DeserializeObject<T>(s))
{
}
}
When I try to create a new migration (to test), I get the error: System.InvalidOperationException: 'Links' cannot be used as a property on entity type 'Notification' because it is configured as a navigation. at the NotificationConfiguration.Configure call above.
However, we are actually storing Links as a json string. Since, LinkRelations is derived from Dictionary, I believe EFCore6 is concluding that it must be a navigation property and then falling over.
So, my question is, how can I configure ef-core-6 to not make my property a navigation property? Or any other suggestion to avoid this error?
PS: This is an existing system in prod, and I cannot really modify the current design / schema.
I have a base model:
public abstract class Status
{
public string updateUserName { get; set; }
}
Then a model which extends the base model defined above:
public class Item : Status
{
public int Id { get; set; }
public string Description { get; set; }
}
Then I have defined configuration classes for each:
public class ItemConfiguration : IEntityTypeConfiguration<Item>
{
public void Configure(EntityTypeBuilder<Item> builder)
{
builder.ToTable("Item", "dbo").HasKey(c => c.Id);
builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
}
}
public class StatusConfiguration : IEntityTypeConfiguration<Status>
{
public void Configure(EntityTypeBuilder<Status> builder)
{
builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
}
Now, I have the following Context class:
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}
public DbSet<Item> Item { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ItemConfiguration());
}
}
I'm trying to figure out how to apply the Status model configurations defined in the StatusConfiguration class to all the models that extend to it (only one in this example: Item). I would like to avoid defining the same Status model configuration every time it gets used. The Status model will essentially be meta data associated with each Item record (i.e. one Item table in database containing all properties defined in both models; nothing more, and nothing less).
For example, my current implementation is the following ItemConfiguration class without using the StatusConfiguration class:
public class ItemConfiguration : IEntityTypeConfiguration<Item>
{
public void Configure(EntityTypeBuilder<Item> builder)
{
builder.ToTable("Item", "dbo").HasKey(c => c.Id);
builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
}
}
That current implementation works correctly and migrates to the database as intended. I'm simply looking for a more manageable way going forward.
My assumption is that I could extend the ItemConfiguration class to include the StatusConfiguration class but cannot find an example of that method online. I'm hoping someone with a little more experience could kindly point me in the right direction?
Let me know if additional information would be helpful.
If I understand correctly, the Status is just a base class and not a base entity participating in Database Inheritance.
In such case it's important to never refer to Status class directly inside entity model and configuration, i.e. no DbSet<Status>, no navigation properties of type Status or ICollection<Status>, no modelBuilder.Entity<Status>() calls and no IEntityTypeConfiguration<Status>.
Instead, you always have to refer to the concrete types inheriting from the Status. In order to reuse configuration code, you should use constrained generic methods or classes and pass the concrete entity types.
Since you are using IEntityTypeConfiguration classes, probably the most natural is to make your StatusConfiguration class generic:
public class StatusConfiguration<TEntity> : IEntityTypeConfiguration<TEntity>
where TEntity : Status
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
}
}
and let derived entity configuration classes derive from it:
public class ItemConfiguration : StatusConfiguration<Item>
{
public override void Configure(EntityTypeBuilder<Item> builder)
{
base.Configure(builder); // <--
builder.ToTable("Item", "dbo").HasKey(c => c.Id);
builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
}
}
so I have 2 different DbContext (ef 6.1.3 code first)
FirstDbContext
SecondDbContext
each context contains a SbSet Users that maps the user table in the corresponding database
NOTE : the data is different, DbFirst User is not DbSecond User!!
I have an abstract repository:
public abstract class Repository<TContext> where TContext : DbContext
{
public Repository(TContext ctx)
{
}
}
and 2 repositories :
public FirstRepo : Repository<FirstDbContext>
{
public FirstRepo(FirstDbContext ctx):base(ctx)
{
}
}
public SecondRepo : Repository<SecondDbContext>
{
public SecondRepo(SecondDbContext ctx):base(ctx)
{
}
}
I Have 2 different MSSQL databases related to the contexes:
DbFirst
DbSecond
I'm using dependency injection to add scoped repository and contexes, 2 database, 2 different connection.
I expected that my .Net Core application would use 2 Models
but once i get data from both the context i get
NotSupportedException: The type 'First.User' and the type
'Second.User' both have the same simple name of
'User' and so cannot be used in the same model.
Why the same model?
I know that in the same model I should have different names because EF does not look for namespaces, but in that case I shouldn't have this kind of issue.
EDIT #1 :
If I use one of the repository alone everything works as expected so i'm sure that there isn't any mispelled namespace
If I use the repositories all together i got this error, for example
var top10 = FirstRepo.GetTop10().ToList();
var sam = SecondRepo.GetByName<Second.User>("sam");
EDIT 2 (#Steve Green):
//Note that I'm not trying to do this :
public class MyTextContext : DbContext
{
public DbSet<Test.Security.Question> Security_Question { get; set; }
public DbSet<Test.Forms.Question> Forms_Question { get; set; }
}
// What I need is something like this :
public class SecurityContext : DbContext
{
public DbSet<Test.Security.Question> Question { get; set; }
}
public class FormsContext : DbContext
{
public DbSet<Test.Forms.Question> Question { get; set; }
}
Important note
If I manually ignore the "other" entity in both of the context everything works
I Remark that the context are not only in different namespaces, but also different assemblies...
// this is working!! .___.
// but i don't want to add a reference to a project just to exclude a class... it's unacceptable
public class FirstDbContext : DbContext
{
public DbSet<First.User> Users {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<Second.User>();
}
}
public class SecondDbContext : DbContext
{
public DbSet<Second.User> Users {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<First.Usere>();
}
}
Any suggestion different from renaming the table will be appreciated
Thanks
I'm having a rather interesting problem and I am not quite sure how to properly get past it. In order to fully understand my problem, please keep in mind the following:
I am "Modulizing" my features. For example I have written a "Logger" dll that is then turned into a package. This DLL has its own DbContext and knows about certain tables as a result. Then I have written a "Tracker" dll which extends the Logger dll. The tracker dll is another module with its own Db Context and its own tables. It knows about the Logger dll only in the fact that it knows about its service layer and its model layer. Let me show you what that looks like:
Here are the Models (representing tables)
//Logger Module
public class LogError : ILogError
{
public Guid Id { get; set; }
//more stuff not relavent to the problem
}
//Tracker Module
public class ErrorTicket : IErrorTicket
{
public Guid Id { get; set; }
public Guid LogErrorId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TicketNumber { get; set; }
//More properties not related to the problem
public virtual LogError LogError { get; set; }
public virtual ILogError MyLogError => LogError;
public virtual ICollection<ErrorTicketNote> ErrorTicketNotes { get; set; }
public virtual IEnumerable<IErrorTicketNote> MyErrorTicketNotes => ErrorTicketNotes;
}
Please keep in mind for the ErroTicket Class, I am using interfaces to expose certain methods. For example my interface only has getters and no setters, so when an interface is passed, the class cannot be updated. I would rather not go into a discussion as to why I do that as I am very certain it is not part of the problem. Just wanted to make a not so you understand why I have LogError and then MyLogError listed up there.
Now for my DbContext I have the following:
//Logger Module
public class LoggerDbContext : DbContext, ILoggerDbContext
{
public DbSet<Model.LogError> LogError { get; set; }
public DbSet<Model.LogInfo> LogInfo { get; set; }
public IEnumerable<ILogError> LogErrors => LogError;
public IEnumerable<ILogInfo> LogInfos => LogInfo;
public LoggerDbContext(string connectionString = "DefaultConnection") : base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
public void CreateLog(ILogError logError)
{
LogError.Add((Model.LogError) logError);
SaveChanges();
}
public void CreateLog(ILogInfo logInfo)
{
LogInfo.Add((Model.LogInfo) logInfo);
SaveChanges();
}
}
//Tracker Module
public class TrackerDbContext : DbContext, ITrackerDbContext
{
public DbSet<ErrorTicket> ErrorTicket { get; set; }
public DbSet<ErrorTicketNote> ErrorTicketNote { get; set; }
public IEnumerable<IErrorTicket> ErrorTickets => ErrorTicket;
public IEnumerable<IErrorTicketNote> ErrorTicketNotes => ErrorTicketNote;
public TrackerDbContext(string connectionString = "DefaultConnection") : base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
public void CreateTicket(IErrorTicket errorTicket)
{
ErrorTicket.Add((ErrorTicket) errorTicket);
SaveChanges();
}
public void ModifyTicket(IErrorTicket errorTicket)
{
Entry(errorTicket).State = EntityState.Modified;
SaveChanges();
}
public void CreateTicketNote(IErrorTicketNote errorTicketNote)
{
ErrorTicketNote.Add((ErrorTicketNote) errorTicketNote);
SaveChanges();
}
public void ModifyTicketNote(IErrorTicketNote errorTicketNote)
{
Entry(errorTicketNote).State = EntityState.Modified;
SaveChanges();
}
}
As you can see two DbContext classes do not know about each other, but through my models I create a relation of foreign key. Now to my problem.
When an error occurs I have code that runs the following:
//the line bellow calls a Logger service that ends up invoking the method from the Logger DbContext - public void CreateLog(ILogError logError)
var var logError = _databaseLoggerService.Error(exception, message);
//Then I try to create my ErrorTicket and I assign the logError object to the class to create the relation of the foreign key.
var currentTime = DateTime.Now;
var errorTicket = new ErrorTicket
{
Id = Guid.NewGuid(),
LogErrorId = logError.Id,
TimeCreated = currentTime,
TimeResolved = null,
TimeOfFirstOccurrence = currentTime,
TimeOfLastOccurrence = currentTime,
TotalOccurrences = 1,
Resolved = false,
Resolution = string.Empty,
CommitNumber = string.Empty,
TimeImplemented = null,
LogError = (LogError) logError
};
_trackerDbContext.CreateTicket(errorTicket);
The problem that I get is the following:
Violation of PRIMARY KEY constraint 'PK_dbo.LogError'. Cannot insert duplicate key in object 'dbo.LogError'. The duplicate key value is (769fb127-a8d8-40de-9492-fc61ca86cb16).
The statement has been terminated.
If I look at my LogError table, a record indeed exists with that key. I assume it was created when I called _databaseLoggerService.Error(exception, message);
What I do not understand, is why is this a problem for EF6 or how to get past it?
I have done a lot of research on the topic and I have found articles which state that because it is 2 sepparate DbContextes the second one may not know that the record exist so it when I caled .Add method, it marked ALL objects for insertoin and thus generated the INSERT queries. Which makes sense, and I could simply not call my db creation and let my tracker just create both objects for me. Which is all fine, however, the problem I have with that is when I try to modify 'the record, I get the exact same problem. Even though I marked the record as modify, it generates the insert queries.
My question is how do I get past this problem?
My web app uses half a dozen tables, each of which get populated when a user passes through the system. In order to do stats analysis I've written a database view to flatten these tables into a single view.
The view is working, however, I want to automate some tests around the view creation.
My idea to do this was to create a model/map and repository for the view - with list action only. My current implementation doesn't work.
This is my Repository:
namespace FunctionalTests.SpssView
{
public class SpssRepository
{
private readonly ISessionManager _sessionManager;
public SpssRepository(ISessionManager sessionManager)
{
_sessionManager = sessionManager;
}
public IList<Spss> ListFromSpssView()
{
ICriteria criteria = _sessionManager.GetSession().CreateCriteria(typeof(Spss));
return criteria.List<Spss>();
}
}
}
This is the model class:
namespace FunctionalTests.SpssView
{
public class Spss
{
public virtual String StudentId { get; set; }
public virtual String UPNSCN { get; set; }
...
}
}
And the mapping:
namespace FunctionalTests.SpssView
{
public sealed class SpssMap : ClassMap<Spss>
{
public SpssMap()
{
Id(x => x.StudentId).GeneratedBy.Assigned();
Map(x => x.UPNSCN);
...
}
}
}
I'm not entirely confident in the ID mapping - as it is just read from the view?
This is my test:
[Test]
public void ShouldPopulateAndRetrieveFromSpssView()
{
var mockSessionManager = new Mock<ISessionManager>();
mockSessionManager.Setup(x => x.GetSession()).Returns(_session);
var caseRepository = new CaseRepository(mockSessionManager.Object);
var caseList = caseRepository.ListCases();
Assert.That(caseList.Count, Is.EqualTo(2));
var repository = new SpssRepository(mockSessionManager.Object);
var spssList = repository.ListFromSpssView();
Assert.That(spssList.Count, Is.EqualTo(2));
}
Note the case list code - I put that in there to make sure the db connection was being made. This part of the test passes.
Running select * from spss; returns two results. (I'm using sql server 2005 fwiw)
And because this isn't production code, I created a new folder in my FunctionalTests visual studio project (I mention this, because it seems to me to be one of the main differences between this and my working repositories.) Should this make a difference??
Is it possible to test views like this?
Is there anyway I can see the sql that is being generated?
What am I doing wrong??!?
Thanks :)
Try adding:
public SpssMap()
{
Table("myViewBame"); // ADD THIS
Id(x => x.StudentId).GeneratedBy.Assigned();
Map(x => x.UPNSCN);
...
}
In order to see the generated SQL add this:
.ShowSql()
For example:
Fluently.Configure().Database(
MsSqlConfiguration.MsSql2005
.ConnectionString(
ConfigurationManager.ConnectionStrings["my"].ConnectionString).ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<MyClass>())
.BuildSessionFactory();