many-to-many, poco, ef4 - entity-framework-4

I have 3 entities:
Goods [GID(PK), GoodName]
Persons [PID(PK), PersonName]
Roles [RID(PK), RoleName]
But now I need to associate these object with each other.
In other words, each Good can have MANY Persons in MANY Roles.
I have a table in DB with 3 fields (GID, PID, RID)
For example:
Book (GID#1), can have 3 associated persons:
1. Jack (PID#1) in role Author (RID#1)
2. Jack (PID#1) in role Editor (RID#2)
3. Bill (PID#2) in role Painter (RID#3)
How can I map this in POCO format in Entity Framework 4?

I believe you have to create another PersonRoles header where you store the Person-Role relation, then you access the person+role via this one:
PersonRoles [PRID(PK), PersonName, RoleName] (note: you could also do this withnow entitykey, just relationships, EF will eliminate this entity and give a direct Person.Roles entity, which you can do access it via Book.Persons.Select((p) p.Roles)).
PersonRole#1: Jack#1/Author#1
PersonRole#2: Jack#1/Editor#2
PersonRole#3: Bill#2/Painter#3
Book.PersonRole = context.PersonRoles.
SingleOrDefault((pr) => pr.Person.PersonId == 1 && pr.RoleId == 1);
Note: my main lang is VB.NET so I apologize for the pseudu code above, but I hope you get the idea.
Update
It should be like:
<DataContract(IsReference:=True)>
<KnownType(GetType(Good))>
<KnownType(GetType(Person))>
<KnownType(GetType(Role))>
Partial Public Class GoodPersonRole
Implements IObjectWithChangeTracker
Implements INotifyPropertyChanged
<DataMember()>
Public Property GoodId() As Integer
Get
Return _goodId
End Get
Set(ByVal value As Integer)
If Not Equals(_goodId, value) Then
If ChangeTracker.ChangeTrackingEnabled AndAlso ChangeTracker.State <> ObjectState.Added Then
Throw New InvalidOperationException("The property 'GoodId' is part of the object's key and cannot be changed. Changes to key properties can only be made when the object is not being tracked or is in the Added state.")
End If
If Not IsDeserializing Then
If Good IsNot Nothing AndAlso Not Equals(Good.GoodId, value) Then
Good = Nothing
End If
End If
_goodId = value
OnPropertyChanged("GoodId")
End If
End Set
End Property
Private _goodId As Integer
<DataMember()>
Public Property Good() As Good
Get
Return _good
End Get
Set(ByVal value As Good)
If _good IsNot value Then
If ChangeTracker.ChangeTrackingEnabled AndAlso ChangeTracker.State <> ObjectState.Added AndAlso value IsNot Nothing Then
' This the dependent end of an identifying relationship, so the principal end cannot be changed if it is already set,
' otherwise it can only be set to an entity with a primary key that is the same value as the dependent's foreign key.
If Not Equals(GoodId, value.GoodId) Then
Throw New InvalidOperationException("The principal end of an identifying relationship can only be changed when the dependent end is in the Added state.")
End If
End If
Dim previousValue As Good = _good
_good = value
FixupGood(previousValue)
OnNavigationPropertyChanged("Good")
End If
End Set
End Property
Private _good As Good
End Class
(part from the generated entity by ADO.NET VB POCO Entity Generator)
I just copied the 'Good' Id and nav. property but it should be the three of them.

Related

Returning a specific string format from an array

I have an array of Persons (class contain name and lastname and id )
what I have to do is to return a string form this array but in a specific format an example will be more explicative
array=[PERS1,PERS2]
I need this as a return value : "The name of all persons : "+ PERS1.name + PERS1.LASTN + " , " + PERS2.name +PERS2.LASTN +","
I know this method
array.each{ |per|
#but this will not return the format ,and with each I think I can only print (I'm new in the ruby field
}
all of this because I need it when overriding to_s , because I need to provide a string -> to_s
def to_s
"THE name of all preson"+#array.each #will not work as I want
end
Thank you for ur time and effort and if you need any clarification please let me know
each just iterates over a collection and returns the collection itself. You may want to use map and join the result.
array.map { |person| "#{person.name} #{person.lastn}" }.join(',')
Or if you modify your Person class it can be even simpler.
# I assume that the name of the class is Person and name and lastn are always present
class Person
def full_name
"#{person.name} #{person.lastname}"
end
end
# Then you can call this method on `map`.
array.map(&:full_name).join(',')
Try this,
array.each do |per|
"#{per.name} #{per.LASTN}"
end
For more info check Interpolation

Rails - dynamic method creation

I have two methods that are identical apart from the ActiveRecord class they are referencing:
def category_id_find(category_name)
category = Category.find_by_name(category_name)
if category != nil
return category.id
else
return nil
end
end
def brand_id_find(brand)
brand = Brand.find_by_name(brand)
if brand != nil
return brand.id
else
return nil
end
end
Now, I just know there must be a more Railsy/Ruby way to combine this into some kind of dynamically-created method that takes two arguments, the class and the string to find, so I tried (and failed) with something like this:
def id_find(class, to_find)
thing = (class.capitalize).find_by_name(to_find)
if thing.id != nil
return thing.id
else
return nil
end
end
which means I could call id_find(category, "Sports")
I am having to populate tables during seeding from a single, monster CSV file which contains all the data. So, for example, I am having to grab all the distinct categories from the CSV, punt them in a Category table then then assign each item's category_id based on the id from the just-populated category table, if that makes sense...
class is a reserved keyword in Ruby (it's used for class declarations only), so you can't use it to name your method parameter. Developers often change it to klass, which preserves the original meaning without colliding with this restriction. However, in this case, you'll probably be passing in the name of a class as a string, so I would call it class_name.
Rails' ActiveSupport has a number of built in inflection methods that you can use to turn a string into a constant. Depending on what your CSV data looks like, you might end up with something like this:
def id_find(class_name, to_find)
thing = (class_name.camelize.constantize).find_by_name(to_find)
...
end
If using a string, you can use constantize instead of capitalize and your code should work (in theory):
thing = passed_in_class.constantize.find_by_name(to_find)
But you can also pass the actual class itself to the method, no reason not to:
thing = passed_in_class.find_by_name(to_find)

Rails - Legacy database integration

I have a rails app with both a native rails database and a legacy database.
I query these legacy tables just fine, BUT I have to insert records into them as well.
But the Legacy table does not conform to rails convention WRT the key. Instead of an auto incrementing integer, the Legacy Key is a string (always numeric characters 0-9, but still a string attribute in the DB). The Legacy database has a table where the current index value is stored, and an insert operation requires reading from the 'current_index' table the current index value, incrementing it by 1, and saving the value back to 'index_table', then returning the new value, which is used in the insert statement for the new key value.
Right now I have the hand crafted SQL required to insert records scattered in various controllers. I'd like to clean that up, and move it into the model.
Currently I do this:
legacy_table.db:
class LegacyTable < Ldb
self.table_name = "legacyTableName"
self.primary_key "legacyKeyName"
end
in controller:
def insert_legacy_record(attrs)
result = Ldb.connection.exec_query("
DECLARE #key int;
EXEC dbo.getKeyField #key OUTPUT, 'RecordKey';
SELECT #key as ref_key;
")
newkey = result.first['ref_key']
result = Ldb.connection.exec_query("
INSERT INTO legacyTableName
(legacyKeyName,foo,...)
VALUES
('#{newkey}','#{attrs[:foo]}',...)
")
end
This is a pain, as I have to manually maintain the list of table attributes and their corresponding values input as attrs.
Can I just override the index generation part of activeRecord? Such that I could do this:
OPTION1
#item = LegacyTable.new
#item.attributes(data)
#item.save <=== somehow override new index value generation
Or should I just override the new method, and have it return the new key value it generates?
OPTION2
newkey = LegacyTable.new(data)
#new = LegacyTable.find(newkey)
I know how to do OPTION2, is OPTION1 possible?
You could create an initializer:
module Extensions
module LegacyModelId
extend ActiveSupport::Concern
included do
before_create :generate_legacy_id
def generate_legacy_id
result = Ldb.connection.exec_query("
DECLARE #key int;
EXEC dbo.getKeyField #key OUTPUT, 'RecordKey';
SELECT #key as ref_key;
")
self.id = result.first['ref_key']
end
end
end
end
In each of your legacy models:
class LegacyModelFoo < ActiveRecord::Base
include Extensions::LegacyModelId
...
end
This will add the before_create callback on each model you include the extension in, causing it to do the lookup and assign the new id.
How about overriding create method in your LegacyTable model like this:
def create
# new_id = The code you use to get/set a new id for insertion
self.id = new_id
super # continue as normal
end

Rendering a textbox for a complex type?

I have been reading the various blog posts on how to deal with a Complex Type in EF including the brad wilson post which most everyone seems to link. owever i dont really get it. I am using EF code first development, MVC and VB. From what i have read so far to render a editor field for a complex types requires a custom object, right? However i dont really understand what code i need to put into the custom template. Can someone break it down for me as to what code needs to go into custom templete so i can render a textbox for the PostTags icollection?
My classes:
Public Class Post
Inherits EntityBase
<Key()> Property PostId As Integer
<DisplayName("Title")> <Required()> Property PostTitle As String
<UIHint("MultilineText")> <DisplayName("Text")> Property PostText As String
ReadOnly Property PostExcerpt As String
Get
If PostText.Length > 100 Then
Return Helpers.TruncateHelper.TruncateAtWord(PostText, 250)
Else : Return PostText
End If
End Get
End Property
<ScaffoldColumn(False)> Property PostDateCreated As DateTime
<ScaffoldColumn(False)> Property PostDateModified As DateTime?
<ScaffoldColumn(False)> Property PostDatePublished As DateTime?
<DisplayName("Publish?")> Property PostIsPublished As Boolean
<DisplayName("Allow Comments?")> Property CommentsAllowed As Boolean
<ScaffoldColumn(False)> Property CategoryId As Integer?
<UIHint("Text")> <DisplayName("Category")> <Required()> Property PostCategory As String
Property Comments As IList(Of Comment)
'Post has collection of Tags
<DisplayName("Tags (comma separated)")> Overridable Property PostTags As ICollection(Of Tag)
End Class
Public Class Tag
Dim _rdsqlconn As RDSQLConn
<Key()> Property TagId As Int32
Property PostId As Int32
Property TagWord As String
'Overridable property to access Post
Overridable Property Post As Post
End Class
Solved.
Created the template Tags.vbhtml in the EditorTemplates folder and applied to PostTags. the template has the textbox which is shown in the view.

Unit testing controller actions with complex viewModels

I'm just getting going with ASP.NET MVC and I'm also new to unit testing :) So far, so good.
I have a controller action that sets up an index view using a viewmodel. Testing the controller action is straight-forward as I can pass a fake service class in the controller's constructor, but my viewmodel is quite complex and fetches it's own service class on instantiation.
Code should make this clearer I hope...
Controller action:
Function Index(ByVal id As Integer?) As ActionResult
Dim totalCount As Integer = 0
Dim selectedClient As Integer
If id Is Nothing Then
selectedClient = _portalClientService.GetFirstClient().ID
Else
selectedClient = id
End If
Dim users As MembershipUserCollection = _membershipService.GetUsersByClientId(selectedClient, 0, 1000, totalCount)
Return View(New UserListViewModel(users, selectedClient))
End Function
Viewmodel class:
Public Class UserListViewModel
Private _clientService As IPortalClientService
Public Sub New(ByVal users As MembershipUserCollection, ByVal selectedClient As Integer)
Me.New(users, selectedClient, Nothing)
End Sub
Public Sub New(ByVal users As MembershipUserCollection, ByVal selectedClient As Integer, ByVal clientService As IPortalClientService)
_users = users
_clientService = If(clientService, New PortalClientService)
_clients = New SelectList(_clientService.GetClients.OrderBy(Function(c) c.ClientName), "ID", "ClientName", selectedClient)
End Sub
Private _users As MembershipUserCollection
Public Property Users() As MembershipUserCollection
Get
Return _users
End Get
Set(ByVal value As MembershipUserCollection)
_users = value
End Set
End Property
Private _clients As SelectList
Public Property Clients() As SelectList
Get
Return _clients
End Get
Set(ByVal value As SelectList)
_clients = value
End Set
End Property
End Class
EDIT:
When testing the controller action, how do I get the viewmodel to use a fake service class?
Should I just ditch the first constructor and always pass in the service from the controller or is there another way?
Cheers,
Nick
I am probably splitting hair, but I would say your model is more a domain model than a view model. Remove the dependency to the IPortalClientService, or at least do not let the model instantiate it by itself.
I prefer to remove such dependencies away from the view, and over to the controller.
Actually, this is a pattern that we use all the time in our public facing API's, and demonstrates good use of dependency injection. I would pass this in a code review with no problems.
Your implementation gives the user the option to flexibly create the object, and provides for testability.
The only "problem" is that your tests can't easily cover the one line of code in the first constructor, but that's only a problem if you have someone who's fanatical about code coverage - which is usually a problem in itself.

Resources