Im currently trying to implement CRUD functionality with a dbfactory and generics with microsoft EF, but while listing entries is working, making changes to the db is currently not working.
public class AbstractDataModel
{
[Key]
public Guid gid { get; set; }
}
Model
class SalesOrder : AbstractDataModel
{
public int salesOrderID { get; set; }
public int productID { get; set; }
public int customerID { get; set; }
public Guid createdBy { get; set; }
public string dateCreated { get; set; }
public string orderDate { get; set; }
public string orderStatus { get; set; }
public string dateModified { get; set; }
}
A DBCore with some other functionality besides the ones listed here, which are not relevant for the factory
public class DBCore : DbContext
{
public static string connectionString = "myConnectionStringToDb";
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString);
}
Data Service which calls factory
class SalesOrderService : DBCore
{
public DbSet<SalesOrder> SalesOrders { get; set; }
public OkObjectResult GetAllSalesOrders()
{
DBFactory factory = new DBFactory();
return new OkObjectResult(JsonConvert.SerializeObject(factory.GetAll(SalesOrders)));
}
public OkObjectResult AddSalesOrder(SalesOrder order)
{
order.gid = Guid.NewGuid();
return DBFactory.AddOne(order);
}
public OkObjectResult UpdateSalesOrder(SalesOrder order)
{
return DBFactory.UpdateOne(order);
}
public OkObjectResult DeleteSalesOrder(SalesOrder order)
{
return DBFactory.DeleteOne(order);
}
}
simple CRUD-Factory,
class DBFactory : DBCore
{
public DbSet<UserModel> Users { get; set; }
public DbSet<SalesOrder> SalesOrders { get; set; }
public List<T> GetAll<T>(DbSet<T> dbset) where T : class
{
using (this)
{
return dbset.ToList();
}
}
public static OkObjectResult AddOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Add(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully added");
}
}
public static OkObjectResult UpdateOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Update(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully updated");
}
}
public static OkObjectResult DeleteOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Attach(data);
factory.Remove(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully removed");
}
}
}
Edit: Following the advices i changed the code so it should SaveChanges for the Factory, which also contains the context as a property. But it still doesnt seem to work for all database operations except listing all entries
Editv2: Thanks for the adivces it seems i have solved that problem, but a new one appeared :D
I can now do database operations like deleting entries, but now i cant list the entries anymore because the following error occurs, although the code there didnt really change:
"Executed 'GetAllOrders' (Failed, Id=5fb95793-572a-4545-ac15-76dffaa7a0cf, Duration=74ms)
[2020-10-23T14:33:43.711] System.Private.CoreLib: Exception while executing function: GetAllOrders. Newtonsoft.Json: Self referencing loop detected for property 'Context' with type 'FicoTestApp.Models.SalesOrder'. Path '[0].ChangeTracker'."
try adding
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
to your
startup.cs
it should to the job
Related
Started to learn asp.net and DB manipulations. Trying to implement some simple functionality - two models, one has list of references to another.
Here is an error that I currently get:
An exception occurred while initializing the database. See the InnerException for details.
Inner exception:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
My models:
public class Killer
{
public Killer(string name, string biography)
{
Name = name;
Biography = biography;
KillerId = Guid.NewGuid();
}
public Guid KillerId { get; set; }
public string Name { get; set; }
public string Biography { get; set; }
public virtual Contract Contract { get; set; }
}
public class Contract
{
public Contract(Status status, Killer target, string description, params Killer[] targets)
{
ContractId = Guid.NewGuid();
this.status = status;
Target = target;
Description = description;
Killers = new HashSet<Killer>();
foreach (var t in targets) Killers.Add(t);
}
public Guid ContractId { get; set; }
public enum Status { active, done, failed, rejected, abandoned }
public Status status { get; set; }
public Killer Target { get; set; }
public string Description { get; set; }
//[ForeignKey("ContractID")]
public virtual ICollection<Killer> Killers { get; set; }
}
In context I initialize db with lists of objects
public class KillerContext : DbContext
{
public DbSet<Killer> Killers { get; set; }
public DbSet<Contract> Contracts { get; set; }
}
In controller I do:
KillerContext k = new KillerContext();
public ActionResult Index()
{
var contracts = k.Contracts.ToList();
ViewBag.contracts = contracts;
return View();
}
In Global.asax:
Database.SetInitializer(new KillerContextInitialization());
Here is how I enter first data in db:
public sealed class KillerContextInitialization : DropCreateDatabaseAlways<KillerContext>
{
protected override void Seed(KillerContext db)
{
List<Killer> killers = new List<Killer>();
//List<Contract> contracts = new List<Contract>();
killers.Add(new Killer(name: "Ivan Firstein", biography: "He was born in the shadows."));
killers.Add(new Killer(name: "Oleg Gazmanov", biography: "test man"));
db.Contracts.Add(new Contract(
Contract.Status.active,
killers.SingleOrDefault(x => x.Name == "Ivan Firstein"),
"KILL OR BE KILLED. As always with love.",
killers.SingleOrDefault(x => x.Name == "Oleg Gazmanov")
));
db.Killers.AddRange(killers);
base.Seed(db);
}
}
Looks like you need add ForeignKey attribute for killer Model, and store this key in property ContractId:
public class Killer
{
[ForeignKey(nameof(ContractId)] //Name of added property in line below
public Contract Contract { get; set; } //no need "virtual"
public Guid? ContractId { get; set; }
// other properties...
}
public class Contract
{
[ForeignKey("ContractId")] //Name of added property in Killer Model
public virtual ICollection<Killer> Killers { get; set; }
// other code...
}
EDIT
You should do something similar to the Contract.Target property:
[ForeignKey(nameof(TargetId)]
public Killer Target { get; set; }
public Guid TargetId { get; set; }
For enum types you should add attributes like this:
[Column(nameof(status), TypeName = "int")]
public Status status { get; set; }
Find out that problem was in public Killer Target { get; set; }
When i was adding data, that field was considered as NOT NULL, and all what i need to do, is save changes after filling killers, like so:
public sealed class KillerContextInitialization : DropCreateDatabaseAlways<KillerContext>
{
protected override void Seed(KillerContext db)
{
List<Killer> killers = new List<Killer>();
killers.Add(new Killer(name: "Ivan Firstein", biography: "He was born in the shadows."));
killers.Add(new Killer(name: "Oleg Gazmanov", biography: "test man"));
db.SaveChanges(); // - save killers first, then add them to contract
db.Contracts.Add(new Contract(
Contract.Status.active,
killers.SingleOrDefault(x => x.Name == "Ivan Firstein"),
"KILL OR BE KILLED. As always with love.",
killers.SingleOrDefault(x => x.Name == "Oleg Gazmanov")
));
db.Killers.AddRange(killers);
base.Seed(db);
}
}
I have .net 4.5.2 test app playing about with Azure Mobile Services and I'm attempting to store data using the TableController. I have my data types as follows:
public class Run:EntityData
{
public int RunId { get; set; }
public DateTime? ActivityStarted { get; set; }
public DateTime? ActivityCompleted { get; set; }
public List<Lap> LapInformation { get; set; }
public Run()
{
LapInformation = new List<Lap>();
}
}
public class Lap
{
[Key]
public int LapNumber { get; set; }
public int CaloriesBurnt { get; set; }
public double Distance {get; set;}
//Some other basic fields in here
public DateTime? LapActivityStarted { get; set; }
public DateTime? LapActivityCompleted { get; set; }
public Lap()
{
}
In my Startup class I call:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
And in my MobileServiceContext class:
public class MobileServiceContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString2";
public MobileServiceContext() : base(connectionStringName)
{
}
public DbSet<Run> Runs { get; set; }
public DbSet<Lap> Laps { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
}
In my controller then, I have:
[MobileAppController]
public class RunController: TableController<Run>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Run>(context, Request);
}
public IList<Run> GetAllRuns()
{
var runs = context.Runs.Include("LapInformation").ToList();
return runs;
}
public SingleResult<Run> GetRun(string id)
{
return Lookup(id);
}
public async Task<IHttpActionResult> PostRun(Run run)
{
Run current = await InsertAsync(run);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
public Task DeleteRun(string id)
{
return DeleteAsync(id);
}
}
I can then POST a record in fiddler which responds with a 201 and the Location of the newly created Item. An Example of the data I'm posting is:
{RunId: 1234, LapInformation:[{LapNumber:1,Distance:0.8, LapActivityStarted: "2017-06-19T00:00:00", LapActivityCompleted: "2017-06-19T00:00:00", CaloriesBurnt: 12}]}
However, when I GET that object, I'm only getting the fields from Run, without the list of Detail records (Lap). Is there anything I have to configure in Entity Framework so that when I GET a Run record from the DB, it also gets and deserializes all associated detail records?
Hopefully that makes sense.
EDIT
Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
You can use custom EF query with Include() method instead of Lookup call preferably overload that takes function from System.Data.Entity namespace.
var runs = context.Runs.Include(r => r.LapInformation)
Take a look at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
AFAIK, you could also use the $expand parameter to expand your collections as follows:
GET /tables/Run$expand=LapInformation
Here is my sample, you could refer to it:
You could mark your action with a custom ActionFilterAttribute for automatically adding the $expand property to your query request as follows:
// GET tables/TodoItem
[ExpandProperty("Tags")]
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
For more details, you could refer to adrian hall's book chapter3 relationships.
EDIT Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
I defined the following models in my mobile client:
public class TodoItem
{
public string Id { get; set; }
public string UserId { get; set; }
public string Text { get; set; }
public List<Tag> Tags { get; set; }
}
public class Tag
{
public string Id { get; set; }
public string TagName { get; set; }
}
After execute the following pull operation, I could retrieve the tags as follows:
await todoTable.PullAsync("todoItems", todoTable.CreateQuery());
Note: The Tags data is read-only, you could only update the information in the ToDoItem table.
Additionally, as adrian hall mentioned in Data Access and Offline Sync - The Domain Manager:
I prefer handling tables individually and handling relationship management on the mobile client manually. This causes more code on the mobile client but makes the server much simpler by avoiding most of the complexity of relationships.
I am using Entity Framework Core code-first with fluent API entity configurations, in an ASP .NET MVC Core application. My code currently compiles, but when I run add-migration in the Package Manager Console, it gives the error below:
The property 'Exam.TempId' is of type 'object' which is not supported
by current database provider. Either change the property CLR type or
manually configure the database type for it.
Searching Google for this error yields no results. Can anybody here help please?
"Exam" is a class in my domain model, but it doesn't have a "TempId" property so I guess that's something that Entity Framework is adding. It does have an "Id" property, but the type is int, not object.
I'll start by sharing the Exam class and the Exam configuration class. I can share more code if required. I'd be really grateful for any advice you can provide to resolve the problem.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace MySite.Core.Models
{
public class Exam : ActivatableEntity
{
private int _numberOfQuestionsToBeAttempted;
private Exam()
{
Topics = new Collection<Topic>();
}
public Exam(IUser createdByUser,
string name,
string description,
double timeAllowedInMinutes,
bool shuffleTopicsTogether = true) :
base(createdByUser)
{
Name = name;
Description = description;
Topics = new Collection<Topic>();
TimeAllowedInMinutes = timeAllowedInMinutes;
ShuffleTopicsTogether = shuffleTopicsTogether;
}
public string Name { get; private set; }
public string Description { get; private set; }
public double TimeAllowedInMinutes { get; private set; }
public bool ShuffleTopicsTogether { get; private set; }
public IEnumerable<Question> PossibleQuestions
{
get
{
return Topics.SelectMany(t => t.PossibleQuestions);
}
}
public int NumberOfQuestionsToBeAttempted
{
get
{
if (_numberOfQuestionsToBeAttempted != 0) return _numberOfQuestionsToBeAttempted;
foreach (Topic topic in Topics)
{
_numberOfQuestionsToBeAttempted += topic.NumberOfQuestionsToBeAttempted;
}
return _numberOfQuestionsToBeAttempted;
}
}
public IEnumerable<Topic> Topics { get; }
public void Update(IUser updatedByUser, string name, string description, double timeAllowedInMinutes, bool shuffleTopicsTogether = true)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Description = description;
TimeAllowedInMinutes = timeAllowedInMinutes;
ShuffleTopicsTogether = shuffleTopicsTogether;
Update(updatedByUser);
}
}
}
Exam configuration class
using MySite.Core.Models;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace MySite.Persistence.EntityConfiguration
{
public class ExamConfiguration
{
public ExamConfiguration(EntityTypeBuilder<Exam> entityBuilder)
{
entityBuilder.HasKey(e => e.Id);
entityBuilder.HasOne(e => (ApplicationUser)e.CreatedByUser)
.WithMany()
.HasForeignKey(e => e.CreatedByUserId)
.OnDelete(DeleteBehavior.SetNull);
entityBuilder.HasOne(e => (ApplicationUser)e.LastUpdatedByUser)
.WithMany()
.HasForeignKey(e => e.LastUpdatedByUserId)
.OnDelete(DeleteBehavior.SetNull);
entityBuilder.Property(e => e.Name).IsRequired().HasMaxLength(50);
entityBuilder.Property(e => e.Description).IsRequired().HasMaxLength(250);
entityBuilder.HasMany(e => e.Topics)
.WithOne(t => t.Exam).OnDelete(DeleteBehavior.Cascade);
}
}
}
As requested by posters, I'm adding the code for the base classes below:
using System;
namespace MySite.Core.Models
{
public abstract class ActivatableEntity :
UpdatableCreatableEntity,
IActivatable
{
protected ActivatableEntity() { }
protected ActivatableEntity(IUser createdByUser) : base(createdByUser) { }
public int? LastActivatedByUserId { get; private set; }
public IUser LastActivatedByUser { get; private set; }
public DateTime? WhenLastActivated { get; private set; }
public int? LastDeactivatedByUserId { get; private set; }
public IUser LastDeactivatedByUser { get; private set; }
public DateTime? WhenLastDeactivated { get; private set; }
public bool IsActive { get; private set; }
protected virtual void Activate(IUser activatedByUser)
{
LastActivatedByUser = activatedByUser ?? throw new ArgumentNullException(nameof(activatedByUser));
LastActivatedByUserId = activatedByUser.Id;
WhenLastActivated = DateTime.Now;
IsActive = true;
}
protected virtual void Deactivate(IUser deactivatedByUser)
{
LastDeactivatedByUser = deactivatedByUser ?? throw new ArgumentNullException(nameof(deactivatedByUser));
LastDeactivatedByUserId = deactivatedByUser.Id;
WhenLastDeactivated = DateTime.Now;
IsActive = false;
}
}
public abstract class UpdatableCreatableEntity :
CreatableEntity,
IUpdatable
{
protected UpdatableCreatableEntity() { }
protected UpdatableCreatableEntity(IUser createdByUser) : base(createdByUser) { }
public int? LastUpdatedByUserId { get; private set; }
public IUser LastUpdatedByUser { get; private set; }
public DateTime? WhenLastUpdated { get; private set; }
protected virtual void Update(IUser updatedByUser)
{
LastUpdatedByUser = updatedByUser ?? throw new ArgumentNullException(nameof(updatedByUser));
LastUpdatedByUserId = updatedByUser.Id;
WhenLastUpdated = DateTime.Now;
}
}
public abstract class CreatableEntity :
IIdentifiable,
ICreatable
{
protected CreatableEntity() { }
protected CreatableEntity(IUser createdByUser)
{
CreatedByUser = createdByUser ?? throw new ArgumentNullException(nameof(createdByUser));
CreatedByUserId = createdByUser.Id;
WhenCreated = DateTime.Now;
}
public int Id { get; private set; }
public int? CreatedByUserId { get; private set; }
public DateTime WhenCreated { get; private set; }
public IUser CreatedByUser { get; private set; }
}
}
I faced same problem and it confused me a lot. But luckily I was using version control, so I was able to trace reasons of the issue.
For me it was many-to-many relation entity model with constructor that assigns values to fields. I was relying to Visual Studio to generate properties for me automatically, and VS did poor job not detecting type of the property that later became a key.
VS created property of type object, which is too generic and hardly could be translated into underlying database abstractions. Hence the error.
I agree, quite not descriptive, hope they will fix that in future versions.
So try to search for properties of object type and check, are they used as keys, if yes, try to replace them with specific types supported by your database provider.
Reported error for developers: #9817.
I am using StructureMap for my dependency resolving. I am faced with an issue I don't know how to solve as the only way to make a form posting I understand will end up being without dependency injection which negate the whole essence of having StructureMap in my project.
The error is that I don't have Add method in my IdbContext Model. Below is my approach.
I have a Model named Module defined as below
public class Module
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public virtual DateTime? DateCreated { get; set; }
public virtual string ModuleDescription { get; set; }
}
and an interface defined as below.
public interface ISolnetDataSource
{
IQueryable<Module> Modules { get; }
void Save();
}
A DbContext like below
public class CMSDB : DbContext, ISolnetDataSource
{
public CMSDB() : base("DefaultConnection")
{
}
public DbSet<Module> Modules { get; set; }
void ISolnetDataSource.Save()
{
SaveChanges();
}
IQueryable<Module> ISolnetDataSource.Modules
{
get { return Modules; }
}
}
and my controller defined as
[HttpPost]
public ActionResult Index(CreateModulesViewModels module)
{
ViewBag.ListModule = _db.Modules.ToList();
if (ModelState.IsValid)
{
var model = new CreateModulesViewModels();
var CreateModule = new Module();
CreateModule.Name = module.Name;
CreateModule.ModuleDescription = module.ModuleDescription;
CreateModule.DateCreated = DateTime.Now;
//_db.modules.Add(CreateModule);
_db.Save();
return View(model);
}
return View(module);
}
The challenge I have with this is that I could'nt do _db.modules.Add(CreateModule) (the commented line in the controller) so as to add a new record. What I'm doing wrong. I want to do this using the best approach applicable.
What about:
_db.modules.Entry(CreateModule).State = System.Data.EntityState.Added;
I have a class like this:
[Table("member_activation")]
public partial class MemberActivation
{
[Key]
public Int64 member_id { get; set; }
public String token { get; set; }
}
My db:
public class SMADbContext : DbContext
{
public SMADbContext() : base("SMADB")
{
Database.SetInitializer<SMADbContext>(new NullDatabaseInitializer<SMADbContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Member> Members { get; set; }
public DbSet<MemberActivation> MemberActivations { get; set; }
public DbSet<ApiAccount> ApiAccounts { get; set; }
public DbSet<ApiHardware> ApiHardwares { get; set; }
public DbSet<MemberRelation> MemberRelations { get; set; }
}
In my controller:
[Route("tester")]
[AllowAnonymous]
public IHttpActionResult tester()
{
using (var db = new SMADbContext())
{
var memberActivation = new MemberActivation();
memberActivation.member_id = 10155;
memberActivation.token = "hello";
db.MemberActivations.Add(memberActivation);
return Json(new { dbset = db.MemberActivations.ToList(), memberAct = memberActivation });
}
}
db.MemberActivations.Add(memberActivation); does not work. When I return the json, the dbset does not include the newly created memberActivation. I do not have db.SaveChanges() because it will not save until the memberActivation is pushed to the dbset
You cant set member_id, it is the key and ef uses it as identity. It will be ignored. You can configure ef so that member_id is not identity but that's another topic.
db.MembershipActivations.Add( new MemberActivation { token = "hello"}):
db.SaveChanges();
should work fine.
if however , as it would appear , you have an existing member and you are trying to set a relationship with that entity via a join table. Then you should retrieve that entity and set the memberactivation. Ef will sort the rest out for you. Bit of guessing here as i would need to see the involved entities.