Saving multiple objects - entity-framework

I have the following classes:
public class Test
{
public int Id { get; set; }
public string Title { get; set; }
public List<TestQuestion> Questions { get; set; }
}
public class TestQuestion
{
public int Id { get; set; }
public int TestId { get; set; }
public string Text { get; set; }
public List<TestQuestionAnswer> Answers { get; set; }
}
public class TestQuestionAnswer
{
public int Id { get; set; }
public int TestQuestionId { get; set; }
public string Text { get; set; }
public bool? IsCorrect { get; set; }
}
I'm having some problems with Save method in TestRepository.
Here's the logic:
If Test.Id > 0 then update Test, otherwise create a new one
If TestQuestion.Id > 0 and TestQuestion.Text = "" delete TestQuestion from database and all Answers for that object
If TestQuestion.Id == 0 then create a new row in database
If TestQuestion.Id > 0 and TestQuestion.Text != "" then update that row
If TestQuestionAnswer.Id > 0 and Text = "" then delete it from database, otherwise call create or update method.
I'm using Entity Framework Code First, but I'm willing to switch to classic ADO.NET if that would make this job much easier.
Any help would be greatly appreciated!

Given the apparent complexity of your update, it would probably make sense to move its logic to a stored procedure, and then map your updates to that stored procedure.

There are two ways you can make this simple and as you said, you would like to switch to classic ADO.NET,
Create a Stored Procedure and place all of these logic in there. This can be done in a single Transction.
Write the ADO.NET coding with related QUERY's.
I personally prefer first option since that will make use of the existing Entity Framework.
Let me know further direction and also may I know the issues while using Save method in TestRepository.

Related

EF6 Code first - skip binary (or any other) columns during load()

I have the ReportingActivity entity class.
public class ReportingActivity
{
public int ReportingActivityID { get; set; }
public DateTime ReportingActivitySend { get; set; }
public string Remark { get; set; }
public string SendersCSV { get; set; }
public string MailSenderStatus { get; set; }
public long RptGenerationCostMiliseconds { get; set; }
public DateTime RptGeneratedDateTime { get; set; }
public string RptGeneratedByWinUser { get; set; }
public string RptGeneratedOnMachine { get; set; }
public Int64 Run { get; set; }
public byte[] AttachmentFile { get; set; }
public virtual Report Report { get; set; }
public virtual Employee Employee { get; set; }
public virtual ReportingTask ReportingTask { get; set; }
}
I use this code to load data:
ctxDetail = new ReportingContext();
ctxDetail.ReportingActivity
.Where(x => x.Employee.EmployeeID == currentEmployee.EmployeeID)
.Load();
My code gets all the columns in (like SELECT * FROM... )
My question is how to skip the byte[] column, ideally recommend me a way how to improve my lines of code to be able specify exact list of columns.
Normally when dealing with a schema where records have large, seldom accessed details, it is beneficial to split those details off into a separate table as David mentions /w a one-to-one relationship back to the main record. These can be eager or lazy loaded as desired when they are needed.
If changing the schema is not practical then another option when retrieving data from the table is to utilize Projection via Select to populate a view model containing just the fields you need, excluding the larger fields. This will help speed up things like reads for views, however for things like performing updates you will still need to load the entire entity including the large fields to ensure you don't accidentally overwrite/erase data. It is possible to perform updates without loading this data, but it will add a bit of complexity and risk of introducing bugs if mismanaged later.
You can use Table Splitting, and optionally Lazy Loading to have only commonly needed columns loaded.
See
https://learn.microsoft.com/en-us/ef/core/modeling/table-splitting
This is for EF Core but it works the same on EF6 code-first.

Vertical partitioning in Entity Framework Code First

I use Entity Framework Code First to access my SQL Server database. The "Client" table currently has about 90 columns:
[Table("Clients")]
public class Client
{
public int Id { get; set; }
public string Property1 { get; set; }
...
public string Property90 { get; set; }
}
I have decided to vertically partition this table into 3 tables, because often not all the properties are used. However, I still have legacy code (that I can't change right now) that expects the full Client object with all 90 columns.
My solution so far is to split the Client class into 3 classes corresponding with the new tables, and then use Table Per Type inheritance to allow the legacy code to access the Client object as though the original Clients table is still there:
[Table("Clients")]
public class Client: Client1
{
public int Id { get; set; }
public string Property1 { get; set; }
...
public string Property30 { get; set; }
}
[Table("Client1s")]
public class Client1: Client2
{
public int Id { get; set; }
public string Property31 { get; set; }
...
public string Property60 { get; set; }
}
[Table("Client2s")]
public class Client2
{
public int Id { get; set; }
public string Property61 { get; set; }
...
public string Property90 { get; set; }
}
However, this somehow seems a bit clunky to me.
Is there a more elegant way to achieve vertical partitioning with Entity Framework Code First?
So, considering you refer to the existing approach as being used by "legacy" systems and your new partitioned approach is most likely intended to be the new "correct" way going forwards, my advice would be to keep them as separated as possible.
What you could look to do is replace the existing monolithic Clients table with a database view that joins the 3 separate, partitioned tables back together. Then you can hook up the existing Clients class in all it's former glory to the view, leaving your legacy systems relatively untouched, in theory.
I'd also recommend ditching the inheritance idea and leaving the 3 new partitioned classes completely independent of one another. Otherwise, both legacy and new systems will be extremely sensitive to any changes being made to classes and properties within that entire inheritance chain.
By doing it this way you are then free to change and evolve the new classes independently and modify any underlying table structures however you see fit in the future. Providing you maintain the views integrity and consistency, your legacy systems should continue to function as normal without any repercussions or regressions, mostly :-)
In my humble experience, shielding the old from changes in the new far outway the slight inconveniences of having some code duplication and stricter boundaries.
To answer your elegance question more directly I'd say that insulating your classes against unnecessary coupling and avoiding the "ripple effect" yields the more elegant solution.
Anyway, I hope it helps.
I'm going to assume your actual classes have much more meaningful property names, right?
// LEGECY SYSTEMS
[Obsolete("Use the newer partitioned classes going forwards")]
[Table("vClients")]
public class Client
{
public int Id { get; set; }
public string Property1 { get; set; }
...
public string Property90 { get; set; }
}
// NEW STRUCTURES
[Table("Client1s")]
public class Client1
{
public int Id { get; set; }
public string Property1 { get; set; }
...
public string Property30 { get; set; }
}
[Table("Client2s")]
public class Client2
{
public int Id { get; set; }
public string Property31 { get; set; }
...
public string Property60 { get; set; }
}
[Table("Client3s")]
public class Client3
{
public int Id { get; set; }
public string Property61 { get; set; }
...
public string Property90 { get; set; }
}
Updating the the base tables via the view can be done using some INSTEAD OF triggers, like so:
CREATE TRIGGER ClientsLegacyInsertAdapter on vClients
INSTEAD OF INSERT
AS
BEGIN
BEGIN TRANSACTION
INSERT INTO Client1s
SELECT Id, PropertyA1, Property2, ..., Property30
FROM inserted;
INSERT INTO Client2s
SELECT Id, Property31, Property32, ..., Property60
FROM inserted;
INSERT INTO Client3s
SELECT Id, Property61, Property62, ..., Property90
FROM inserted;
COMMIT TRANSACTION
END
You should be able to use the same technique for UPDATE and DELETE commands also.

How to setup my code as DB First in an ORM

I have looked at using EF, nHibernate and Dapper/Dapper.SimpleCRUD. In none of them can I figure out how to represent my use case in regards to my database (SQL Server 2012) model. I am building an ASP.NET website with a grid in C# 4.0/.NET 4.0 (due to technical limitations) that will have CRUD capabilities, with the initial state of the grid being set by dropdowns.
My two tables are set up as such:
Address_Book
|_[EntryID]
|_[Last_Name]
|_[First_Name]
|_[Title]
|_[Office_Num]
|_[Cell_Num]
|_[Home_Num]
|_[Email_Address]
|_[Special_Info]
|_[hr24_Emails]
|_[hr48_Emails]
|_[RM_Emails]
|_[Prestige_Emails]
|_[GEB_Emails]
|_[LAW_Emails]
Distribution
|_[Brand]
|_[Location_Mnemonic]
|_[Location_Code_Numeric]
|_[EntryID]
|_[Division_Mnemonic]
|_[Region_Mnemonic]
|_[Zone_Mnemonic]
|_[District_Mnemonic]
|_[Key]
With a many-to-one relationship between Distribution and Address_Book where Address_book.EntryID = Distribution.EntryID.
Any help with how to set this up would be appreciated. I am having issues managing the CRUD operations manually, so I thought an ORM would help, but I cannot figure it out. Any help is appreciated.
Thanks in advance!
The whole .net CRUD thing is a big realm with a lot of flavors and ways of doing the work. And while I don't know exactly where you are at with this, the following my help out. In my experience EF can handle relationships quite well, though the whole EF learning process is a bit steep and I've shied away from it. I typically use Dapper with extensions and do stuff pseudo-manually. I haven't used the SimpleCrud extension. Since you inherited the DB, hopefully it's set up well and there's a FK constraint on Distribution, Column EntryID.
In Dapper, you could set up your classes like:
using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace Jacrys
{
[Table("dbo.address_book")]
public partial class AddressBook
{
[Dapper.Contrib.Extensions.Key]
public int EntryID { get; set; }
public string Last_Name { get; set; }
public string First_Name { get; set; }
public string Title { get; set; }
public string Office_Num { get; set; }
public string Cell_Num { get; set; }
public string Home_Num { get; set; }
public string Email_Address { get; set; }
public bool Special_Info { get; set; }
public bool hr24_Emails { get; set; }
public bool hr48_Emails { get; set; }
public bool RM_Emails { get; set; }
public bool Prestige_Emails { get; set; }
public bool GEB_Emails { get; set; }
public bool LAW_Emails { get; set; }
//use this only if you need all of the distributions to be
//part of your main AddressBook class
public IEnumerable<Distribution> Distributions { get; set; }
public static AddressBook GetById(short id)
{
using (IDbConnection cn = new SqlConnection("getConnString"))
{
cn.Open();
return cn.Get<AddressBook>(id);
}
}
public static IEnumerable<AddressBook> GetAll()
{
using (IDbConnection cn = new SqlConnection("getConnString"))
{
cn.Open();
return cn.GetAll<AddressBook>();
}
}
public int Insert()
{
using (IDbConnection cn = new SqlConnection("getConnString"))
{
cn.Open();
return (int)cn.Insert(this);
}
}
public bool Update()
{
using (IDbConnection cn = new SqlConnection("getConnString"))
{
cn.Open();
return cn.Update(this);
}
}
public bool Delete()
{
using (IDbConnection cn = new SqlConnection("getConnString"))
{
cn.Open();
return cn.Delete(this);
}
}
}
[Table("dbo.distribution")]
public partial class Distribution
{
[Dapper.Contrib.Extensions.Key]
public int Key { get; set; }
public int EntryID { get; set; }
public string Brand { get; set; }
public string Location_Mnemonic { get; set; }
public int Location_Code_Numeric { get; set; }
public string Division_Mnemonic { get; set; }
public string Region_Mnemonic { get; set; }
public string Zone_Mnemonic { get; set; }
public string District_Mnemonic { get; set; }
//similar CRUD methods to AddressBook follow here
}
}
Then with a GridView like:
<asp:GridView ID="gvAddresses" runat="server" AutoGenerateColumns="true" DataKeyNames="EntryID">
</asp:GridView>
You can load it up in the code behind with (and adding lambda expression for pre-sorting):
gvAddresses.DataSource = Jacrys.AddressBook.GetAll().OrderBy(c=>c.Last_Name);
Any dropdowns you need can be loaded in similar ways.
Everything depends on your needs. If you have a gridview of Distribution, then you can add in a dropdown for Address when in edit mode (in the row data bound event). When you go to update and save, you'll have to find the control in the row and parse it's selected value before saving the record. There really isn't a nifty one single way to get this done. Though, if you have all of your business classes set up with CRUD methods you can wire it all together more simply.

How do I add index for a Model First entity in Entity Framework (EF6)?

I'm using EF6.1 with Model First approach, and I want to add an index to some database columns.
Is there a more convenient way than the one described in Add index in EF Model First design?
I would prefer to generate the index in the initial SQL, and avoid a migration.
EF 6.1 added IndexAttribute so that you can specify an index on a property like so:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[Index]
public int Rating { get; set; }
public int BlogId { get; set; }
}
It is talked about in detail here : http://msdn.microsoft.com/en-us/data/jj591583.aspx#Index

How to create a LINQ to Entities query including both tag class name and text string

My ASP.NET MVC project has some
chapter class
and related
tag class
They have many to many relationship though one tag has multiple chapter and also one chapter has multiple tags on it.
My question is: I want to make a query by using tag.title within this structure but LINQ doesn't allow objects; it only gets variables like string, int ...etc.
I am feeling a little bit stupid but I can't figured it out this basic thing :( (May be I have to give a break to let it be clear in my mind)
My models are:
public partial class Chapter
{
public string Id { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public partial class Tag
{
public int? TagId { get; set; }
public string Title { get; set; }
public int CallCount { get; set; }
public virtual ICollection<Chapter> Chapters { get; set; }
}
and also for db:
public class DataContext : DbContext
{
public DbSet<Chapter> Chapters { get; set; }
public DbSet<Tag> Tags { get; set; }
}
so here we go:
var tag = (from t in db.Tags where t.Title.Contains(search_word)).FirstOrDefault();
var chapters = (from c in db.Chapters where (m.Title.Contains(search_word) || m.Tags.Contains(tag)) select c).ToList();
As I explained above; it gives
"you should use string, guid, int... on queries"
error for this query. I think there is a logical failure I made but my brain stopped responding...
Briefly; I want to search by tag names and if you have another approach I will be pleased to listen it..
Any help will be highly appreciated.
Thanks!
You want:
var tag = (from t in db.Tags
where t.Title.Contains(search_word)
select t).FirstOrDefault();
var chapters = (from c in db.Chapters
where m.Title.Contains(search_word)
|| m.Tags.Any(t => t.TagId == tag.TagId)
select c).ToList();